# Model Config — Shared Tab Shell Tracker

**Decision:** Option 1 — a shared tab-strip component rendered at the top of
every per-model sub-page. Each sub-page stays its own LiveView/route (all
already share `live_session :fraud`, so navigating between them is a fast
in-socket transition, not a full page reload); only the header becomes
consistent and every tab links to every sibling.

## Findings that shaped the plan

- 4 different hand-rolled sub-nav variants already exist independently
  (Script/Function/Adaptation share a near-identical but copy-pasted list;
  Sanctions has a completely different rose-colored 4-link version; XPaths /
  TTL Counters / Tags / Gateway Rules have no tabs at all, just a breadcrumb).
- None of the existing ad-hoc lists include **Sanctions** or **Tags**.
- **Tags has no entry point anywhere in the UI** — the route
  (`/fraud/model-config/:model_id/tags`) and `ModelTagsLive` both exist and
  work, but the main model list's "Configuration Sub-pages" grid never links
  to it. Only reachable by typing the URL. Fixed as part of this work.
- `/fraud/model-config/sync` (Synchronisation) is **not** model-scoped (no
  `:model_id` in its path) — it stays off the per-model tab strip; it's not
  a sibling of these 10 pages the way they are of each other.

## Tab set (10, model-scoped)

Request XPaths · TTL Counters · Gateway Rules · Abstraction Rules ·
Calculations · HTTP Adaptation · Inline Functions · Inline Scripts ·
Sanctions · Tags

## Plan

1. New `GatewayWebWeb.ModelConfigComponents` module (mirrors
   `CaseBrowseComponents`) with one function component,
   `sub_page_tabs(assigns)`, taking `model` + `active` (tab key). Renders:
   breadcrumb ("← Models") + model name + the 10-tab strip
   (horizontal, `overflow-x-auto`, active tab highlighted — same visual
   language as Case Browse's bottom-tab-strip).
2. Each of the 10 sub-page LiveView modules: `import
   GatewayWebWeb.ModelConfigComponents`.
3. Each of the 10 sub-page templates: replace whatever ad-hoc
   breadcrumb/sub-nav block currently sits at the top with
   `<.sub_page_tabs model={@model} active="..."/>`. Each page's own H1,
   subtitle, and "Add" button are page-specific content and stay — only the
   *navigation* block is being unified, not each page's visual identity.
4. Main `ModelConfigLive` "Configuration Sub-pages" grid: add the missing
   Tags card; leave Synchronisation where it is (not model-scoped).
5. Compile + asset build; manual smoke test of tab navigation across all 10
   pages, including that the `active` tab highlight is correct on each.

## Status

| ID | Item | Status | Notes |
|----|------|--------|-------|
| T1 | `ModelConfigComponents.sub_page_tabs/1` component | ☑ | found & fixed a real bug during implementation: `~p` verified-routes sigil can't interpolate a dynamic path *segment* (`#{key}`) — switched that one link to a plain string interpolation |
| T2 | `ModelXpathLive` — import + header swap | ☑ | plain breadcrumb → tabs |
| T3 | `ModelTtlCounterLive` — import + header swap | ☑ | plain breadcrumb → tabs |
| T4 | `ModelGatewayRuleLive` — import + header swap | ☑ | plain breadcrumb → tabs |
| T5 | `ModelTagsLive` — import + header swap | ☑ | plain breadcrumb → tabs |
| T6 | `ModelAbstractionsLive` — import + header swap | ☑ | drop ad-hoc "← Model Config" / "Calculations" links |
| T7 | `ModelCalculationsLive` — import + header swap | ☑ | drop ad-hoc "← Model Config" / "← Abstraction Rules" links |
| T8 | `ModelAdaptationLive` — import + header swap | ☑ | drop 7-link hand-rolled sub-nav |
| T9 | `ModelFunctionLive` — import + header swap | ☑ | drop 7-link hand-rolled sub-nav |
| T10 | `ModelScriptLive` — import + header swap | ☑ | drop 7-link hand-rolled sub-nav |
| T11 | `ModelSanctionsLive` — import + header swap | ☑ | keep icon-badge/subtitle identity; drop rose 4-link sub-nav; Add button moved to standard position |
| T12 | `ModelConfigLive` — add missing Tags card to the sub-pages grid | ☑ | fixes dead-end bug |
| T13 | `mix compile` clean | ☑ | one pre-existing unrelated warning in model_function_live.html.heex (single-quoted charlist), not introduced by this change |
| T14 | `mix assets.build` clean | ☑ | no JS changes, build unaffected |
| T15 | Manual smoke: navigate all 10 tabs from each of the 10 pages, verify active-tab highlight, verify Tags reachable from main list | ☑ | confirmed by user — main list shows new Tags card, Request XPaths sub-page renders tab strip with active tab highlighted correctly |

## Phase 6 — Fixes from user testing round 1

| ID | Item | Status | Notes |
|----|------|--------|-------|
| F1 | Right-side dead space on main model edit form | ☑ | dropped `max-w-3xl` on the form; widened "Configuration Sub-pages" grid to `grid-cols-2 lg:grid-cols-3 xl:grid-cols-4` so cards don't stretch too wide at full page width |
| F2 | Right-side dead space on XPaths / TTL Counters / Gateway Rules / Tags | ☑ | dropped `max-w-4xl` on all four (fields are already in `grid-cols-*` layouts, so they widen proportionally rather than stretching edge-to-edge) |
| F3 | Same dead space on Sanctions | ☑ | dropped `max-w-5xl` (found while fixing F2, same root cause, not explicitly reported but same page family) |
| F4 | Flicker / "broken screen" flash when clicking between sub-page tabs | ☑ | root cause: `topbar` progress-bar JS was already wired to LiveView's `phx:page-loading-start/stop` events, but there was no CSS for the `.phx-loading` state LiveView applies during the live-redirect, so the outgoing page cut to blank with no transition. Added `[data-phx-main].phx-loading { opacity: 0.4; transition: ...; pointer-events: none; }` in `app.css` — dims the current page smoothly instead of an abrupt cut. Verified against `deps/phoenix_live_view` JS source (`PHX_LOADING_CLASS`/`PHX_MAIN` = `data-phx-main`) rather than guessing the selector. |
| F5 | `mix compile` / `mix assets.build` clean after fixes | ☑ | |
| F6 | Manual re-test: confirm no dead space, confirm smoother transition on tab clicks | ☐ | needs running app |

## Phase 7 — Real root cause of the flicker (found via console/network diagnosis)

F4's `.phx-loading` CSS was a reasonable general safety net but not the actual fix — user confirmed via DevTools (no console errors, no document-type network request, only websocket frames) that navigation was a genuine LiveView live-patch the whole time, ruling out a hard reload.

**Real cause:** the sidebar (`layouts.ex`) is an accordion — each `<.nav_subgroup>` only stays expanded while `@active_nav` matches its own set of values (e.g. "ML & Analytics" opens for `fraud_models`/`fraud_labels`/etc.; "Model Config" opens for `fraud_model_config*`/`fraud_sync`/`fraud_feature_catalog`). 7 of the 10 model-config sub-pages (XPaths, TTL Counters, Gateway Rules, Tags, Abstraction Rules, Calculations, Sanctions) had `active_nav: "fraud_models"` — copy-pasted from `ModelTrainingLive`'s ("ML Models") mount and never corrected — while only Adaptation/Functions/Scripts had the correct `"fraud_model_config"`. Every click between a "fraud_models" page and a "fraud_model_config" page forced the sidebar to collapse the "ML & Analytics" accordion section and expand "Model Config" (and vice versa) — a real structural DOM change during the live-patch, which is what looked like a broken/flickering screen. It also meant these pages incorrectly highlighted "ML Models" in the sidebar instead of "Model Config" — a real, pre-existing (not something this tab work introduced) bug independent of the flicker.

| ID | Item | Status | Notes |
|----|------|--------|-------|
| G1 | Standardize `active_nav` to `"fraud_model_config"` across all 11 pages (main list + 10 sub-pages) | ☑ | fixed 8 files: xpath, ttl_counter, gateway_rule, tags, abstractions, calculations, sanctions, and the main `model_config_live.ex` list page (adaptation/functions/scripts were already correct) |
| G2 | `mix compile` / `mix assets.build` clean | ☑ | |
| G3 | Manual re-test: click through all 10 tabs, confirm sidebar stays stable (no accordion collapse/expand) and no flicker | ☐ | needs running app |

## Deferred / out of scope

- Redesigning each page's own H1/subtitle/button styling into one visual
  standard (font sizes, colors currently vary: `text-lg` vs `text-2xl`,
  indigo vs rose vs purple buttons) — only the navigation block is unified
  here; a follow-up visual-consistency pass is a separate decision.
- Surfacing the Model GUID / Verify Endpoint (currently only on the main
  edit form) inside the shared tab shell for quick access from any sub-page.
- True single-LiveView instant-switch tabs (Option 2 from the original
  assessment) — deliberately not pursued; would require merging ~10
  independent CRUD/editor domains into one module.
