Skip to main content

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), contract contracts/v1/agent-product-manifest.schema.json (fixed, not changed here), tests contracts/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​

  1. One shell. The frontend of navin-enterprise-banking-backoffice/frontend is 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.
  2. 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 through NAVIN_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 test contracts/tests/test_product_manifests.py), freezes it, and refuses to start on a missing or invalid file (never silent defaults).
  3. Public subset. Only product_id, use_case, sector, name, short_name, description, branding, locales, roles, navigation, entities and copilot reach client components (publicManifestOf). Ports, API prefix, OpenAPI path, override files and the template identity stay on the server.
  4. Manifest-driven BFF. The /api/backoffice/* proxy, the instance branding read, the readiness route and the copilot SSE stream use api.prefix and api.health of the manifest. Existing environment variables still win (NAVIN_BACKOFFICE_API_PREFIX, NAVIN_BACKOFFICE_READINESS_PATH); the manifest supplies the default.
  5. Instance branding above product defaults. The name, logo (branding.logo_path, served from public/products/<product_id>/) and colours of the manifest are the defaults, shown with the "default identity" notice. The backend answer of GET <prefix>/settings/branding, when present, replaces them; the branding administration editor is unchanged.
  6. Derived navigation. Menus come from manifest.navigation: id, href, fr/en/ar label, icon named from a bounded set of @mui/icons-material icons 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).
  7. Generic screens. Existing dedicated pages keep precedence in Next; a generic route src/app/(app)/[...screen]/page.tsx renders 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_list builds the table from entity.columns (money per currency from decimal strings, never summed; statuses in words; references in monospace), the detail drawer from detail_fields (with the ETag), and the actions from entity.actions: WriteDialog with Idempotency-Key, If-Match when if_match, ConfirmSensitiveAction when risk_level is side_effect, fields from action.fields with the decimal and currency validation of src/lib/money.ts. Nothing banking-specific lives in this generic path; a second manifest (insurance claims) proves it in a test.
  8. i18n. Shell messages stay in src/i18n/messages/*; a product adds its wording through i18n_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.
  9. Physical location. The shell stays in navin-enterprise-banking-backoffice/frontend during the UC-00 session. Its move to a neutral folder is a git mv after UC-00 closes, never during it, so that the nine concurrent workstreams do not overwrite each other. A folder navin-enterprise-common/frontend-shell that 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 (the resource pattern 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.json does not expose POST /documents/{id}/reextract at 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 neither 8471 nor /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_overrides files cannot leave the product folder.

Points to validate​

  1. Confirm that the shell stays in navin-enterprise-banking-backoffice/frontend until UC-00 closes, and the name of the neutral target folder of the git mv.
  2. Decide the fate of navin-enterprise-common/frontend-shell (consume this shell or remove it).
  3. Validate the list of generic screens and the bounded icon set.