Rebuilding against a live backend, verification-first
The mobile app was built from scratch as a 16-phase, dependency-ordered rebuild targeting the same backend the web client used — but with an explicit rule change: never trust a documented API shape, always verify it against the actual Prisma schema and controller before writing a screen against it. This caught real backend defects before they became mobile bugs, and produced a living document (CLAUDE.md) that tracks every verified contract and every discovered gotcha, rather than a static spec written once and left to rot.
The verification discipline paid for itself repeatedly. Building Receipts caught that the list/get-by-number endpoints were undocumented-wrapped ({ message, receipts }), which had shipped a silently-empty ReceiptDetailScreen for an entire session before being caught. Building Customers caught two fabricated fields (balance/spent) that don't exist on the model at all. Building Reports caught that StockItem's money fields had been mistyped as strings since the very first phase. A strict per-domain hook pattern (use<Domain>List / use<Domain> / use<Domain>Mutations, gated on useCapabilities().can(pageKey)) kept 12+ domains consistent and made cache invalidation predictable instead of duplicated per screen. Checking every mutating route individually paid off — assuming "Credits' all-Admin-only mutation pattern" would generalize to Quotations or Services would have been wrong both times; the actual rule turned out to be domain-specific, and checking caught it before shipping the wrong gate. Real backend defects were surfaced and documented rather than silently worked around: a cross-tenant data leak in the /api/tax domain, a broken toggle-email-notifications route, two raw password-hash leaks in API responses, an unfinished "convert quotation to invoice" placeholder.
Money-field typing bit repeatedly despite the rule existing — Float vs. Decimal (number vs. string serialization) isn't just inconsistent across models, it's inconsistent across endpoints on the same model (GET /payroll/employees returns computed numbers; GET /payroll/employee/:id returns raw Decimal strings for the same columns). Even with the "always grep the schema" rule in place, this class of bug still needed to be rediscovered per-domain rather than solved once. canCreate vs. canMutate was conflated on the first attempt in four separate places (Dashboard quick-actions, Inventory's Add FAB) before the distinction was made a named, checked convention — the backend gates create and update/delete independently per domain, and assuming they share a gate is an easy, repeatable mistake. Two screens shipped with no way back (Receipts, Customers) because "is this a tab" was used as a proxy for "does this need a back button," which fails for screens that are sometimes pushed and sometimes tab roots depending on role. Several substantial features (Services accounting sub-ledger, payroll approval workflow, letterhead branding) were deliberately scoped out to keep phases shippable — real, not accidental, technical debt that's now an explicit backlog rather than a surprise.
A documentation file is only useful if it's falsifiable and gets corrected in place — treating every claim (including your own previous session's) as "wrong until re-verified" caught bugs a static spec would have let ship silently. Discipline has to be re-applied per domain, not assumed to generalize — "this worked for Credits" and "this worked for Customers" were each wrong guesses for the next domain at least once; checking the actual route/model every time was the only thing that reliably worked. Scoping cuts explicitly and writing them down (rather than quietly skipping them) turns technical debt into a plan instead of a surprise for the next person — or the next session — picking up the code. Compared to the web client's postmortem: the same category of bug (wrong assumptions about response shape, permission gating) appeared in both codebases, but the mobile build caught it before shipping because verification was a built-in step, not an afterthought audit.