Prompt file imported from jibu7/vinea (
.github/prompts/phase-3-frontend-foundation.prompt.md). Copyright stays with the author.
Implement Phase 3 as specified in docs/Vinea_ERP_Master_Plan_v5.md §5 (P3), §B.3 (design language, D9 hybrid) and Appendix C (navigation contract). P3 is the first phase the owner will see. Beauty is a phase gate here, not a coat of paint: the build has a mandatory STOP for owner approval after step 2. Do not proceed past it without an explicit "approved" from the owner.
Where the plan leaves a design choice open, decide, implement, and list it under "Decisions worth review" in your final report, as P1 and P2 did.
Preconditions (already built — consume, never re-implement)
- Auth (P1): httpOnly cookie sessions with refresh rotation and reuse detection. The browser never sees a token; every fetch uses
credentials: 'include'. A user has one or moreCompanyMemberships; the active company is a server-side session concept, switched via the existing membership endpoint. Do not store tokens or company ids in localStorage. - Kernel + GL API (P2, hardened):
POST /v1/gl/journal-entries,POST /v1/gl/cashbook-entries(both require anIdempotency-Key; a reused key with a different body returns409 idempotency_key_reused), reversal endpoint, trial balance and account-transactions enquiries,GET /gl/period-balances/verify, CRUD forgl_accounts(rename keeps history),gl_transaction_types(withmodulediscriminator),projects, fiscal years / periods, year-end close and reopen (accounting_periods:reopen). Error responses carry a machine code (unbalanced_entry,period_closed,missing_exchange_rate, …) andfield_errors. The engine may append a line flaggedis_rounding_line=true. - Masters (P1/P2):
currencies(withdecimal_places; RWF = 0),exchange_rates,tax_codes,branches,gl_settings(retained earnings + rounding-difference accounts). - Backend rule: if a screen needs an endpoint that does not exist (e.g. thin CRUD for
exchange_rates,branches,gl_settings, users/memberships listing), add it as a thin service + router following the P1 pattern, with RLS linter tests. No new tables that duplicate existing ones; no writes to journal tables outside the Posting Engine. - Decisions already made: English only at launch, but i18n plumbing goes in now (§8 Q3). Hybrid UI direction (D9). Navigation order = owner's
software_interfacespec with the three corrections (Input/Output VAT labels; the duplicated "Account Receivables" heading is Accounts Payable; Allocation sits under Transactions) and the added Enquiries intent.
Stack for this phase
Next.js 15 App Router, TypeScript strict, Tailwind with design tokens exposed as CSS custom properties (light + dark), next-intl for i18n, TanStack Query for server state, React Hook Form + Zod for forms, a headless-primitive component library (Radix-based) for dialogs/menus/combobox, Vitest + Testing Library for units, Playwright for e2e, axe for accessibility checks. Salvage from the abandoned repo only what §A.2 lists (CurrencyInput, CurrencyDisplay, data-table, date pickers, dialog/form/toast set) and re-style it to the tokens; port nothing else.
Build order — commit after each step with green checks
- Design system (
frontend/src/design/):tokens.css(colour scales incl. semantic roles, type scale with a distinctive display/body pairing, spacing, radii, elevation, focus ring, dense-mode variables for work screens), light and dark themes, and a/designroute rendering every primitive: buttons, inputs, select/combobox, status chips, table, tabs, dialog, toast, command palette shell, empty state, and a dense data grid sample. Locale-awareformatMoney(amount, currency)honouringdecimal_places(RWF shows no decimals, USD two) andformatDate. All UI strings go throughnext-intlmessage files from this first commit — no hard-coded English in components. - Flagship prototypes (static, real components, mock data): three routes under
/design/prototypes: (a) dashboard — airy SaaS shell, company switcher, four-intent nav, KPI cards, recent activity; (b) document workspace — the journal batch screen: header strip, spreadsheet-grade line grid, sticky footer (Debit / Credit / Difference), status chip,Ctrl+Kpalette open state; (c) POS — till screen with product grid, basket, tender panel (visual only; POS logic is P11). Each in light and dark. Then STOP. Report with screenshots (Playwright captures at 1440×900 and 1280×720). Wait for owner approval; apply requested changes; do not start step 3 until approved. - Shell + navigation contract: authenticated layout with sidebar rendering the full tree from Appendix C in the owner's order — Administration / Maintenance / Transactions / Enquiries / Reports. Items built in this phase are live; items belonging to later phases are rendered disabled with a phase tag (
P4…P12) so the tree doubles as the acceptance checklist. Permission-filtered from the membership's roles.Ctrl+Kcommand palette (navigate, "new journal", "new cashbook entry", switch company, toggle theme). Company switcher calls the membership endpoint and invalidates all queries. Global error boundary; toasts mapped from API error codes via i18n. - Maintenance screens: Company details (company, fiscal years + accounting periods with status timeline and close/reopen actions, general settings incl.
gl_settingsaccount pickers); Foreign Currency (currencies, exchange-rate table byvalid_from); Tax types (read/edit the seeded set; labels as corrected); General Ledger → Chart of Accounts (tree byparent_id, class, postable/control flags, active toggle), Branches, Transaction types (module='gl'only in this phase), Defaults, Rename Account (code change with the history note the API returns); Projects master (D8). Administration → Users & memberships (list, invite, role assignment, deactivate). - Transactions — the document workspaces: Journal batches and Cashbook batches on one shared
DocumentWorkspace+LineGridcomponent. Keyboard model: Tab/Enter move cells, arrow keys navigate rows, Enter on the last row adds a row,Ctrl+Enterposts,Esccancels edit; account/transaction-type/project/branch/tax cells are typeahead combos; multi-currency lines show rate and base amount; footer shows totals and the difference in base currency; Post is disabled while unbalanced. Drafts autosave client-side (IndexedDB/localStorage keyed by user + company) — nothing touches the ledger until Post. Each draft carries a generated UUID used asIdempotency-Key, so a retried post replays instead of duplicating. Mapunbalanced_entry/period_closed/missing_exchange_rate/idempotency_key_reusedto inline field errors, not toasts. After posting, show the entry read-only with anyis_rounding_linehighlighted as engine-generated; reversal action with date + reason and the one-reversal rule surfaced in the UI. Cashbook variant defaults one side to the selected bank/cash account. - Enquiries: Account enquiry (date range, running balance, branch/project filters, click a row → entry detail drawer); Trial balance enquiry (as-of date, branch, project; click an account → account enquiry). Both stream large results with server pagination.
- Reports (GL, this phase's scope): Account transactions, Chart of accounts listing, Trial balance — print-ready layout (
@media print), CSV export. Balance sheet, income statement, cashbook and bank reconciliation reports stay disabled with their phase tags (P8/P10). - Tests + CI: Vitest units for
formatMoney(RWF 0dp, USD 2dp, negative, half-up display), the LineGrid keyboard model, and permission-filtered nav. Playwright e2e againstdocker compose: login → switch company → create a balanced two-line journal → post → entry visible in account enquiry → trial balance foots; an unbalanced journal cannot be posted; a retried post with the same draft replays (one entry, no duplicate); a post into a closed period shows the inline error.axepasses with no serious violations on dashboard, workspace and COA. Addfrontend: lint · typecheck · test · build · e2ejobs toci.ymlnext to the backend gates. Backend suite andalembic checkmust stay green.
Definition of Done (all must be true)
Owner approved the step-2 prototypes; the sidebar renders the complete Appendix C tree in the owner's order with later phases visibly tagged; every string is externalised and the app ships one locale (en); RWF renders with no decimals everywhere money is shown; a journal and a cashbook entry can be drafted, autosaved, posted, seen in enquiries and reversed from the UI; light and dark both pass axe; frontend CI jobs green; backend suite unchanged and green. Final report: what landed, screenshots of the six main screens, decisions worth review, and any plan deviations with reasons.