Skip to content

Phase 11 — Showcase

Defined 2026-08-24 at the goal reset (plan.md). Serves Goal C: the three showcase-script.md moments get their "wow layer". UI-led — the first phase split primarily in the climate-lama-ui repo — with a small set of backbone support features.

Ground truth (verified 2026-08-24): v1 of every moment already exists (/lookup, /portfolios/:id, /matrix + /agreement + /compare); most upgrades are UI wiring over API parameters the backbone already accepts.

Status — 2026-08-27: complete, all 11 target steps walked

The backbone capability wave landed 2026-08-26 (#1008, #1009, #1010). The UI wave — 12 issues, ui#218–ui#229 — was split and landed 2026-08-27, followed by the stack + spec work of ui#254 and four fixes that only a real walk could surface (ui#248, ui#249, ui#252, ui#258) plus one backbone fix (#1057/#1058).

Exit criterion 1 is met. All 11 target: P11 steps in showcase-script.md now execute against a compose stack on UI trunk 73413cf + backbone trunk 4bfd768: ci.yml green, e2e-smoke 25 passed / 0 failed / 2 skipped (run 33064354961).

step issue evidence (smoke run 33064354961)
M1 s4 — scenario/horizon on the lookup ui#218 + ui#219 lookup-axis.spec.ts:19
M1 s5 — risk over time across horizons ui#220 lookup-axis.spec.ts:57
M2 s2 — the book's assets on a map ui#225 portfolio-map.spec.ts:34
M2 s4 — scenario/horizon on the rollup ui#221 portfolio.spec.ts step 5
M2 s5 — monetary loss computation ui#228 + #1008 portfolio.spec.ts steps 6-7
M2 s6 — real portfolio-list metrics ui#226 + #1010 portfolio.spec.ts step 3
M2 s7 — download a portfolio report ui#222 portfolio.spec.ts step 8
M3 s2 — heatmap grid + cell drill-in ui#224 scenario-matrix.spec.ts:123
M3 s3 — save a batch and recall it ui#223 + #1009 scenario-matrix.spec.ts:123
M3 s4 — per-result CSV/PDF export ui#222 scenario-matrix.spec.ts:240
M3 s5 — /compare nav entry ui#227 scenario-matrix.spec.ts:110

Neither remaining smoke skip is a Phase 11 step: compare.spec.ts:5 needs ≥2 saved scenarios and settings.spec.ts:27 needs a non-English locale. Both predate the phase and are unrelated to its markers.

What the first real walk cost — five defects, only two of them the phase's

Giving these moments an executable walk for the first time is what found all five. Two belong to Phase 11; three predate it.

  • ui#249 — predates the phase, and was the severe one. Backbone migration 0046 renamed the role admin → org_admin in June 2026 (issue #271) and moved cross-org authority to is_platform_admin; the UI never followed. The DB's ck_users_role constraint means role = 'admin' cannot exist, so every org_admin ranked below viewer — unable to create a portfolio, save a scenario or edit portfolio detail — and RequireAdmin gated /admin on a role nobody could hold, making the admin console unreachable by anyone. It stayed invisible because every unit test mocked the dead 'admin' string.
  • ui#248 — a real Phase 11 regression. ui#219 wired the shared control in with a hardcoded ssp2-4.5/2050 and sent it on first load, where the lookup had previously sent nothing and let the backbone choose. Against a catalog without that combination the score card returned only coverage-gap misses. Also fixed a stale matrix selector that collided with ui#224's per-cell buttons.
  • ui#252 — fallout from fixing ui#248. With the forced default removed the backbone resolves its no-horizon surface and honestly echoes horizon_year: 0 (NO_HORIZON = 0); the axis control had no option for it and rendered a bare 0. The baseline is now a labelled first-class option.
  • #1057/#1058 — predates the phase, backbone. The compute preflight asked "is anything assigned to this hazard?" whenever no exposure_dataset_id was given, so a portfolio-addressed job read another exposure dataset's junction rows as its own, skipped centroid assignment and then failed with "No exposure rows could be paired with this hazard dataset". Any book uploaded against an already-computed hazard hit it — the moment-2 compute could never have worked on a used stack. The probe now mirrors the row set compute_order_sql(scope="portfolio") loads.
  • ui#258 — predates the phase. PortfolioComputePanel had no state between selecting a hazard and the impact-function fetch settling, so "No impact functions registered" rendered over a request still in flight. Beyond the flash on every visit, portfolio.spec.ts guards moment 2 on that exact string: two smoke runs over a byte-identical UI tree disagreed, one walking the compute and one skipping it. The verdict is now withheld until the fetch settles.

Guardrail check (exit criterion 2) passes: no persona word appears anywhere in the Phase 11 diff, and every route is reachable from the nav — /dashboard is a deliberate redirect to /, and /admin became reachable again via ui#249.

Design constraint — one capability layer, no persona products

Owner decision, 2026-08-26. climate-lama-ui is a showcase of the platform's capabilities, not a product cut for one audience. Whether it later becomes the single client platform with role-based show/hide, or is split into per-client apps with this one left as the showcase, is a parked cross-cutting decision — and Phase 11 must not prejudge it. Every surface stays reachable and useful to every logged-in user; no user type is steered into its own area of the app.

Verified in the UI on 2026-08-26 — this is the state to preserve, not to build:

  • Flat router. /lookup, /datasets, /portfolios, /scenarios, /measures, /matrix, /agreement, /compare, /settings all sit at the same level behind one auth wrapper (src/App.tsx).
  • Flat nav. Every link renders for every logged-in user.
  • Exactly one gate in the whole app, and it is a permission, not an audience: hasRole('admin') (admin nav link + RequireAdmin, portfolio delete, org settings). A grep for persona returns nothing.

Guardrails — apply to every Phase 11 issue

  1. No persona word in the artifact. Not in a route, component, nav label, API path, test name, or issue title. Personas live in showcase-script.md as walk narration only.
  2. Controls bind to objects, not pages. A scenario/horizon control takes a query; it does not know which surface hosts it.
  3. Second occurrence is wiring, never a rebuild. If a capability appears in two moments, the second issue must name the component or endpoint it reuses.
  4. Every route gets a nav link. The UI has no feature-flag layer, so a landed route with no nav entry is invisible — /compare is exactly that today, and it is why fixing it is in scope.
  5. admin stays the only gate. A second gate needs an ADR, not an issue.

A capability layer on a flat surface can be carved into per-client products later. A persona-carved app cannot be un-carved — which is why the guardrails bind now, while the parked decision is still open.

Investment rule

Surfaces may be dropped and others added before the client-UI decision is made. Prefer work whose value survives deleting the surface it was built on:

Durability Kind of work Posture
Survives a UI rewrite backbone capability, built generically do first
Survives a surface being dropped shared component; wiring an existing capability into more surfaces do next — cheapest visible progress per unit of work
Dies with its surface surface-local presentation (chrome, layout, per-page metrics) timebox, do last

Work — capabilities first, then surfaces

The same scope as the moment-by-moment reading in showcase-script.md, grouped by what is being built rather than by who the walk narrates it to. Roughly two-thirds of the original persona-grouped list was cross-cutting capability wearing an audience label.

A. Cross-cutting capabilities — build once, wire everywhere

Capability Shape Serves Note
Scenario/horizon control one component bound to a query M1 s4, M2 s4, and the matrix axis appeared three times in the persona reading — build once, or pay 3× for three behaviours
Metric-across-horizons chart series in, chart out M1 s5 equally valid for an asset, a portfolio, a matrix cell — do not scope it to the pin
URL state / permalink pattern landed on /lookup (ui#179) M1 s6 (live) generalize to portfolio detail, matrix, compare
Raster overlay Dashboard LayerPanel pattern M1 s3 (live, ui#205) reuse; a second overlay implementation is a defect, not a feature
Compute addressed by a portfolio backbone — the phase's one new capability. A portfolio is an exposure selector: same job, same result object, listed in the same /scenarios. Not a portfolio-shaped endpoint returning a portfolio-shaped result M2 s5 the phase's most consequential design decision. Built generically, a matrix run over a portfolio comes free and a single pin is the n=1 case; built as a portfolio silo, it is expensive to undo in the backbone
Result export (CSV/PDF) ExportButton exists, wired only into the parked Wizard M3 s4 highest reach-to-effort ratio in the phase — wire it into every result surface
Report download the composed report is already live on M3 s4 M2 s7 reuse that export path. Do not build a second, portfolio-specific report
Saved / recallable objects /scenarios (MyScenariosPage) already is the saved-things surface M3 s3 extend it to matrix batches; a separate batch-save mechanism is a silo

B. Surface-local presentation — fine to build per surface

Item Surface Serves
scenario × horizon heatmap grid + per-cell drill-in /matrix M3 s2
portfolio asset map (uses the shared map, not a new one) /portfolios/:id M2 s2
real list metrics replacing the em-dash placeholders (UI + possibly a list-with-metrics endpoint) /portfolios M2 s6
/compare nav entry nav M3 s5 — guardrail 4; near-free, and it un-hides a surface that is reachable only by selection today

C. Already landed — the /lookup wave

Split as "Phase 9b -- Risk Lookup revamp" (2026-08-24): ui#175–ui#187, label milestone:phase-9b, split concurrently with the goal reset. The name predates the goal reset; the label contract is kept as-is for /phase-build. Don't re-split it.

Wave landed and walked (2026-08-26). All of ui#175–ui#187 plus ui#188/#193/#205/#208/#211 are merged and live in UI v0.8.0; the owner's walk flipped Moment 1 steps 3 and 6 to live (#971). Backbone deps #964 and #965 closed with it — both were real API gaps that only surfaced because UI work pulled on them, which is the showcase working as an acceptance test on the backbone.

What it still owes: only Moment 1 steps 4 and 5 — the scenario/horizon toggle (the chips are read-only today) and the risk-over-time chart. Both are table-A capabilities, not lookup features.

On epic #367

#367 ("insurer portfolio workbench") is a product-wave-3 artifact of the parked commercial track, and its body predates Phase 9's close. Phase 11 builds the capability in table A and the surfaces in table B; it does not build "the insurer workbench", and #367 stays parked rather than becoming this phase's parent — folding it in as-is would import exactly the persona framing guardrail 1 forbids.

Prerequisites (before the first UI wave)

  • ~~Phase 10 exit (the walk exists; UI verifies against real bootstrap, not demo scaffolding).~~ Met 2026-08-26 — Phase 10 closed; the UI's own smoke stack provisions real ingested data since ui#212.
  • UI verification posture: a debounced UI trunk gate per wave + the same-files-isolated vitest rule pre-merge (the UI repo currently has no merge-time gate at all), and an on-demand publish.yml dispatch producing a sha-tagged image so merged UI work is walkable via UI_TAG=sha-<short> in compose without cutting a release.
  • Per-moment smoke coverage. Moment 1 gained an e2e smoke that provisions real data (ui#211/#212); Moments 2 and 3 have none. Each workstream's exit adds its moment to e2e-smoke.yml the same way. Exiting a moment on "walked once by the owner" is the Phase 8 waived-walkthrough pattern that cost four carried defects.

Exit criteria

  1. Each moment's Phase-11 target steps in showcase-script.md flip to live, demonstrated by walking the script against a compose stack (backbone trunk + sha-tagged UI). Met 2026-08-27 — 11 of 11 (see Status above), UI trunk 73413cf + backbone trunk 4bfd768, smoke run 33064354961.
  2. No landed issue violates the guardrails above: no persona in any route, component, nav label, API path or issue title; admin still the only gate; every route reachable from the nav.