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
0046renamed the roleadmin→org_adminin June 2026 (issue #271) and moved cross-org authority tois_platform_admin; the UI never followed. The DB'sck_users_roleconstraint meansrole = 'admin'cannot exist, so everyorg_adminranked belowviewer— unable to create a portfolio, save a scenario or edit portfolio detail — andRequireAdmingated/adminon 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/2050and 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 bare0. 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_idwas 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 setcompute_order_sql(scope="portfolio")loads. - ui#258 — predates the phase.
PortfolioComputePanelhad 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.tsguards 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,/settingsall 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 forpersonareturns nothing.
Guardrails — apply to every Phase 11 issue¶
- 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.
- Controls bind to objects, not pages. A scenario/horizon control takes a query; it does not know which surface hosts it.
- 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.
- Every route gets a nav link. The UI has no feature-flag layer, so a
landed route with no nav entry is invisible —
/compareis exactly that today, and it is why fixing it is in scope. adminstays 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 tolive(#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.ymldispatch producing a sha-tagged image so merged UI work is walkable viaUI_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.ymlthe 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¶
- 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 trunk4bfd768, smoke run 33064354961. - No landed issue violates the guardrails above: no persona in any route,
component, nav label, API path or issue title;
adminstill the only gate; every route reachable from the nav.