# Cockpit Surfacing + Mobile Redesign — Design

**Date:** 2026-07-10
**Status:** Approved (brainstorming; visual mockup approved) — ready for implementation plan
**Sub-project:** the OBSERVABILITY layer of the "unified data-driven pipeline" north-star — make the pipeline's
decisions visible in the cockpit, and redesign the cockpit for navigation + mobile. The "verify" enabler
before the accumulated A/B1/B2a/B3/C1/C2 pipeline is trusted/deployed.
**Builds on:** A/B1/B2a/B3/C1/C2 (all merged). Reads their already-written tables; adds NO pipeline logic and
moves NO capital.
**Approved mockup:** the redesign was validated as a visual artifact (mobile overview + detail + desktop) with
real data before this spec.

## Goal

Two things at once, in the EXISTING server-rendered cockpit (`adapters/web/`, FastAPI + Jinja2 + HTMX):

1. **Surface the pipeline's decisions** (read-only) — fully integrated into the existing Overview + Detail
   pages (NO new page, NO JSON dumps): B2a **allocation** (per-edge target $, weight, gross-vs-$35k-ceiling),
   B3 **funding** health (funded / decaying / de-funded + why), and the C1/C2 **executed-vs-modeled**
   reconciliation (the executed twin nested under its modeled edge).
2. **Redesign for usability** — a mobile-first, thumb-reachable **bottom-tab navigation** on small screens
   (top bar on desktop), edge **cards** instead of wrapping tables on mobile, clearer hierarchy — a refinement
   of the existing GitHub-dark terminal aesthetic, not a re-skin.

Read-only, human-gated capital unchanged. Verified LOCALLY in a real browser before deploy.

## Locked design decisions (from the approved mockup)

- **Navigation:** sticky bottom tab bar (Overview · Paper · Replay · Backtest) below ~640px; the existing top
  nav on desktop. htmx `hx-boost` preserved.
- **Capital meter:** in the fund headline — `deployed $X / $35,000 ceiling` with a fill bar + "N funded · M
  de-funded".
- **Funding badge:** a semantic pill on every edge — `funded` (green `#3fb950`), `decaying N/10` (amber
  `#d29922`, a NEW token distinct from the neutral-grey WAIT), `de-funded` (red `#f85149`).
- **Executed twin:** nested UNDER its modeled edge (a sub-line / sub-row `executed +X% vs modeled +Y% · Δ Z% ·
  slip N bp`), never a separate top-level track.
- **Detail page** = the full per-edge lifecycle: status → forward → **funding** → **allocation** →
  **executed reality** → backtest → sleeves → daily rows.
- Aesthetic honored: dark-committed terminal, monospace, `tabular-nums`, existing palette + badge classes.

## Components

### 1. Read model (`DashboardService` + repo reads)

The dashboard reads three already-written tables. Add repo read methods (mirroring `current_allocation` /
`current_funding`, which return only partial data today):

- `ForwardNavRepo.all_allocations() -> list[StrategyAllocationRow]` (target_weight, target_dollars,
  policy_version, as_of) — `current_allocation()` returns only `{sid: dollars}`, insufficient for the UI.
- `ForwardNavRepo.all_funding() -> list[StrategyFundingRow]` (state, label, decay_run, recovery_run,
  first_pass_date, policy_version, as_of) — `current_funding()` returns only `{sid: state}`.

`DashboardService` gains pure read-model methods that JOIN these onto the existing `FleetRow` / `StrategyDetail`:

- **Allocation:** per-sid `{target_dollars, target_weight, policy_version}`; fund-level `gross_deployed =
  Σ target_dollars` and `ceiling` (from `ALLOCATION_POLICY.capital_ceiling`).
- **Funding:** per-sid `{state, label, decay_run, recovery_run, first_pass_date, policy_version}`.
- **Executed pairing:** for each MODELED sid that has an executed twin, resolve the twin via the REVERSE of
  C1/C2's `CockpitBacktestRefProvider._REF_ALIAS` (`{multistrat_exec: multistrat, bybit_4edge_exec:
  bybit_4edge}` → reverse `{multistrat: multistrat_exec, bybit_4edge: bybit_4edge_exec}`); read the twin's
  `forward_summary` (executed total_return, days) and compute `divergence = executed_total_return −
  modeled_total_return`. **Mean slippage** comes from `bybit_testnet_nav.mean_exec_slippage_bps` /
  `mean_timing_drift_bps` for the crypto twin only (Alpaca is commission-free → no slippage row; show "—").

The executed twins are `tier=research` observed tracks: on the Overview they are NOT rendered as their own
top-level rows — they are folded into their modeled parent's row/card. (They keep their own detail page.)

### 2. `base.html` — navigation + CSS refinement

- **Bottom tab nav:** add a `<nav class="tabs">` (Overview/Paper/Replay/Backtest) rendered as a sticky bottom
  bar under `@media(max-width:640px)`; hidden on desktop. The existing top `nav.bar` shows on desktop, hidden
  on mobile. Both `hx-boost`. The active tab is derived from `request.url.path` exactly as today.
- **CSS tokens:** add the `decaying` amber token + the funding/exec/meter/card classes from the mockup, into
  the existing inline `<style>`. Keep the existing badge classes (WAIT/GO/PASS/NO_GO/paper/live).
- **Responsive:** the existing `table.fleet → cards` mobile pattern is extended to carry the new allocation +
  funding fields (`data-label` cells). `overflow-x:auto` wrappers on any wide table.
- Accessibility: visible keyboard focus on tabs/links; `prefers-reduced-motion` respected (no essential motion).

### 3. `cockpit.html` (Overview)

- **Fund headline:** add the capital meter (`gross_deployed / ceiling` + fill bar + funded/de-funded count).
- **"All forward tracks" table → "every gate + capital + health at a glance":** add `allocated` (target $ ·
  weight, or `$0`/`—`) and `funding` (badge) columns. Under a modeled deploy edge with an executed twin,
  render a nested `exrow` sub-line (executed return, Δ, slippage). Mobile: each row → card (the new fields as
  labeled rows; the exec twin as a nested block).
- Grouping by tier (deploy/research) is unchanged.

### 4. `strategy.html` (Detail)

Add three lifecycle blocks (only when data exists), between the status header and the backtest verdict:

- **Funding health:** state badge + `first_pass_date`, `decay_run`/`recovery_run`, and a plain-language "why"
  (e.g. "would de-fund after 10 consecutive divergence days") + policy version.
- **Allocation:** target capital, book weight, ceiling, sizing note + policy version. A de-funded / $0 edge
  shows "$0 — de-funded/killswitched, held as cash".
- **Executed reality** (only for a modeled track WITH an executed twin): executed forward vs modeled forward,
  divergence, mean slippage (crypto) / "—" (Alpaca), a link to the exec track's own detail page.

### 5. Graceful degradation

Every new read is wrapped so a MISSING table/row/column (e.g. a not-yet-migrated DB, or an edge with no
allocation/funding/exec row) renders `—` or hides the block — NEVER a 500. This matches the overview's existing
discipline (`except Exception: return []`). Load-bearing because prod's schema/data lags until deploy + the
first nightly.

## Data flow

```
strategy_allocation ─┐
strategy_funding    ─┼─> ForwardNavRepo reads ─> DashboardService (pure joins) ─> Jinja2 (cockpit/strategy.html)
forward_summary(exec)┤                                                             + base.html nav/CSS
bybit_testnet_nav   ─┘                                                            (server-rendered, read-only)
```

## Error handling

- Missing table/column/row → `—` / hidden block, never 500 (wrap each new read).
- No executed twin for a track → no "executed" section (most tracks).
- No `bybit_testnet_nav` slippage row → slippage shows "—".
- Read-only: the dashboard NEVER writes (dev loop builds repos without `.migrate()`; prod migrates at boot).

## Testing

- **Unit (read model):** `DashboardService` allocation/funding/exec-pairing methods over a seeded in-memory
  sqlite repo — correct join, gross-vs-ceiling, divergence math, reverse-alias pairing, and graceful `—` when a
  table/row is absent.
- **Repo:** `all_allocations()` / `all_funding()` return the full rows; empty when absent.
- **Local browser verification (the load-bearing acceptance):** the current `dev-cockpit-local` loop is
  CONFOUNDED — it runs master's code against prod's 24-day-old schema (missing `forward_summary.policy_version`
  etc.), so it crashes. Verification instead runs the cockpit against a **local DB migrated to the current
  schema + seeded** with representative allocation/funding/exec/forward rows, driven by Playwright:
  screenshot the Overview (capital meter, funding badges, nested exec twin) + a Detail page (the three
  lifecycle blocks) at BOTH desktop and mobile widths, and READ THE NUMBERS (per
  feedback_verify_cockpit_locally_not_prod). A tiny `scripts/seed_cockpit_demo.py` (or a pytest fixture the
  verification reuses) seeds the local DB. This same loop re-runs post-deploy against real prod data.

## Out of scope (YAGNI)

- No new pipeline logic, no capital movement, no new nightly assets — pure display of written tables.
- No new top-level routes/pages — integrate into the existing Overview + Detail (and the existing
  Paper/Replay/Backtest tabs are untouched beyond the nav restyle).
- No cockpit surfacing of the Paper/Replay/Sim internals beyond the nav restyle.
- The DEPLOY itself (push + argo + migrate-forward-anchors) is a SEPARATE follow-up, gated on this being
  browser-verified locally.

## Affected code (initial map — the plan refines this)

- Modify: `src/fxhnt/adapters/persistence/forward_nav.py` — `all_allocations()`, `all_funding()` reads.
- Modify: `src/fxhnt/application/dashboard_service.py` — allocation/funding/exec-pairing read-model methods +
  join onto `FleetRow`/`StrategyDetail` (extend those DTOs).
- Modify: `src/fxhnt/adapters/web/templates/base.html` — bottom-tab nav + CSS tokens (decaying amber, meter,
  funding/exec/card classes) + responsive rules.
- Modify: `src/fxhnt/adapters/web/templates/cockpit.html` — capital meter, allocation/funding columns, nested
  executed twin.
- Modify: `src/fxhnt/adapters/web/templates/strategy.html` — funding / allocation / executed-reality blocks.
- New: `scripts/seed_cockpit_demo.py` (or a shared pytest fixture) — seed a local DB for browser verification.
- Reuse: `ALLOCATION_POLICY.capital_ceiling` (B2a), `CockpitBacktestRefProvider._REF_ALIAS` (C1/C2),
  `scripts/dev-cockpit-local.sh` pattern (repointed at the seeded local DB), `charts.py` sparklines.
```
