docs: record the currency invariants and the CSS-token guards
roundMoney takes digits now, Currency is open with the dataset as authority, codes are identity, and an amount field parses its own currency. Also folds in the two CSS guards earned during the redesign. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 5
parent
cdece63c68
commit
5b9754f1d2
@@ -88,6 +88,15 @@ A float, deliberately, with two rules that make it safe:
|
||||
**`FxRate.baseUnitsPerOne` is never rounded.** Rates are stored at six decimals
|
||||
and are not money; quantizing one to cents is wrong in kind.
|
||||
|
||||
**Decimal places are data, not a constant.** `Dataset.currencies` records each
|
||||
currency's code, name, symbol and digits, because the owner can define their
|
||||
own. `roundMoney` therefore takes a digit count and every call site resolves it
|
||||
with `digitsFor` — required rather than defaulted, so a missed site is a
|
||||
compile error instead of something that works for every 2-decimal currency and
|
||||
breaks on the first zero-decimal one. `src/domain/currencyDigits.test.ts` drives
|
||||
a zero-decimal currency through the whole stack, which is the only test that can
|
||||
tell a real implementation from one that assumes 2.
|
||||
|
||||
## Splits and finance plans compose
|
||||
|
||||
A purchase can be split across posts *and* spread over months at once. The trick
|
||||
|
||||
@@ -111,13 +111,29 @@ For more information, read the Bun API docs in `node_modules/bun-types/docs/**.m
|
||||
without an explicit argument. It must be testable with no DOM.
|
||||
- Money is a float. Always round with `roundMoney` from `src/domain/money.ts`
|
||||
after any division, after any FX conversion, and before persisting.
|
||||
- **`roundMoney(amount, digits)` takes a DIGIT COUNT, not a currency code.**
|
||||
Decimal places live in `Dataset.currencies`, because the owner defines
|
||||
currencies; resolve them with `digitsFor(dataset.currencies, code)`. The
|
||||
argument is required on purpose — a default of 2 is right for every currency
|
||||
the app shipped with, so a missed call site would pass every test and be
|
||||
silently wrong for the first zero-decimal currency added.
|
||||
- When splitting an amount, never round the last part independently. Compute it
|
||||
as `total - sum(others)` so the parts sum exactly to the whole.
|
||||
- `MonthId` is the string `"YYYY-MM"`. Stored data never contains `Date` objects.
|
||||
- **`Currency` is an open string code; `Dataset.currencies` is the authority.**
|
||||
There is no global list of valid currencies any more. A code is IDENTITY — it
|
||||
keys the FX table and every stored `Money` — so codes are normalised
|
||||
uppercase, are never editable, and a currency still referenced cannot be
|
||||
removed (`currencyUsage` names what refers to it). `parseDatasetJson`
|
||||
validates the table and every `Money` against it; that boundary check is
|
||||
what replaced the compile-time safety of the old closed union.
|
||||
- The envelope rollover fold has exactly one implementation, in
|
||||
`src/domain/fold.ts`. Year and summary views aggregate over it — they never
|
||||
reimplement the math.
|
||||
- Over-budget is always allowed. Never add validation that blocks it.
|
||||
- An amount field parses its own currency: "30$" is thirty dollars. Use
|
||||
`parseMoneyInput` (`src/ui/moneyInput.ts`) and keep the input `type="text"` —
|
||||
a number input discards the "$" before the parser ever sees it.
|
||||
- Base path comes from `BUN_PUBLIC_BASE_PATH`; never hardcode a subpath, and
|
||||
read it through a `try/catch` accessor — see the gotcha below.
|
||||
- A post's allocation is a dated series, `Post.rules: RuleVersion[]`, resolved by
|
||||
@@ -148,8 +164,16 @@ choice.
|
||||
unset, the bare reference reaches the browser and throws before the app boots.
|
||||
A `typeof process` guard does not work — it survives inlining and discards the
|
||||
inlined value, silently breaking subpath deploys.
|
||||
- **Never define a bare `--accent` CSS variable.** shadcn owns it; overriding it
|
||||
drops hover contrast below WCAG AA app-wide. Project tokens are `--budget-*`.
|
||||
- **Never define a bare `--accent` CSS variable.** shadcn owns it as a hover
|
||||
BACKGROUND paired with `--accent-foreground`; setting one without the other
|
||||
drops hover contrast below WCAG AA app-wide. Redefine shadcn tokens only in
|
||||
pairs — `src/cssPairs.test.ts` enforces it — and put brand colour on
|
||||
`--primary`, never `--accent`. Project tokens are `--budget-*`.
|
||||
- **A `var(--token)` naming a token `src/index.css` does not define paints
|
||||
NOTHING, silently.** An undefined custom property invalidates the whole
|
||||
declaration: no warning, no throw, no fallback. Renaming `--rule` this way
|
||||
killed the carry meter and no test noticed. `src/ui/cssTokens.test.ts` guards
|
||||
it.
|
||||
- **A React error boundary does not catch event-handler throws.** Domain
|
||||
functions that throw must be guarded at the call site when called from one.
|
||||
- **Do not "fix" a domain throw by making it return a fallback.** Silently wrong
|
||||
|
||||
Reference in New Issue
Block a user