Skip to content

Latest commit

 

History

History
110 lines (84 loc) · 11.2 KB

File metadata and controls

110 lines (84 loc) · 11.2 KB

YAFFA - Claude Code Context

Project Overview

YAFFA (Yet Another Free Financial Application) is a self-hosted personal finance web application. It enables multi-account/currency tracking, transaction categorization, investment monitoring, and long-term financial planning. Conscious manual tracking is a core product value — it is not a bank-sync tool.

Tech Stack

  • PHP 8.4 / Laravel 13 — MVC + Services architecture
  • Vue 3 (Options API) + Bootstrap 5.3 + CoreUI — multi-page app, NOT a SPA
  • MySQL 8 — primary database
  • Redis — queue backend for background jobs
  • Vite — asset bundling
  • Laravel Sail — Docker-based local development

Key Commands

All PHP/Artisan/Composer/Node commands must be prefixed with vendor/bin/sail:

vendor/bin/sail up -d                     # start services
vendor/bin/sail artisan migrate           # run migrations
vendor/bin/sail npm run dev               # build assets (dev)
vendor/bin/sail npm run build             # build assets (production)
vendor/bin/sail artisan test --compact    # run tests
vendor/bin/sail bin pint --dirty          # fix PHP code style

Critical Rules

  • NEVER modify .env — ask the user instead; it is off-limits at all times
  • Never comment on a GitHub issue or PR (gh issue comment, gh pr comment, gh pr review, or the raw API) without asking first. Draft the comment and ask; post only after explicit approval. Creating PRs/issues, pushing commits, and other git operations already covered by an established collaborative workflow are unaffected — this is scoped to posting comments specifically.
  • Always rebuild assets after JS/Vue/SCSS changes before testing UI
  • Always run Pint and PHPStan before finalizing PHP changes
  • Run only the minimum affected tests, then ask if the full suite should follow
  • Do not add dependencies or restructure directories without user approval
  • QIF/CSV import — system profiles are code-only: FileImportProfile rows of type = system (with executable matching_rules) are defined solely in SystemFileImportProfileRegistry and applied via artisan app:import:sync-system-profiles at deploy time. Never add an API/UI path that lets a user create or mutate a system-typed profile or set options_json.matching_rules/actions on a user-typed one — see .ai/docs/features/qif-csv-import/permissions.md and architecture.md (ReDoS risk note).
  • API personal access token abilities are enforced on every API controller, not just token-management and 2FA-management. Each controller's middleware() (via Illuminate\Routing\Controllers\HasMiddleware) declares abilities:read/abilities:write/abilities:settings per action, scoped with Illuminate\Routing\Controllers\Middleware's only: parameter — a no-op for session requests (TransientToken::can() is always true), a real gate for bearer tokens. The per-controller mapping, rationale, and the five config/credential controllers that are settings-gated on every action (including reads) are documented in .ai/docs/features/api-access-and-2fa/permissions.md. Coverage is pinned by tests/Feature/API/ApiAbilityEnforcementTest.php (one deny + one allow case per controller) — keep that data provider in sync whenever a new API controller or action is added.
  • A stray storage/app/duskapiconf_tmp.txt silently overrides live config() values project-wide, for any artisan command, not just Dusk runs. alebatistella/duskapiconf (used by tests/DuskTestCase.php via setConfig()/getConfig()) persists overrides to this file, and its service provider re-applies the whole file to config() on every non-production boot until the file is deleted. DuskTestCase::tearDown() deletes it after every Dusk test, but a killed test run (Ctrl+C, OOM) can still leak it. If a config('yaffa.*') value looks wrong in this dev environment for no reason you can find in code (e.g. sandbox_mode stuck true), check this file before chasing anything else — cat storage/app/duskapiconf_tmp.txt then rm it. See tests/CLAUDE.md "Dusk-Specific" for the full mechanism.
  • Money/quantity model attributes are cast via App\Casts\MoneyCast/App\Casts\DecimalCast (brick/math/brick/money), never a blanket 'float' cast. MoneyCast (an actual currency amount — resolves its Currency via a per-model resolve*Currency() method) yields a Brick\Money\Money; DecimalCast (a quantity/ratio that isn't itself a currency amount — a share count, an exchange rate) yields a Brick\Math\BigDecimal. Both implement SerializesCastableAttributes, so toArray()/toJson() — and therefore every /api/v1/* response — emit these fields as decimal strings, not JSON numbers; a new API consumer must parse them as such, not assume a JSON number (see UPGRADE.md's "API Response Precision" entry for the exact field list). Never call Money::formatTo() from backend code — it would introduce an undocumented ext-intl runtime dependency; display formatting is centralized in resources/js/shared/lib/i18n/format.js on the frontend instead. Any new non-Eloquent value object that stands in for a Transaction (e.g. App\Support\ScheduleInstance) must unwrap Money/BigDecimal the same way in its own toArray(), or it'll silently serialize to Money's own {"amount":...,"currency":...} JSON shape instead of matching the cast's decimal-string wire format. See .ai/docs/specifications/precision-improvements/ for the full rationale.
  • AI document retention irreversibly deletes user data — keep it opt-in, per-user, finalized-only, and path-safe. ai_user_settings.document_retention_days (NULL = keep forever; there is deliberately no env var or global default) drives App\Jobs\CleanupOldAiDocuments, dispatched per user by ai-documents:cleanup-old-files (daily 03:30 + the maintenance button). Only finalized documents are deleted; old documents in any other status only trigger a reminder email. Any code that deletes files from ai_document_files.file_path must go through CleanupOldAiDocuments::isOwnedPath() (inside ai_documents/{userId}/, no traversal, no symlink escape) — the manual delete action does not yet, don't copy it. transactions.ai_document_id is ON DELETE SET NULL: deleting a document must never delete its transaction. Drive file names are reduced to their last path segment before becoming part of file_path (ProcessGoogleDriveConfigJob::safeFileName()); keep any new import path doing the same, and still don't treat a stored file_path as trusted. See .ai/docs/features/ai-document-retention/.
  • All recurrence-pattern evaluation (budgets and transaction schedules) must go through RecurrenceRuleService, never a hand-built Recurr\Rule; recurrence shape is stored only in the rrule column. budgets/transaction_schedules persist one RFC 5545 RRULE string (rrule — #[Hidden], not fillable, never client-writable). frequency/interval/count/end_date/by_day/by_month/days_before_month_end/last_business_day_of_month are virtual attributes composed/decomposed by App\Models\Concerns\HasRecurrenceRule (effectiveRrule()); those DB columns no longer exist, so never where()/orderBy()/raw-SQL on them, and add any new RFC 5545 feature in HasRecurrenceRule + ValidatesRecurrenceRule, not as a new column or RecurrenceRuleService parameter. Occurrence methods take (Carbon $startDate, string $rrule, ...); buildRule() (public so Transaction::scheduleInstances() can reuse it) is the only runtime place a stored RRULE is parsed into a Recurr\Rule. Hand-built API payloads (getScheduledItems() budget rows, budgetChart() breakdowns) get no #[Appends] — list every virtual recurrence field in them explicitly. TransactionSchedule::isActive()/getNextInstance()/occursOn(), Budget, and Transaction::scheduleInstances() (forecast/budget-chart projection) all route through the service. See .ai/docs/features/budget-schedule-redesign/architecture.md.

Domain Documentation

Read .ai/docs/ before implementing a feature — it describes the domain model and product intent:

Path Contents
.ai/docs/product-context.md Philosophy, goals, non-goals
.ai/docs/assets/ Entity definitions (account, transaction, category, payee, investment, etc.)
.ai/docs/features/ As-built feature docs, extracted from code post-implementation (architecture, flows, permissions, tests, variables)
.ai/docs/specifications/ Pre-implementation design specs (background/rationale, spec, future directions)

Code is always the source of truth if docs and code conflict. Notify the user if you find discrepancies, and suggest doc updates.

Agent Role Files

Role-specific implementation guidelines live in .ai/agents/:

File Purpose
planning.agent.md Feature scoping and requirement structuring
laravel-backend.agent.md Laravel backend implementation rules
frontend.agent.md Vue/Blade frontend implementation rules
testing.agent.md Test design and coverage rules
documentation.agent.md Feature documentation extraction

Architecture Highlights

  • Services over controllers: business logic lives in app/Services/
  • Form Requests: all validation via dedicated app/Http/Requests/ classes
  • No SPA state: Blade pages are independent; Vue components are self-contained islands
  • New tests are written in Pest 5 (any level); existing PHPUnit tests are not converted without owner approval
  • Feature tests preferred; browser tests only for critical E2E flows, as Pest browser tests in tests/PEST/ (excluded from the default run: pest --testsuite=Browser). Dusk is legacy — no new Dusk tests
  • Build output (public/js/, public/css/) is Git-ignored — do not commit built assets

Directory Reference

app/Http/Controllers/   thin controllers
app/Services/           business logic
app/Models/             Eloquent models
app/Policies/           authorization
app/Jobs/               queue jobs
resources/views/        Blade templates
resources/js/           Vue components + JS
tests/Unit/             pure logic tests
tests/Feature/          HTTP/API tests
tests/Browser/          Dusk E2E tests
.ai/docs/               domain documentation
.ai/agents/             agent role instructions

Linting

Run linters before committing code to catch style and quality issues.

# PHP linting (PSR-12 code style)
./vendor/bin/pint              # Auto-fixes style issues

# PHP static analysis (PHPStan Level 5)
./vendor/bin/phpstan analyse   # Finds type errors and bugs

# JavaScript/Vue linting
npx eslint resources/js --ext .js,.vue

Note: Pint excludes vendor/, public/, storage/, bootstrap/ directories. PHPStan analyzes app/ directory only.