Commit graph

378 commits

Author SHA1 Message Date
le king fu
a9d1301dd2 Merge PR #184: feat(balance) 2-step onboarding card on empty /balance (#178) 2026-05-02 15:32:01 -04:00
le king fu
e342a1f567 Merge PR #183: fix(balance) atomic snapshot save + cleanup migration v11 (#176) 2026-05-02 15:32:01 -04:00
le king fu
3260ea8c47 Merge PR #182: fix(balance) SQL aggregate misuse in getAccountsPeriodAnchor (#175) 2026-05-02 15:31:53 -04:00
le king fu
eac2a516b5 feat(balance): 2-step onboarding card on /balance empty state
Replace empty BalanceOverviewCard with BalanceOnboardingCard showing
two steps:
1. Create an account
2. Enter a snapshot

Step 2 is grayed out until at least one account exists; the entire
card is replaced by BalanceOverviewCard once a snapshot is recorded.
Hide "+ New snapshot" button when 0 accounts (it lives inside the
overview card, which is now hidden in that state).

Improve SnapshotEditPage noAccounts copy to clarify account vs
snapshot semantics.

Resolves #178
2026-05-02 11:48:57 -04:00
le king fu
50b119121f fix(balance): atomic snapshot save with BEGIN/COMMIT + cleanup migration
useSnapshotEditor.save now validates all simple/priced lines in-memory
before any DB write, then delegates to a new saveSnapshotAtomic helper
that wraps INSERT snapshot + INSERT lines in an explicit BEGIN/COMMIT
transaction (ROLLBACK on catch). Pattern matches categorizationService.

Migration v11 cleans existing orphan snapshots in profiles that hit the
old race; new orphans are no longer possible thanks to the transaction.

Resolves #176
2026-05-01 07:33:44 -04:00
le king fu
44cc77d8f6 fix(balance): use ROW_NUMBER window function in getAccountsPeriodAnchor
All checks were successful
PR Check / rust (pull_request) Successful in 22m48s
PR Check / frontend (pull_request) Successful in 2m22s
SQLite raised "misuse of aggregate function MIN()" because MIN was used
in the WHERE clause of a scalar subquery. Replace with ROW_NUMBER()
OVER (PARTITION BY account_id ORDER BY snapshot_date ASC) filtered on
rn = 1.

Adds vitest coverage and a regression test for /balance load.

Resolves #175
2026-05-01 07:18:53 -04:00
le king fu
bde47dabed chore(gitignore): ignore reports/ and spec-* scratch
reports/ is used by /autopilot for daily reports and per-worker decision
logs (scratch). spec-decisions-*.md and spec-plan-*.md are produced by
/plan-overnight and committed only when promoted to docs/archive/.

Prevents `git add -A` in workers from sweeping these into PRs.
2026-05-01 07:12:24 -04:00
le king fu
5836760f3c chore: release v0.9.0
All checks were successful
Release / build-and-release (push) Successful in 25m25s
2026-04-29 19:20:03 -04:00
67c48029a0 Merge pull request 'feat(prices): commit smoke test scaffold for /v1/prices (Phase A)' (#173) from issue-161-smoke-scaffold into main
feat(prices): commit smoke test scaffold for /v1/prices (Phase A) (#173)

Refs #161
2026-04-29 10:25:36 +00:00
le king fu
e0844f0f34 feat(prices): commit smoke test scaffold for /v1/prices
All checks were successful
PR Check / rust (pull_request) Successful in 22m30s
PR Check / frontend (pull_request) Successful in 2m24s
Phase A of #161: ships the smoke script and README before the
maximus-api prices-proxy endpoint is live in prod. The script will
fail on case 1 (HTTP 404) until /v1/prices is implemented and
deployed — that is expected; running it is gated to Phase B (after
prices-proxy ships, before cutting v0.9.0).

Script covers 4 cases:
  1. Stock happy path (AAPL) — HTTP 200, .price > 0
  2. Crypto happy path (BTC) — HTTP 200, .price > 0
  3. Invalid symbol — HTTP 404, error.code=symbol_not_found
  4. Missing auth — HTTP 401, error.code=missing_token

`set -euo pipefail`, exits non-zero on first failure. Reads token
from MAXIMUS_API_TEST_TOKEN env var (never committed). README
documents env vars and the two paths for obtaining a premium test
token (admin endpoint TODO in maximus-api, manual JWT signing as
current workaround).

CSP whitelist for https://api.lacompagniemaximus.com is already in
place in src-tauri/tauri.conf.json — verified, no change needed.

No application code touched; npm test (492) and cargo test --lib
(69) remain green.

Phase A only (#161)
2026-04-28 21:31:27 -04:00
f16f340c22 Merge pull request 'chore(ci): drop redundant push trigger; add concurrency group' (#172) from issue-171-ci-drop-push-trigger into main
chore(ci): drop redundant push trigger; add concurrency group (#172)

Fixes #171
2026-04-29 01:17:30 +00:00
le king fu
af36b51cf7 chore(ci): drop redundant push trigger; add concurrency group
All checks were successful
PR Check / rust (pull_request) Successful in 22m29s
PR Check / frontend (pull_request) Successful in 2m26s
The `push` + `pull_request` combo doubled CI runs on every PR (visible
on #170 with 4 pending checks instead of 2). Drop `push`, keep
`pull_request: branches: [main]`.

Trade-off: branches pushed without an open PR no longer get CI
feedback. Open a draft PR if you want CI to run before requesting
review — `/fix-issue` always opens a PR right after pushing, so the
gap is essentially zero in practice.

Also adds a concurrency group `ci-${{ github.ref }}` with
cancel-in-progress so force-pushes cancel the previous run instead
of stacking.

Same change applied to .github/workflows/check.yml (GitHub mirror)
to keep the two configs in sync.

Fixes #171
2026-04-28 20:50:34 -04:00
3342fd9bb7 Merge pull request 'feat(balance): add asset_type column to balance_categories' (#170) from issue-169-asset-type-balance-categories into main
feat(balance): add asset_type column to balance_categories (#170)

Fixes #169
2026-04-29 00:47:01 +00:00
le king fu
3963f552ae feat(balance): add asset_type column to balance_categories
All checks were successful
PR Check / rust (push) Successful in 23m42s
PR Check / frontend (push) Successful in 2m26s
PR Check / rust (pull_request) Successful in 22m55s
PR Check / frontend (pull_request) Successful in 2m24s
Priced balance categories now carry an explicit `asset_type`
('stock' | 'crypto') so PriceFetchControl can route to the right
provider without symbol heuristics. ETH = Ethan Allen NYSE AND
Ethereum crypto are no longer ambiguous.

Migration v10 adds a nullable column and backfills the two seeded
priced categories (key='stock','crypto'). Legacy custom priced rows
stay NULL until the user edits the category — SnapshotLineRow hides
the price-fetch button when asset_type is NULL on a priced row, so
manual entry remains available.

Service-side validation rejects priced creation without asset_type
('asset_type_required') and rejects values outside ('stock','crypto')
('asset_type_invalid'). Simple kind coerces asset_type to NULL.

The CategoryVariant of AccountForm shows the selector only when
kind=priced, requires it on submit, and resets it on kind switch.
i18n keys added under balance.category.assetType.* (FR + EN).

Tests:
- 4 new Rust migration tests in lib.rs (column add, seed backfill,
  legacy row stays NULL, CHECK rejects 'gold')
- 6 new vitest cases on createBalanceCategory + listBalanceAccounts
  asserts c.asset_type AS category_asset_type in the join
- balance-flow integration test updated to pass asset_type='stock'

No new test for SnapshotLineRow render guard — project lacks
@testing-library/react + jsdom; the guard is one boolean expression
covered by manual QA per autopilot decisions in PR #167.

Fixes #169
2026-04-28 19:54:04 -04:00
877aff8d6d Merge pull request 'feat(prices): Settings revocation toggle for price_fetching_consent (#159)' (#168) from issue-159-settings-revoke-toggle into main 2026-04-28 01:36:14 +00:00
le king fu
a6097afcf3 chore: drop decisions-log.md (autopilot scratch, conflicts with main cleanup)
All checks were successful
PR Check / rust (push) Successful in 23m34s
PR Check / frontend (push) Successful in 2m30s
PR Check / rust (pull_request) Successful in 23m22s
PR Check / frontend (pull_request) Successful in 2m27s
2026-04-27 21:35:57 -04:00
d140ed938a Merge pull request 'feat(prices): PriceFetchControl + consent modal + best-effort UX (#158)' (#167) from issue-158-pricefetchcontrol into main 2026-04-28 01:35:23 +00:00
le king fu
da4eef2bdd chore: drop decisions-log.md (autopilot scratch, conflicts with main cleanup)
All checks were successful
PR Check / rust (push) Successful in 23m37s
PR Check / frontend (push) Successful in 2m36s
PR Check / rust (pull_request) Successful in 23m18s
PR Check / frontend (pull_request) Successful in 2m27s
2026-04-27 21:35:04 -04:00
le king fu
88c3c04dea chore: untrack decisions-log.md (autopilot scratch from #166 merge) 2026-04-27 21:32:59 -04:00
97f91f87aa Merge pull request 'feat(prices): balance.service prices section + rate-limit + tests (#156)' (#166) from issue-156-balance-service-prices into main 2026-04-28 01:32:41 +00:00
le king fu
55c610c1f2 chore: untrack decisions-log.md (autopilot scratch file)
The autopilot worker prompt instructed writing decisions to
decisions-log.md at worktree root, but didn't exclude it from git
add — so each worker committed its version, causing add/add merge
conflicts between sibling PRs in the prices milestone.

Add to .gitignore so future autopilot runs leave the scratch file
local only.
2026-04-27 21:32:28 -04:00
edd1a5cbe4 Merge pull request 'feat(prices): Rust Tauri command fetch_price + tests (#155)' (#165) from issue-155-rust-fetch-price into main 2026-04-28 01:06:40 +00:00
01cfbdba8b Merge pull request 'feat(prices): useIsPremium hook (#157)' (#164) from issue-157-use-is-premium into main 2026-04-28 01:06:35 +00:00
3b2384af25 Merge pull request 'feat(prices): i18n FR/EN keys + CHANGELOG entries (#160)' (#163) from issue-160-i18n-changelog into main 2026-04-28 01:06:30 +00:00
0511d2ef06 Merge pull request 'feat(prices): commit /v1/prices contract + ADR 0011 (#154)' (#162) from issue-154-contract-and-adr into main 2026-04-28 01:04:48 +00:00
le king fu
80c28d43ac feat(prices): Settings revocation toggle for price_fetching_consent
All checks were successful
PR Check / rust (push) Successful in 28m16s
PR Check / frontend (push) Successful in 3m0s
PR Check / rust (pull_request) Successful in 29m45s
PR Check / frontend (pull_request) Successful in 3m6s
- Adds PriceFetchConsentToggle to SettingsPage Privacy section
- Reads/writes user_preferences.price_fetching_consent for active profile
- Confirmation dialog before revoke (DELETE the key entirely so next click re-opens consent modal)
- Disabled (with notPremium tooltip) when license is not premium
- Adds deletePreference() to userPreferenceService
- Adds settings.privacy.title i18n key (FR + EN)
- 10 vitest tests covering all paths

Closes #159
2026-04-27 08:41:15 -04:00
le king fu
8fa34d786d merge: bring in #158 (transitively #156/#157/#160) 2026-04-27 08:38:16 -04:00
le king fu
043e9bf622 feat(prices): PriceFetchControl component + consent modal + best-effort UX
All checks were successful
PR Check / rust (push) Successful in 30m45s
PR Check / frontend (push) Successful in 3m12s
PR Check / rust (pull_request) Successful in 28m50s
PR Check / frontend (pull_request) Successful in 3m15s
- New component renders button + consent modal + spinner + attribution
- Best-effort warning shown once per session for stock categories
- Hidden if not premium or category kind != 'priced'
- Consent persisted per-profile in user_preferences.price_fetching_consent
- Manual unit_price input remains active in all paths
- 17 vitest tests (no RTL/jsdom — logged MEDIUM in decisions-log.md)
- Wired into SnapshotLineRow/SnapshotEditor/SnapshotEditPage
- asset_type hardcoded to 'stock' pending category schema extension (MEDIUM)

Closes #158
2026-04-27 08:36:23 -04:00
le king fu
c90badae39 merge: bring in balance.service prices namespace from #156 2026-04-27 08:30:53 -04:00
le king fu
99814b9a0d merge: bring in useIsPremium hook from #157 2026-04-27 08:30:50 -04:00
le king fu
b1dc76b487 merge: bring in i18n keys from #160 2026-04-27 08:30:48 -04:00
le king fu
920f81fce5 feat(prices): balance.service prices section with rate-limit + dedup + retries
All checks were successful
PR Check / rust (push) Successful in 27m27s
PR Check / frontend (push) Successful in 2m49s
PR Check / rust (pull_request) Successful in 28m58s
PR Check / frontend (pull_request) Successful in 2m57s
- prices.fetchPrice wraps invoke('fetch_price', ...) with local rate-limit (1/2s), in-flight dedup, exp backoff on 5xx (2/4/8s, max 3 retries), no retry on 4xx/429, hard 100/session cap
- 9 vitest tests with vi.useFakeTimers() (happy, 401/403/404, 429 no-retry, 5xx retries, dedup, pacing, session cap)
- Annexe B i18n mapping wired (PriceError → balance.priceFetching.errors.* keys)
- Session cap checked before rate-limit/dedup; failures do not consume budget (MEDIUM decision)

Closes #156

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-27 08:28:24 -04:00
le king fu
531624bcb4 feat(prices): Rust Tauri command fetch_price + tests
All checks were successful
PR Check / rust (push) Successful in 25m28s
PR Check / frontend (push) Successful in 2m33s
PR Check / rust (pull_request) Successful in 25m38s
PR Check / frontend (pull_request) Successful in 2m44s
- Add fetch_price command with PriceResponse and FetchPriceError types
- Privacy-strict header policy (Authorization, Accept, User-Agent only)
- Rename SIMPL_API_URL -> MAXIMUS_API_URL across src-tauri
- 7+ mockito tests covering happy path, 401/403/404/429/5xx, and header allowlist
- Fix pre-existing clippy warnings (doc_overindented_list_items, is_multiple_of)

Closes #155
2026-04-27 08:23:18 -04:00
le king fu
98f68f7a1f feat(prices): useIsPremium hook from license.edition
All checks were successful
PR Check / rust (push) Successful in 26m18s
PR Check / frontend (push) Successful in 2m37s
PR Check / rust (pull_request) Successful in 25m0s
PR Check / frontend (pull_request) Successful in 2m41s
- Reads useLicense().state.edition === 'premium'
- Ergonomic only — server enforces independently (ADR 0011)
- 3 vitest tests (premium, base, free)
- CLAUDE.md hook count 12 -> 13

Closes #157
2026-04-27 08:11:23 -04:00
le king fu
ab7e0a3362 feat(prices): i18n FR/EN keys + CHANGELOG entries
All checks were successful
PR Check / rust (push) Successful in 26m25s
PR Check / frontend (push) Successful in 2m34s
PR Check / rust (pull_request) Successful in 26m20s
PR Check / frontend (pull_request) Successful in 2m54s
Closes #160
2026-04-27 08:06:54 -04:00
le king fu
ddb0cb257b docs(prices): commit /v1/prices contract + ADR 0011
All checks were successful
PR Check / rust (push) Successful in 25m39s
PR Check / frontend (push) Successful in 2m42s
PR Check / rust (pull_request) Successful in 26m45s
PR Check / frontend (pull_request) Successful in 2m58s
- Add frozen v2 /v1/prices API contract (docs/api-contract-prices.md)
- Add ADR 0011: providers best-effort Yahoo (docs/adr/0011-providers-best-effort-yahoo.md)
- Add dedicated ADR table section to docs/architecture.md (rows 0001-0011)

Closes #154
2026-04-27 08:06:03 -04:00
c14de9a6f8 Merge pull request 'feat(license): rotate Ed25519 public key for maximus-api (#49)' (#137)
Closes #49 — maximus-api now serves /licenses/* in production at https://api.lacompagniemaximus.com

Fixes #49
2026-04-26 13:45:01 +00:00
le king fu
97680417ee feat(license): rotate embedded Ed25519 public key (#49)
All checks were successful
PR Check / rust (push) Successful in 22m25s
PR Check / frontend (push) Successful in 2m25s
PR Check / rust (pull_request) Successful in 22m19s
PR Check / frontend (pull_request) Successful in 2m24s
Replace the placeholder public key with the one whose private
counterpart is now held by the maximus-api license server. The old
key had no licenses issued against it (the server did not exist), so
no users are affected.

The 34 Rust unit tests still pass — license_commands tests use
ad-hoc test keypairs rather than the embedded one, and
embedded_public_key_pem_parses confirms the new PEM is valid.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 09:42:40 -04:00
9c79b73871 Merge pull request 'docs(balance): architecture + ADRs + user guide (#145)' (#153) from issue-145-bilan-7 into main 2026-04-26 13:25:43 +00:00
51a6cec8f1 Merge pull request 'test(balance): cross-cutting integration tests (#144)' (#152) from issue-144-bilan-6 into main 2026-04-26 13:25:37 +00:00
8df1aed258 Merge pull request 'feat(balance): Modified Dietz returns + transfer linking (#142)' (#151) from issue-142-bilan-4 into main 2026-04-26 13:25:32 +00:00
47ecf886d2 Merge pull request 'feat(balance): /balance page + evolution chart + sidebar (#141)' (#150) from issue-141-bilan-3 into main 2026-04-26 13:25:26 +00:00
6341aeb74c Merge pull request 'feat(balance): priced-kind support (#140)' (#149) from issue-140-bilan-2 into main 2026-04-26 13:25:20 +00:00
a344eab2bb Merge pull request 'feat(balance): SnapshotEditPage + simple-kind editor (#146)' (#148) from issue-146-bilan-1b into main 2026-04-26 13:25:15 +00:00
b6387f4b31 Merge pull request 'feat(balance): schema migration v9 + service skeleton + AccountsPage (#138)' (#147) from issue-138-bilan-1a into main 2026-04-26 13:25:09 +00:00
le king fu
ce15c903e4 docs: i18n + CHANGELOG for Bilan documentation
All checks were successful
PR Check / rust (push) Successful in 22m56s
PR Check / frontend (push) Successful in 2m23s
- Add docs.balance.* keys (FR + EN) for the new Balance Sheet section
  consumed by DocsPage (title / overview / features / steps / tips).
- Wire the new section into DocsPage.tsx SECTIONS array with a Wallet
  icon, slotted between reports and settings.
- Add bilingual [Unreleased] CHANGELOG entry for #145 covering the
  architecture.md updates, the 3 ADRs, the new guide section and the
  i18n keys.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 17:06:53 -04:00
le king fu
bef330affb docs(guide): add user guide section for Bilan
New section 10 "Bilan" walks through:
- the 3 routes (/balance, /balance/snapshot, /balance/accounts)
- snapshot entry (simple + priced kinds, prefill button)
- transfer linking with auto-suggested direction
- multi-horizon Modified Dietz returns (3M / 1Y / since inception)
  read alongside the unadjusted return for comparison
- the FK RESTRICT user-facing message on linked-transaction deletion
- price-fetching premium flagged "coming Phase 5"

Renumbers Settings to section 11.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 17:06:46 -04:00
le king fu
098e15bb5c docs(adr): add ADRs 0008-0010 (Modified Dietz, proxy price-fetching, FK RESTRICT)
- 0008 — Modified Dietz: justifies the choice over ROI / TWR / IRR;
  references commands/return_calculator.rs and the 7 TDD cases.
- 0009 — Proxy price-fetching via maximus-api: documents the privacy
  proxy architecture (header stripping, no log correlation, fixed
  simpl-resultat UA), the Yahoo + CoinGecko adapter abstraction, the
  Bearer activation_token auth strategy, the rate limiting (client
  + server), and the dual-side premium gating. Implementation stays
  BLOCKED in #143; this ADR documents the agreed-upon design.
- 0010 — FK ON DELETE RESTRICT on balance_account_transfers
  .transaction_id: justifies the integrity-over-friction trade-off
  for Modified Dietz reproducibility.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 17:06:40 -04:00
le king fu
4d5a0e2e3b docs(architecture): document balance domain (tables, services, hooks, commands, routes)
Update docs/architecture.md to reflect the Bilan feature:
- BDD: 5 new tables + 7 indexes + CHECK + FK invariants (CAD lock,
  kind invariants, ON DELETE RESTRICT on transaction_id)
- Migrations: v8 + v9 added to history
- Services: balance.service.ts with its 4 logical sections
- Hooks: useBalanceAccounts / useSnapshotEditor / useBalanceOverview
- Commands: compute_account_return (1 new) + Phase 5 fetch_price stub
- Routing: /balance, /balance/snapshot, /balance/accounts
- Components: balance/ folder added in tree

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 17:06:30 -04:00
le king fu
5274e51907 chore: CHANGELOG entry for cross-cutting tests
All checks were successful
PR Check / rust (push) Successful in 22m44s
PR Check / frontend (push) Successful in 2m23s
Bilingual entry under [Unreleased] documenting the integration test
suite added for Issue #144: end-to-end happy path, currency lock,
priced-kind tolerance safety, computeAccountReturn wiring, three Rust
migration-on-seeded-DB scenarios, and the source-level non-regression
test on the inlined transfer icon.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 16:54:04 -04:00