feat(balance): detail-account wizard (pivot date) (#215) #225
No reviewers
Labels
No labels
autopilot:pending-human
source:analyste
source:defenseur
source:human
source:medic
status:approved
status:blocked
status:in-progress
status:needs-clarification
status:needs-fix
status:ready
status:review
status:triage
type:bug
type:feature
type:infra
type:refactor
type:schema
type:security
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: maximus/Simpl-Resultat#225
Loading…
Reference in a new issue
No description provided.
Delete branch "issue-215-detail-wizard"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Resolves #215
Stacked on #224.
Light confirmation modal (
DetailAccountWizard) flipping a simple balance account to detailed entry mode. Toggle-only per the 2026-06-04 plan-overnight decision: setskind='detailed'+detailed_since = today(local civil day, YYYY-MM-DD) viaupdateBalanceAccount. No title capture — per-security holdings are entered at the next normal snapshot (validateDetailedSnapshotrequires them from the pivot on).Détailler en titresaction in the per-row actions menu ofBalanceAccountsTable, shown only forkind === 'simple'rows (replaces the disabledDétail / coming soonplaceholder).< detailed_since) stays frozen read-only.detailed → simpleonce holdings exist; the UI exposes no inverse action. The wizard surfaces the typed error viabalance.detailWizard.errors.account_kind_detailed_has_holdings.buildDetailToggleInput()helper covered by 3 focused unit tests (no jsdom harness in this project).balance.detailWizard.*; removed the now-deadbalance.overview.detailAction/detailComingSoonkeys.npm run buildgreen,npm test621 passing.Generated autonomously by /autopilot run of 2026-06-06
Adversarial review — PR #225 (Issue #215, Link 7/9)
Verdict: APPROVE ✅ — no must-fix issues.
Stacked on
issue-216-drilldown-gain; base→head diff confirms the table change is surgical andkind/detailed_since/validateDetailedSnapshot/#212 guard all come from the lower stack (migration v15), not invented here.Toggle correctness ✔
buildDetailToggleInput(today)→{ kind: 'detailed', detailed_since: localISO(today) }.localISOuses local civil accessors (getFullYear/getMonth()+1/getDate(), zero-padded) — no UTC, no off-by-one. Testnew Date(2026, 5, 6, 12, 30)→"2026-06-06"is constructed in local time, so the assertion is self-consistent.normalizeSnapshotDateonly validates^\d{4}-\d{2}-\d{2}$and returns the string verbatim —YYYY-MM-DDfromlocalISOpasses through unchanged. Round-trips cleanly intobalance_accounts.detailed_since.Pivot semantics ✔
detailed_sinceis the authoritative pivot persisted on the account.validateDetailedSnapshotreturns early whenholdings.length === 0(pre-pivot aggregated lines tolerated read-only); holdings are required only at/after the pivot via the snapshot editor. Toggle-only / no-title-capture is correctly honored.Gating ✔
onDetailAccount && acc.kind === 'simple'. Already-detailed rows never show it → no double-toggle from the UI. Even if re-fired,updateBalanceAccountis idempotent on an already-detailed account (benign).simple → detailedis always allowed by the service (the guard only fires ondetailed → simplewith holdings).detailed → simple (one-way) ✔
src/(grep clean — the onlykind:'simple'hits are AccountForm creation default, category kinds, and the #212 guard's own tests). Backed by the service backstopaccount_kind_detailed_has_holdings(COUNT overbalance_snapshot_holdings ⋈ lines). Defense-in-depth confirmed.Refetch ✔
onDetailed → void reload()(BalancePage), wizard thenonClose()→detailTarget=nullunmounts the modal. Row re-renders withkind='detailed'; the menu item self-hides. No stale state. Confirm buttondisabled={submitting}blocks double-submit in-flight.Dead i18n key removal ✔
balance.overview.detailAction/detailComingSoonremoved from both locales; full-tree content grep across 246 src files finds zero remaining refs. Newbalance.detailWizard.*tree is symmetric FR/EN (10 keys each, no empty values).common.close/common.cancelpresent both locales.#216 collision ✔ (no break)
<button>inside the actions-menu<div>plus prop wiring +ListTreeimport. The #216 restructure —Fragment key={account_id}, chevron,colSpanlatent-gain drill-down<td>(lines ~456–645) — is untouched. Row structure / colSpan intact.i18n copy ✔
pointFrozen), next-snapshot entry (pointNextSnapshot), irreversibility (irreversible), plus the pivot date (pointPivot). No hardcoded text — every string goes throught(). Typed-error path interpolateserrors.${e.code}withdefaultValue: e.messagefallback (unknown code degrades gracefully, never leaks a raw key).Tests ✔
buildDetailToggleInputunit tests, no.skip/.only/.todo/xit/fit. They cover: midday→date, zero-padding single-digit month/day, and the no-downgrade/no-envelope-mutation invariant (asserts exactly['detailed_since','kind']keys). Reasonable given the project has no jsdom harness (matchesSecurityPicker.test.tsconvention).Non-blocking suggestion
pivotshown in the modal islocalISO(new Date())computed at render, while the committeddetailed_sinceislocalISO(new Date())recomputed at confirm (insidehandleConfirm). If the modal stays open across a midnight boundary, the user sees day N but commits day N+1. Extreme edge case; resolving the pivot once (e.g.useState(() => localISO(new Date()))) and reusing it in both the copy and the confirm payload would make the displayed and persisted date provably identical.Files:
DetailAccountWizard.tsx(+163),DetailAccountWizard.test.ts(+35),BalanceAccountsTable.tsx(+21/-8),BalancePage.tsx(+17),fr.json/en.json(+15/-3 each). Build green / 621 tests passing per PR body.