Establishes the server-TypeScript foundation and migrates utils/money →
utils/money.mts with branded Cents/Dollars (mirroring the client), so the
cents↔dollars boundary — the origin of the reconciliation bug class — is a
compile error for any typed server caller.
Approach (no build step): Node 25 runs .ts natively (type-stripping).
Migrated modules use the explicit-ESM .mts extension (the project is
"type":"commonjs", which disables .ts ESM auto-detection) and are required
from the CJS callers via Node's require(esm) — verified working. Infra:
tsconfig.server.json (allowJs + checkJs:false → incremental like the
client), npm run typecheck:server, check:server extended to .mts, wired
into ci. 28 require sites repointed to money.mts.
typecheck:server clean; full server suite 225 + e2e probe 17/17 green.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
- The four plan-lifecycle routes (pause/resume/complete/abandon) were near
-duplicate copies returning a plain {error} shape; folded into one
transitionPlan(req,res,{allowedFrom,setSql,action,past}) helper that returns
standardizeError {message, code}, keeps the state guards and ownership scoping.
- Standardized the remaining plan endpoints' error responses (start/list/active/
patch) to standardizeError too.
- enrichPlanWithProgress fetched each snapshot bill one-by-one and wasn't user
-scoped; now a single WHERE id IN (…) AND user_id = ? batch.
Test: tests/snowballPlanRoute.test.js (transitions, INVALID_PLAN_STATE guard,
ownership 404, dollar-denominated current_debts). Server 154 pass.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
PATCH /api/snowball/order silently skipped rows with bad ids or invalid
snowball_order values via bare 'continue' — no feedback, partial updates.
Now validates every item before touching the DB, returning 400 on the first
bad entry. Also adds deleted_at IS NULL filter so soft-deleted bills are
skipped instead of updated silently.