41 Commits

Author SHA1 Message Date
Gonçalo Rodrigues
5f60d963a0 feat(finance): transaction-backed goals + interactive waterfall
Goals are now funded entirely through tagged transactions — no more
manually-maintained saved_cents. Free cash waterfall (income → living →
goals → free cash) is the single source of truth for where money goes.

Core changes:
- Transaction.GoalID field links outflows to goals; SavedCents is derived
  via MongoDB aggregation (getGoalFundedCentsAll) instead of stored
- Waterfall on dashboard and goals page splits outflows into living vs
  goal-funded using GoalID presence
- ImpactOnDisposable fixed: uses income−living−monthlyCents instead of
  waterfallFreeCash−monthlyCents (was double-subtracting goal spend)
- avgMonthlySavings fixed: divides by positive-saving months only, and
  uses year+month key to avoid Dec cross-year collision

Interactive waterfall drill-down:
- Click Income / Living / Goals rows to expand category breakdown
- Click a category to reveal individual transactions inline
- All rendered server-side (instant, no extra API call)
- New WaterfallRow type + IncomeCats/LivingCats/IncomeCatTxns/LivingCatTxns
  on DashboardData

Goals page:
- Summary cards switched from heuristic disposable/committed to waterfall
- Each goal card shows funding history (last 5 tagged transactions)
- "Fund this goal" button links to /transactions?fund_goal=<id>

Transactions page:
- Add Transaction modal has goal picker dropdown
- submitAdd() includes goal_id in POST body
- Auto-opens modal pre-selected when arriving from goals page

Seed:
- seedGoalTransactions() back-fills tagged contributions for all 4 demo
  goals (Emergency fund, House down payment, Japan trip, MacBook Pro)
- Idempotent — skips if goal-tagged transactions already exist

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-19 22:18:47 +01:00
Gonçalo Rodrigues
ccbb60ace9 fix(finance): i18n — remove nav.settings scalar conflicting with [nav.settings] table
Same TOML duplicate-key pattern as nav.analysis: the scalar
settings = "..." in [nav] blocked parsing of the [nav.settings]
sub-table. Removed the scalar; nav dropdown labels now reference
the existing nav.drawer.*_label keys which hold the same strings.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-17 22:51:25 +01:00
Gonçalo Rodrigues
7aa510e1f5 fix(finance): i18n — fix TOML duplicate key and missing Lang on Translator
- Remove conflicting `analysis = "..."` scalar from [nav] in both
  en.toml and pt.toml; it shadowed the [nav.analysis] sub-table,
  causing the TOML parser to reject the entire file at startup
- Update nav analysis dropdown label to reuse nav.drawer.analysis_label
- Add lang field to Translator and expose T.Lang() method so base.html
  can highlight the active language in the switcher without requiring
  a Lang field on every page data struct

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-17 22:49:54 +01:00
Gonçalo Rodrigues
2166790fab feat(finance): i18n — auth pages and homepage fully translated
Wire T translator into auth login, auth register, and homepage
handlers; convert all hardcoded strings in those three templates
to T.Get keys (business features section, mock screen data,
sign-in block, footer, page title). Completes full i18n coverage
across all Finance Hub templates.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-17 22:42:27 +01:00
Gonçalo Rodrigues
4b7c01e632 feat(finance): i18n — TOML-based translations for all personal finance templates
Adds a full translation layer (English + European Portuguese) using
BurntSushi/toml with go:embed. Locale detection reads the lang cookie,
falls back to Accept-Language, then defaults to "en". A language switcher
in the nav writes the cookie and redirects back. All 20 personal finance
templates now use {{.T.Get "key"}} for every UI string.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-17 22:32:49 +01:00
Gonçalo Rodrigues
b4b7a1381c feat(dashboard): committed goals widget (#32)
* feat(dashboard): committed goals widget

Shows all committed goals on the dashboard with progress bars,
months remaining, saved vs target, and monthly required (green
when on track, red when not). Links to /goals for the full view.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(auth): enable TLS on ingress so Secure session cookie is honoured

BASE_URL was https:// but the ingress had no TLS block, causing the
browser to silently drop the Secure cookie after login. Adding tls: to
the Traefik ingress makes the site serve HTTPS via Traefik's default
cert so cookie and scheme match.

Also adds SeedExtras to seed goals and property/loan data independently
of the transaction-based idempotency guard in SeedAdmin.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Gonçalo Rodrigues <guga@Goncalos-MacBook-Pro.local>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-16 22:27:57 +01:00
Gonçalo Rodrigues
2ab3acdce2 feat(goals): Goal Planner — type-driven planner merged into /goals tab
* feat(property): Layer 3 — Dream House Simulator

Add /dream page with a four-phase simulation engine:

  Phase 1 — Save the down payment (uses current property equity)
  Phase 2 — Construction period (both loans running simultaneously)
  Phase 3 — Sell current house, apply proceeds to construction loan
  Phase 4 — Final state: just the construction loan remaining

Inputs: dream cost, down payment %, construction loan rate/term,
build duration, monthly savings, expected sale price. All pre-filled
from existing property/loan data when available.

Output: per-phase timeline cards, monthly cost bar chart, total
interest, final payoff date, and a key levers section.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor(plan): rename Dream House to Goal Planner at /plan

- Route /dream → /plan
- Nav label "Dream House" → "Goal Planner"
- Template dream.html → plan.html
- All user-facing labels generalised (construction loan → new loan,
  build duration → acquisition/build period, current property →
  current asset, dream house cost → new goal cost, etc.)
- Empty state updated with generic copy and 🎯 icon

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(goals): merge Goal Planner into /goals as a second tab

- /goals now has two tabs: "Committed goals" and "Goal Planner"
- Goal creation only happens from the Planner tab (simulate first,
  then "Save as goal" → creates an uncommitted goal)
- Commitment, deadline adjustment, and deletion stay on the Goals tab
- Off-track goals show an "Adjust deadline →" button that pushes the
  deadline to the realistic date based on current savings rate
- /plan and /dream both redirect to /goals?tab=planner (301)
- "Goal Planner" nav link removed; plan.html kept for redirect compat
- GoalsData gains Tab, PlanProperties, PlanLoans, HasPlanResult,
  PlanResult, PlanForm fields

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(goals): type-driven planner — Save for a purchase vs Sell & upgrade

Goal Planner tab now opens with two goal type cards:

  🛒 Save for a purchase — name, target, monthly savings, optional
     deadline. Shows time-to-reach at current rate, monthly needed
     to hit the deadline, and a feasibility banner.

  🔄 Sell & upgrade — the full four-phase transition simulator
     (existing asset + loan → acquire new → sell old → payoff).

Each type has its own focused form and result section. Selecting a
type highlights the card and loads the matching form. Results include
a "Save as goal" action that drops an uncommitted goal into the
Goals tab.

Also adds runPurchaseSim() and PurchaseSimResult model.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Gonçalo Rodrigues <guga@Goncalos-MacBook-Pro.local>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-16 22:02:41 +01:00
Gonçalo Rodrigues
ac073acad9 feat(finance): Layer 2 — property equity integrated into Net Worth (#30)
* feat(finance): Layer 2 — property equity flows into Net Worth

- NetWorthData gains PropertyValueCents, LoanBalanceCents, PropertyEquityCents
- NetWorth handler fetches properties + loans; adds equity to current snapshot
  and uses amortisation formula to compute historical loan balances per month,
  so the chart reflects how equity grew as loans were paid down
- Dashboard NetWorthCents now includes property equity
- loanBalanceAt() helper: B_n = P*(1+r)^n - (M/r)*((1+r)^n - 1)
- networth.html: inline breakdown row in hero card (cash / portfolio / equity),
  new "Property equity" breakdown card (value − loans), chart gains a dashed
  red "Loans outstanding" line when properties are present

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(property): resolve template, image pull, and build issues

- Fix parseTmpl missing base.html causing "base.html is undefined" error
- Change imagePullPolicy to IfNotPresent for local k3d dev workflow
- Add SERVICE_NAME to Makefile so make build-deploy uses correct image name

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Gonçalo Rodrigues <guga@Goncalos-MacBook-Pro.local>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-15 23:01:55 +01:00
Gonçalo Rodrigues
4305a77612 feat(finance): Layer 1 — Property & Loan foundation (#29)
Introduces properties and loans as first-class financial entities:

- models_property.go: Property, Loan, LoanView, PropertyView, PropertyData
- store_property.go: full CRUD for finance_properties + finance_loans collections
- handler_property.go: GET/POST /property with add/edit/delete for both entities;
  amortization helpers (EMI, remaining months, total interest)
- templates/property.html: summary equity cards, property cards with equity bar
  and linked loan details, standalone loan cards with payoff progress
- base.html: "Property" nav link added to desktop and mobile drawer
- storeIface + mockStore updated with 10 new property/loan methods

Co-authored-by: Gonçalo Rodrigues <guga@Goncalos-MacBook-Pro.local>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-15 22:40:57 +01:00
Gonçalo Rodrigues
fb6c839352 feat: public landing page + split personal/business nav (#26)
* feat: public landing page with auth-conditional state

Rewrites homepage.html as a full marketing landing page serving both
unauthenticated visitors (Sign In CTA) and authenticated users (Personal
+ Business portal links). Fixes handler to pass UserID so auth-conditional
rendering activates correctly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(k8s): expose / without auth so homepage is publicly reachable

Adds a second Ingress (api-public) for the exact path / with no
forward-auth middleware. Traefik prefers the Exact match for the root,
while the Prefix ingress (with auth) still protects all other routes.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: homepage renders correctly at / for unauthenticated visitors

Two fixes:
1. Added parseStandalone() helper — parseTmpl() roots on "" but ParseFS()
   stores standalone (no {{define}}) files under their base filename, so
   Execute() ran the empty root and returned Content-Length: 0.
2. Added router.priority: 100 annotation to api-public ingress so Traefik
   picks the Exact / rule over the Prefix / rule (Traefik ranks by rule
   string length by default, which made PathPrefix beat Path).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: self-contained auth — email/password + Google OAuth, HMAC session cookies

Embeds a full authentication system into the finance API so it can be
deployed as a standalone container without any external auth dependency.

- Email/password registration and login with bcrypt hashing
- Google OAuth 2.0 (GOOGLE_CLIENT_ID / GOOGLE_CLIENT_SECRET env vars)
- HMAC-SHA256 signed session cookies (SESSION_SECRET env var, 30-day TTL)
- Sessions stored in MongoDB finance_sessions with TTL index auto-expiry
- Users stored in MongoDB finance_users with unique email index
- /auth/login, /auth/register, /auth/logout, /auth/oauth/google routes
- authMW now redirects to /auth/login?next=... instead of auth.homelab.local
- getAuth() resolves session cookie first, falls back to X-Auth-* headers
- Default categories seeded automatically on new account creation
- seed.go checks finance_users before the shared legacy users collection

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: homepage sign-in links point to /auth/login instead of auth.homelab.local

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(k8s): remove forward-auth middleware from finance ingress

The app now handles its own auth at /auth/login — Traefik no longer
needs to forward-auth requests, which was causing redirects to
auth.homelab.local instead of finance.homelab.local.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Gonçalo Rodrigues <guga@Goncalos-MacBook-Pro.local>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-15 18:18:09 +01:00
Gonçalo Rodrigues
541a1c3556 feat: animated homepage + split personal/business navigation (#25)
- New animated homepage at / with 3D card tilt, particle canvas,
  floating ring decorations, and gradient title shimmer
- Personal finance pages move to /dashboard (base.html shows only
  personal nav + a subtle Business link)
- Business/org inner pages use base_org.html with a purple theme,
  org breadcrumb, Year/Team dropdowns, and a ← Hub back link
- org home/teams/members/invite/events/requests/ledger/analysis/report
  all switched to renderOrg() + base_org.html
- Route strings updated to org-home, org-teams, org-events, etc.
  so active nav highlighting works correctly in the business shell

Co-authored-by: Gonçalo Rodrigues <guga@Goncalos-MacBook-Pro.local>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-14 17:02:37 +01:00
Gonçalo Rodrigues
1fce3b36aa feat: add checkable goals list to org events (#24)
Goals are stored as EventGoal items embedded in the event document.
During active fiscal years, members can check/uncheck goals inline.
Goals can be added and deleted while the event is not yet approved.

Co-authored-by: Gonçalo Rodrigues <guga@Goncalos-MacBook-Pro.local>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-14 16:15:55 +01:00
Gonçalo Rodrigues
26a7236494 style: global dark form inputs + team emoji avatars (#23)
Form inputs:
- Add a catch-all CSS rule targeting all text-type inputs, selects, and
  textareas not already covered by .form-group or .form-input — sets
  background:--bg2, color:--text, focus ring. Fixes white-box appearance
  in transactions, goals, settings, portfolio, import, people, and tax
  templates without touching any HTML.

Team avatars:
- OrgTeam.Avatar string (emoji) — persisted in MongoDB, defaults to 👥
- OrgTeamCreate handler reads "avatar" form field
- org_teams.html: emoji picker (30 options) in new-team modal; preview
  updates live; selected emoji highlighted with accent border
- avatarEmojis() and teamAvatar() template functions registered in FuncMap
- Team badges in org_members, org_events, org_event_detail, org_report
  all show the emoji inline with the team name

Co-authored-by: Gonçalo Rodrigues <guga@Goncalos-MacBook-Pro.local>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-14 16:08:29 +01:00
Gonçalo Rodrigues
ceeee2a46a style: add .form-input/.form-label CSS classes for dark-theme form fields (#22)
Org templates use class="form-input" and class="form-label" which had no
CSS definition — browsers fell back to white-box defaults, clashing with
the dark teal theme.

- .form-input: dark bg (--bg2), teal border, accent focus ring + glow,
  placeholder dimming, file input button styling
- .form-label: uppercase micro-label style (11px, --text3) matching the
  existing card section headers throughout the app
- textarea.form-input: resize:vertical, min-height, line-height
- select option / .form-input option: dark background on dropdown items
- Keep .form-group label unchanged so existing non-org templates are unaffected

Co-authored-by: Gonçalo Rodrigues <guga@Goncalos-MacBook-Pro.local>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-14 16:03:45 +01:00
Gonçalo Rodrigues
07e3525dae fix(finance): org polish — broken links, upload handler, populated selects (#21)
- org_home.html: fix Requests/Ledger links (were pointing to non-existent
  /years/{id}/requests routes; corrected to /orgs/{slug}/requests and
  /orgs/{slug}/ledger). Add Analysis link. Add Close year button for admins.
- OrgRequestNew: pass events + teams to GET template so dropdowns are
  populated (were silently discarded with _ = events).
- OrgRequestDetailData: add NewEvents/NewTeams fields for new-request form.
- OrgRequestUpload: implement file upload handler — saves to
  /data/org-files/{org_id}/{req_id}/{id} and records metadata in MongoDB.
  Register POST /orgs/{slug}/requests/{req_id}/upload route.
- org_request_detail.html: show upload form in attachments section;
  populate event/team selects on new request form.

Co-authored-by: Gonçalo Rodrigues <guga@Goncalos-MacBook-Pro.local>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-14 16:00:23 +01:00
Gonçalo Rodrigues
0f58a51c6d feat(finance): org Phases 2-5 — events, requests, ledger, analysis, report (#20)
Phase 2 — Event planning:
- Create/edit/delete/submit events per fiscal year
- Budget lines (income + expense) with planned amounts
- Admin review flow: approve / reject / comment
- Post-mortem feedback comments on closed years

Phase 3 — Transaction requests:
- 5 types: reimbursement, purchase_order, cash_advance, income, budget_transfer
- Full status machine with append-only StatusLog audit trail
- PO delivery sub-form (actual vendor, amount, invoice note)
- Cash advance settlement sub-form (spent + returned)
- Ledger view per fiscal year with income/expense summary
- Bank CSV import: parse → preview → confirm → ledger entries

Phase 4 — Plan vs actual analysis:
- Variance table by event (planned vs actual income/expense)
- Variance table by team

Phase 5 — Year-end report:
- Executive summary with net variance
- Per-event section: budget lines + team feedback comments
- Feedback form available on closed fiscal years

Supporting changes:
- New store methods: getLedgerEntries, createLedgerEntry, updateLedgerEntry,
  getAttachments, createAttachment
- New template funcs: dateInput, statusColor, varColor
- parseEuroAmount + parseBankCSV helpers in handler_org.go

Co-authored-by: Gonçalo Rodrigues <guga@Goncalos-MacBook-Pro.local>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-14 12:58:47 +01:00
Gonçalo Rodrigues
6ed848a001 feat(finance): org management scaffold — Phase 1
Adds multi-tenant organisation support inside the existing finance namespace.
Users can create organisations, invite others via a copy-paste token link,
and manage teams/members with RBAC (admin, finance, member, viewer).
Fiscal year lifecycle is gated: activation requires all planned events to
be approved first. All org data lives in `org_`-prefixed MongoDB collections.

New files:
- models_org.go      — domain types (Org, OrgTeam, OrgMember, OrgInvite,
                       FiscalYear, OrgEvent, BudgetLine, EventComment,
                       TxRequest with full StatusLog audit trail, etc.)
- store_org.go       — MongoDB store methods for all org collections
- handler_org.go     — HTTP handlers + RegisterOrgRoutes(); join invite
                       route lives at /join/{token} to avoid ServeMux
                       conflict with /orgs/{slug}/... wildcard routes
- templates/org_*.html — list, create, home, teams, members, invite, join

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-14 12:43:48 +01:00
Gonçalo Rodrigues
a66941160a fix: portfolio value formula, Yahoo UA header, mobile nav hamburger
- Remove erroneous /100 in currentValue calculation (portfolio values
  were 100x too small, causing net worth card to show ~€0)
- Add User-Agent header to Yahoo Finance requests (avoids 429s)
- Wrap ES module chart body in if(total>0){} block (return at top
  level of a module is a SyntaxError — chart was silently broken)
- Add mobile hamburger menu: full-screen drawer at ≤720px with
  animated open/close, all nav links, scroll lock while open

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-13 19:33:12 +01:00
Gonçalo Rodrigues
09984272cd style: dark teal/cyan theme across the whole app
Replace indigo accent palette with deep black backgrounds and
cyan-teal accents (#00c9b8 dark, #00897b light). Borders, glows,
text muted tones and background gradients all updated to match.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-13 19:12:58 +01:00
Gonçalo Rodrigues
5c6752b4a3 style: blue-green palette for portfolio allocation chart
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-13 19:09:02 +01:00
Gonçalo Rodrigues
6712c36081 feat: user-editable ISIN→ticker mappings for unrecognised holdings
When a holding has no price (ISIN not in the built-in map and Yahoo
rejects the raw ISIN), the portfolio page now shows an amber banner
listing each missing ISIN with an inline text input and a "Look up"
link to Yahoo Finance symbol search.

Submitting the form POSTs to /portfolio/ticker which upserts the mapping
into a finance_ticker_mappings collection keyed by (user_id, ISIN).
On the next page load custom mappings are resolved first, before the
hardcoded isinToTicker table, so user overrides always win.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-13 19:05:36 +01:00
Gonçalo Rodrigues
a2d3b60500 fix: allocation chart — illegal top-level return in ES module
`return` at the top level of a <script type="module"> is a SyntaxError
in strict mode, so the entire Three.js chart script was killed before
executing regardless of whether prices were available.

Replaced `if (total <= 0) return` with `if (total > 0) { ... }` wrapping
the full chart body. Also filter out holdings with value=0 before building
the arc geometry so a single un-priced ISIN can't produce a zero-arc slice.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-13 18:55:38 +01:00
Gonçalo Rodrigues
437fb5a2df refactor: consolidate nav from 15 items to 7
- Merge Sharing + Household → /people (tab switcher: sharing | household)
- Merge Accounts + Categories → /settings (tab switcher: accounts | categories)
- Add Analysis dropdown in nav: Reports, Projections, Tax, Net Worth, What If
- Add Settings dropdown in nav: Accounts & Categories, Import CSV, Import Guide
- Legacy GET /sharing, /household, /accounts, /categories redirect 301 to new URLs
- Remove Import and Import Guide as standalone nav links
- New People handler consolidates all people-related mutations (_action field)
- New Settings handler renders both account and category lists in one page

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-13 18:33:15 +01:00
Gonçalo Rodrigues
7f7e696b4d refactor: retire Auto Import schedules, add Import Guide + duplicate notice
- Auto Import page replaced with a static Import Guide: step-by-step
  instructions for CGD/Trade Republic/generic CSV export, duplicate
  detection explanation, and a curl example for headless automation
- Removed POST /auto-import and DELETE /auto-import/{id} routes and
  their handler logic; schedule CRUD was misleading since no bank
  exposes an unauthenticated CSV endpoint
- Nav label changed from "Auto Import" to "Import Guide"
- Transactions page now renders an amber banner when redirected with
  ?notice=all_duplicates (every row in the uploaded file was skipped)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-13 18:20:48 +01:00
Gonçalo Rodrigues
2170457528 feat: duplicate detection on CSV import
Compute a sha256 fingerprint (date|description|amount|account_id, first
16 hex chars) for every CSV row and store it as bank_ref. At preview
time, existing fingerprints are fetched and matching rows are shown
greyed out with a "duplicate" label. At confirm time, those rows are
silently skipped — only truly new transactions are inserted.

If every row is a duplicate the user is redirected with ?notice=all_duplicates
instead of inserting an empty batch.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-13 18:15:23 +01:00
Gonçalo Rodrigues
1c2bac1d5f feat: implement Tax Summary, Household Mode, and Auto Import
- Tax Summary (/tax): annual gross income from Income transactions,
  FIFO capital gains/losses from trades, expenses by category, CSV export
- Household Mode (/household): link a partner account, combined
  income/expense/disposable view for current month, shared goals list
- Auto Import (/auto-import): schedule recurring CSV imports with
  account, format and optional source URL; delete schedules; webhook docs
- New store methods for households and import_schedules collections
- storeIface extended; mockStore updated; all tests still pass

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-13 17:28:22 +01:00
Gonçalo Rodrigues
995c6d89d6 feat: phase 6 — dashboard alerts and nudges
Adds server-computed alert banners to the dashboard:
- Budget exceeded (red): when a category spend is over 100% of budget
- Budget pace warning (amber): 80%+ of budget used but <80% of month elapsed
- Goal deadline risk (amber): avg savings rate too low to hit a goal on time
- Spend pace warning (amber): disposable spending is 20%+ ahead of month progress

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-13 17:03:12 +01:00
Gonçalo Rodrigues
39282ff550 feat: phase 5 — what-if simulator + savings rate history
Adds /simulator page with live sliders for income change, one-off
expenses, and fixed cost shifts. Goal timelines update in real time
showing months gained/lost. Includes a savings rate bar chart from
historical monthly data.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-13 16:57:14 +01:00
Gonçalo Rodrigues
42f5b0df4d feat: phase 4 — net worth page + dashboard card
Adds /networth page with hero total, cash/portfolio breakdown cards,
and a month-by-month historical chart. Also shows net worth as a
value card on the dashboard with a link to the full breakdown.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-13 16:52:57 +01:00
Gonçalo Rodrigues
bfd5f62a7a feat: show committed goals in fixed costs panel
Committed goals now appear in the Fixed Costs panel on the dashboard
with a "committed goal" label, and the total line uses TotalCommittedCents
(fixed costs + goal contributions) instead of total monthly expenses.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-13 16:46:55 +01:00
Gonçalo Rodrigues
2324f62721 feat: add fixed costs panel to dashboard
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-13 16:41:48 +01:00
Gonçalo Rodrigues
3b041267ad feat: phase 3 — goals commit + plan
- Commit/uncommit button on each goal card
- Committed goals are deducted from disposable income on the dashboard
  (Available to spend now reflects reserved goal contributions)
- Conflict detection: warning banner when committed goals exceed
  disposable income, showing the shortfall
- Goals summary bar: disposable before goals, reserved per month,
  free to spend after committed goals

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-13 16:37:45 +01:00
Gonçalo Rodrigues
99be71be8a fix: replace month input with month/year dropdowns in goal modal
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-13 16:28:58 +01:00
Gonçalo Rodrigues
be0c2bd89e feat: phase 2 — goals explore mode
Adds a Goals page where users can plan financial goals before committing
to them. Each goal shows:

- Required monthly contribution to hit the deadline
- Months remaining vs months at current savings rate
- Disposable income impact (what's left after the contribution)
- Feasibility banner (green if on track, red with month delta if not)
- Progress bar once savings are tracked

Goal types: one-off purchase, deposit/down-payment, emergency fund,
recurring investment — each with a description hint in the creation modal.

Data: Goal model + store CRUD in finance_goals collection.
Nav: Goals tab added between Portfolio and Sharing.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-13 16:20:46 +01:00
Gonçalo Rodrigues
5412dda2ac fix: dashboard investment display and budget health
- Exclude FixedCategories (Housing, Utilities, Subscriptions, Investments)
  from budget health panel — they are committed costs, not variable spend
- Portfolio card and stocks panel now degrade gracefully to cost basis
  when Yahoo Finance prices are unavailable, instead of showing €0
- Stocks panel shows "cost basis" label per holding when live prices
  are not available

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-13 16:14:44 +01:00
Gonçalo Rodrigues
1d3aa764cb feat: phase 1 dashboard redesign — disposable income + smart panels
Replaces the old KPI/chart dashboard with a focused layout that
answers the three key questions immediately:

- Hero block: "Available to spend" = income − fixed costs − spent so far,
  with a progress bar showing % of disposable used vs month elapsed
- Bank math panel: detects recurring fixed expenses (Housing, Utilities,
  Subscriptions, Investments) from last 3 months and shows the minimum
  bank balance needed right now including a 2-week safety buffer
- Savings rate card with month-over-month delta
- Portfolio snapshot card with total value and P&L
- Stocks at a glance panel: per-holding value and P&L inline
- Budget health: thin bars per category, red when over limit
- Recent activity: last 5 transactions with category color dots

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-13 15:51:46 +01:00
Gonçalo Rodrigues
2b98599bcf fix(finance): portfolio template float64 comparison error
ge/lt in Go templates cannot mix float64 and untyped int literals.
TotalPCLPct is float64, so replaced `ge $d.TotalPCLPct 0` with
`eq (pctSign $d.TotalPCLPct) "+"` which uses the existing pctSign
helper that already handles the sign check correctly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-13 12:50:31 +01:00
Gonçalo Rodrigues
c255d7f523 fix(finance): portfolio prices and projections template
Portfolio:
- fetchPrices was passing ISINs directly to Yahoo Finance, which only
  accepts ticker symbols (e.g. VWCE.DE, not IE00B3RBWM25). Added a
  hardcoded isinToTicker map covering common European ETFs (Vanguard,
  iShares, Xtrackers, Amundi). fetchPricesByISIN now resolves each ISIN
  to its ticker before calling Yahoo and keys the result by ISIN so
  computeHoldings can look up prices correctly. Renamed holdingsByISIN
  to uniqueISINs to reflect what it actually returns.

Projections:
- Template had a missing <tr> and a broken pace-bar width expression
  that mixed float64/int64 in the div template function, causing a
  template execution error that rendered the page blank. Moved all
  math server-side: the handler now pre-computes []catProjection with
  MonthlyAvg, AnnualTotal, and PacePct already calculated. Template
  is now simple range + printf. Chart switched to horizontal bar for
  better readability of long category names.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-13 12:47:03 +01:00
Gonçalo Rodrigues
7a2cb10c79 feat(finance): dark mode UI overhaul + admin seed data
UI — full dark/light theme system:
- CSS custom-property token system (--bg, --surface, --accent, --green, --red, etc.)
  with complete light-mode overrides via [data-theme="light"]
- Sticky frosted-glass nav with animated brand icon, theme toggle persisted to localStorage,
  respects prefers-color-scheme on first visit
- Cards with layered shadows, glass backdrop-filter, shimmer accent stripe on value cards
- Glowing category color dots, colored P&L badges, budget bars with glow effect
- All Chart.js instances use CSS-variable-aware grid/text colours
- Scroll-reveal animations and animated money counters on every KPI card
- 3-D donut portfolio chart recoloured to match palette; hover lifts the hovered slice
- Accounts page shows type emoji icons; delete removes row in-place
- Sharing page search dropdown themed with var() colours
- Import preview: colour-coded left border on category select driven by category colour
- Projections: second KPI card (monthly avg) + pace bars per category

Seed data (seed.go):
- SeedAdmin() runs in a goroutine at startup; idempotent (skips if transactions exist)
- Resolves admin user ID via internal users service GET /admin/users?search=<email>
  (SEED_USER_EMAIL env var, defaults to admin@homelab.local)
- Seeds 4 accounts (CGD Checking, CGD Savings, Visa Credit, Trade Republic)
- Seeds 14 categories with colours and monthly budgets
- Seeds ~65 realistic Portuguese household transactions spread across 6 months
- Seeds 7 ETF buy trades across VWCE, SXR8 (S&P 500), EUNL (MSCI World)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-13 12:16:23 +01:00
Gonçalo Rodrigues
452f97e6d9 feat(finance): improve UX across dashboard, transactions, reports and categories
- Fix sortStrings no-op bug that caused balance trend chart to display in random date order
- Fix reports table referencing wrong scope for row totals ($.Totals → .Totals per row)
- Add income vs expense split cards on dashboard alongside net figure
- Add budget progress bars on dashboard using existing category budget_cents data
- Color-coded category badges throughout using each category's configured color
- Replace window.prompt() category editor in transactions with inline dropdown
- Replace window.prompt() budget editor in categories with inline input field
- Add manual transaction entry modal (POST /api/transactions) so users don't need CSV for every entry
- Show account names instead of raw MongoDB IDs in the transaction list
- Add clampPct and isOver template helpers for budget bar rendering

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-13 11:33:39 +01:00
Gonçalo Rodrigues
13b7149614 First Commit 2026-06-13 11:25:23 +01:00