ADR-0180: Shared frontend shell of the sector agents, driven by a product manifest
- Status: proposed on 2026-11-04, pending user validation (see "Points to validate")
- Date: 2026-11-04
- Scope:
navin-enterprise-banking-backoffice/frontend(the shell),navin-enterprise-banking-backoffice/product.manifest.json(first consumer, UC-00), contractcontracts/v1/agent-product-manifest.schema.json(fixed, not changed here), testscontracts/tests/test_product_manifests.py - Related documents: ADR-0175 (Navin Enterprise RAG Agent), ADR-0176 (Navin Enterprise Banking Back-Office Automation
Agent), ADR-0177 (Compliance & Audit Agent),
.navin/continuity/DECISIONS.md(entries of 2026-11-04 "One frontend for the 16 sector agents" and "Agents UC-01 to UC-05 developed in parallel") - Numbering: 0178 and 0179 were taken the same day by other workstreams; this record is 0180, the first free number when it was written.
Context
The user decided on 2026-11-04: the sixteen sector agents of agent_uses_cases/ (UC-00 to UC-15) use one and the
same frontend; only the name, logo, colours, API prefix, menus and entities change. The UC-00 frontend
(navin-enterprise-banking-backoffice/frontend, Next.js 16.4.4, MUI 9.5.0, framer-motion, OIDC Keycloak BFF with
server-side tokens, fr/en/ar RTL) is the most complete one: 23 menus, cursor-paginated list screens, write dialogs
with Idempotency-Key and If-Match, sensitive action confirmation (annexe E), SSE copilot.
Before this decision the name, default colours, the /api/v1/banking-backoffice prefix, the readiness route, the 23
menus (src/components/shell/navigation.ts) and the seven copilot prompts were hardcoded. Five insurance products
(UC-01 to UC-05) are built in parallel and each ships a product.manifest.json: a fixed contract and a shell that
reads it are needed, with no screen copied by any product.
Decision
- One shell. The frontend of
navin-enterprise-banking-backoffice/frontendis the shell of every sector agent. No frontend is created per product; a product ships a manifest, a logo, optional i18n override files and its backend. - Manifest contract.
contracts/v1/agent-product-manifest.schema.json(version 1.0) is the contract between the shell and the products. The shell reads it at start throughNAVIN_PRODUCT_MANIFEST(default../product.manifest.json, relative to the frontend folder), validates it with a hand-written type guard (src/lib/product-manifest.ts, no npm package added; the full JSON Schema validation is the contract testcontracts/tests/test_product_manifests.py), freezes it, and refuses to start on a missing or invalid file (never silent defaults). - Public subset. Only
product_id,use_case,sector,name,short_name,description,branding,locales,roles,navigation,entitiesandcopilotreach client components (publicManifestOf). Ports, API prefix, OpenAPI path, override files and the template identity stay on the server. - Manifest-driven BFF. The
/api/backoffice/*proxy, the instance branding read, the readiness route and the copilot SSE stream useapi.prefixandapi.healthof the manifest. Existing environment variables still win (NAVIN_BACKOFFICE_API_PREFIX,NAVIN_BACKOFFICE_READINESS_PATH); the manifest supplies the default. - Instance branding above product defaults. The name, logo (
branding.logo_path, served frompublic/products/<product_id>/) and colours of the manifest are the defaults, shown with the "default identity" notice. The backend answer ofGET <prefix>/settings/branding, when present, replaces them; the branding administration editor is unchanged. - Derived navigation. Menus come from
manifest.navigation: id, href, fr/en/ar label, icon named from a bounded set of@mui/icons-materialicons the shell already bundles (NAV_ICONS), display roles, generic screen. An unknown icon falls back to a generic one and the UC-00 manifest test fails if any icon is unmapped. Role filtering is unchanged (display only; the backend decides on every route). - Generic screens. Existing dedicated pages keep precedence in Next; a generic route
src/app/(app)/[...screen]/page.tsxrenders any manifest entry whose href has no dedicated page, through the screen it names:dashboard,work_queue,entity_list,approvals,copilot,documents,eod,controls,reports,connectors,audit,governance,admin,settings.entity_listbuilds the table fromentity.columns(money per currency from decimal strings, never summed; statuses in words; references in monospace), the detail drawer fromdetail_fields(with the ETag), and the actions fromentity.actions:WriteDialogwithIdempotency-Key,If-Matchwhenif_match,ConfirmSensitiveActionwhenrisk_levelisside_effect, fields fromaction.fieldswith the decimal and currency validation ofsrc/lib/money.ts. Nothing banking-specific lives in this generic path; a second manifest (insurance claims) proves it in a test. - i18n. Shell messages stay in
src/i18n/messages/*; a product adds its wording throughi18n_overrides(one JSON file per locale, read on the server, shell keys only, no U+2013 or U+2014). Labels of menus, entities, columns, actions and copilot prompts come from the manifest in fr, en and ar. - Physical location. The shell stays in
navin-enterprise-banking-backoffice/frontendduring the UC-00 session. Its move to a neutral folder is agit mvafter UC-00 closes, never during it, so that the nine concurrent workstreams do not overwrite each other. A foldernavin-enterprise-common/frontend-shellthat appeared the same day is not the shell described here; its consolidation (consume this shell or disappear) is decided at the close of UC-00 together with this ADR.
What a product may customise
name, short_name, description, branding (logo, colours), api.prefix, api.health, api.openapi, ports,
locales.default, roles, navigation (menus, order, icons from the bounded set, roles, screen), entities
(resources, columns, detail fields, actions and their fields), copilot.quick_prompts, i18n_overrides,
template.service_id.
What a product may not customise
The stack (Next.js, MUI, framer-motion), the BFF and its cookie policy, the session, CSRF, the Idempotency-Key
header on every write, the sensitive action confirmation dialog, cursor pagination, the "never sum across
currencies" rule, the screen components, the supported locales (fr, en, ar) and text direction, the icon set, the
reserved platform ports, template.published (always false until the user publishes).
Alternatives rejected
- One frontend per agent (copy of the UC-00 folder): sixteen copies to maintain, refused by the user.
- A generic frontend without business screens: refused (hollow interface).
- Extracting a shared npm package first, during UC-00: impossible to stabilise while nine workstreams change the shell; deferred to after UC-00 closes (point 9).
- Validating the manifest in the frontend with a JSON Schema library: extra package refused; the type guard is enough for the shell and the contract test does the full validation.
Consequences
- UC-00 renders exactly the 23 menus of before (same ids, hrefs, icons, roles, order): test
tests/product-manifest.test.tsx. - The navigation and i18n parity tests read the manifest instead of
NAV_ITEMS. - The five insurance products ship a manifest and a logo; no screen is cloned.
- The UC-00 manifest has 23 menus, 19 entities and 36 write actions, validated by the contract test.
- An entity whose resource depends on a parent (
/reconciliations/{id}/breaks,/connectors/{id}/sync-runs) is not in manifest 1.0 (theresourcepattern refuses{id}): those screens stay dedicated pages; a contract evolution may describe them. - The deferred "Re-run extraction" action of documents stays deferred:
docs/api/openapi.backend.jsondoes not exposePOST /documents/{id}/reextractat the time of writing.
Security
- No port, backend URL or file path reaches the browser: the public subset is computed on the server and tested
(
JSON.stringify(public)contains neither8471nor/api/v1/). - The manifest logo goes through
safeLogoUrl(same-origin path or https only). - Action paths of the manifest are relative to the prefix and refused when they contain
..; the row id is encoded; the BFF keeps its segment checks. i18n_overridesfiles cannot leave the product folder.
Points to validate
- Confirm that the shell stays in
navin-enterprise-banking-backoffice/frontenduntil UC-00 closes, and the name of the neutral target folder of thegit mv. - Decide the fate of
navin-enterprise-common/frontend-shell(consume this shell or remove it). - Validate the list of generic screens and the bounded icon set.