seriffic's picture
docs: replace handoff bundle with v0.4.5 spec
79cf005
# Handoff: Riprap Findings Region
**Current target version: v0.4.5** (polish on v0.4.4 — now applied in `Riprap Stone-Grouped UI v0.4.5.html`). See `V0.4.5_SPEC.md` for the nine deltas. This README is the v0.4.4 specification and remains the reference for everything *not* changing in v0.4.5.
**Read order for the implementer:**
1. `CLAUDE_CODE_PROMPT.md` — paste into Claude Code
2. `V0.4.5_SPEC.md` — the nine fixes (now realized in `v0.4.5.html`)
3. `README.md` (this file) — v0.4.4 reference
4. `design_files/` — prototypes (both v0.4.4 and v0.4.5 HTMLs are present)
## Overview
**Riprap** is a citation-grounded Flood Exposure Briefing tool for New York City. A user enters an address, neighborhood, or proposed development; Riprap returns a written briefing where every numeric claim links to its primary public-record source (FEMA, USGS, NYC DOITT, FloodNet, NYC OpenData, etc.).
This handoff covers the **Findings region** — the structured-data sibling of the briefing prose. It groups model outputs into five named "Stones" (cognitive roles), each rendered as a card stack with explicit epistemic tiering, smart provenance traces, and cross-linking to the map.
The target codebase is **SvelteKit** (Svelte 5 with runes). The files in `design_files/` are React-based prototypes — design references, not production code. Recreate them as Svelte components.
## About the Design Files
The files in `design_files/` are **design references created in HTML + React** — high-fidelity prototypes showing the intended look and behavior. They are **not production code to copy directly**.
Your task is to **recreate these designs in the existing Svelte codebase**, using its established patterns (Svelte 5 runes, scoped styles, the project's existing route structure and data layer). Lift visual values, copy, and interaction logic from the references; do not transpile JSX to Svelte.
**File pairing** (each prototype area has one or two source files):
- `Riprap Stone-Grouped UI v0.4.4.html` — main prototype, the v0.4.4 Findings region in context
- `findings.jsx` — Findings region: stones, cards, run-health, grammar reference (the centerpiece of this handoff)
- `briefing.jsx` — long-form briefing prose with inline citations
- `map.jsx` — mini-map with FEMA AE / HWM / FloodNet / 311 / address layers and link highlighting
- `trace.jsx`, `stones-trace.jsx`, `stone-evidence.jsx` — provenance trace variants
- `shell.jsx` — app header, footer, cold-start state
- `glyphs.jsx` — four tier glyphs as inline SVG
- `tokens.css` — design tokens (colors, type, spacing). Port verbatim.
- `styles.css` — component CSS. Reference only; rewrite as scoped Svelte styles.
- `tweaks-panel.jsx`, `design-canvas.jsx`, `landing-variants.*` — prototype-time tooling. Ignore.
## Fidelity
**High-fidelity (hifi).** Pixel-perfect mockups with final colors, typography, spacing, glyphs, and interactions. Recreate the UI pixel-perfectly using the codebase's existing libraries and patterns. The tier color values, the IBM Plex font stack, the 4/8/12/16/24/32/48/64/96 spacing scale, and the four-tier glyph system are all final.
The Findings region in particular has been through several iterations (v0.4.0 → v0.4.4) and is settled. Don't redesign card layouts; port them as-is.
## Screens / Views
The product is a single-page app with two states: **cold-start** (no query) and **briefing** (query active).
### 1. Cold-start
Empty state with the wordmark, a deck explaining what Riprap is, a query input, three sample-query buttons, and a "How Riprap is built" trust band. See `shell.jsx``<ColdStart>`.
- **Layout**: centered single column, max-width ~720px, paper background (`--paper`)
- **Wordmark**: `riprap` lowercase, IBM Plex Mono 14px / 600, with a 0.85em accent bar `▌` prefix
- **Deck**: serif paragraph, 18px, line-height 1.55, ink-secondary
- **Query input**: full width, 1px ink border, mono placeholder, 16px
- **Submit**: ink fill, paper text, mono caps, 13px
- **Sample queries**: three buttons in a column, each shows mode (caps mono) / query (sans 16px) / sub (mono 12px tertiary)
- **Trust band**: section-label heading, italic-serif "Cornerstone remembers. Keystone tallies. Touchstone watches. Lodestone projects. Capstone writes it all down with citations." then a bullet list
### 2. Briefing (active query)
Three-region layout, vertical stack:
1. **App header** (sticky top): wordmark · context · query pill · methodology / export PDF / live status
2. **Briefing prose region**: long-form text with inline `[N]` citations, three side-by-side panes (excerpt · evidence cards · mini map)
3. **Findings region**: the v0.4.4 Stone-grouped card stack (this handoff's focus)
4. **Footer**: tier legend + build line
The Findings region is the substantial new surface. Everything below documents it.
## Findings Region (v0.4.4) — detailed spec
### Composition
```
<FindingsRegion>
<RunHealthStrip /> ← 1 row, top, summarizes all 25 model calls
<StoneRegion stone="cornerstone" />
<StoneRegion stone="keystone" />
<StoneRegion stone="touchstone" />
<StoneRegion stone="lodestone" />
<StoneRegion stone="capstone" />
<CardGrammarReference /> ← optional, on by default; one stub per variant
</FindingsRegion>
```
### Stones
Five fixed roles, in order:
| key | name | role | tag |
|---|---|---|---|
| `cornerstone` | Cornerstone | the hazard reader | what NYC's ground remembers |
| `keystone` | Keystone | the asset register | what's exposed |
| `touchstone` | Touchstone | the present-tense witness | what's happening now |
| `lodestone` | Lodestone | the future-pointer | what's coming |
| `capstone` | Capstone | the writer | what we say, with citations |
Each `<StoneRegion>` has:
- **Header**: Stone name (IBM Plex Serif 26px italic for the name, sans for the role) · role tagline · `<StoneTally>` chip showing `N/M cards fired` · provenance toggle button
- **Provenance trace**: smart-default expansion (see below). Renders specialist tree with status pips.
- **Card grid**: 12-column grid, each card spans 4 cols by default (3 per row); register/timeseries/raster cards may span 6.
### Card data schema
```ts
type Card = {
stone: "cornerstone" | "keystone" | "touchstone" | "lodestone" | "capstone";
tier: "empirical" | "modeled" | "proxy" | "synthetic";
variant: CardVariant; // see below
source: string; // short label, e.g. "FEMA"
agency: string; // long form, e.g. "FEMA preliminary FIRM, panel 36047C..."
vintage: string; // e.g. "2024-Q3" or "2007–present"
title: string; // card title
// variant-specific fields:
headline?: string; subhead?: string;
columns?: string[]; rows?: (string | number)[][];
scalars?: { label: string; value: string; unit?: string }[];
spark?: number[]; histogram?: number[];
timeseries?: { hours: number[]; values: number[]; threshold?: number };
forecast?: { years: number[]; p10: number[]; p50: number[]; p90: number[] };
raster?: "stormwater" | "fema-ae" | "hwm" | "floodnet-density" | ...;
register?: { tag: string; label: string; sourceId: string; detail: string }[];
comparison?: { left: ScalarSet; right: ScalarSet; delta: string };
meta?: Record<string, string>;
// citation fan-out:
cites?: { id: string; label: string; href?: string }[];
// map link:
mapLayer?: "fema-ae" | "hwm" | "floodnet" | "nycha" | "address" | ...;
};
```
See `findings.jsx` lines 12–230 for the canonical `CARDS` table populated for the Red Hook query.
### Card grammar (12 variants)
Every card uses the same chrome — title row (sans 14/600), source · agency · vintage row (mono 11/tertiary), a body block, and a footer with the **tier badge** (3-letter caps mono) and a "cite" button. The body block is one of:
| variant | shape | use |
|---|---|---|
| `headline` | one big number/label, scenario-tagged subhead | single-fact cards: "Zone AE" |
| `tabular` | small N×3 table, mono | observation lists: HWM marks |
| `scalars` | 2–3 labeled scalars in a row | "1.2 m · 0.18 mi · 2012" |
| `spark` | 60×24 inline sparkline, no axes | trend at a glance |
| `histogram` | 8–12 bar histogram, mono labels | distributions |
| `timeseries` | 240×84 line chart with threshold rule | hourly water level, 311 calls |
| `forecast` | 240×88 fan chart (p10/p50/p90) | 2050/2080 SLR, surge |
| `raster` | 240×120 stylized raster thumbnail | FEMA AE polygon, stormwater extent, HWM contour |
| `raster-pred` | same shape with dashed top-rule (synthetic tier) | TerraMind 2050 prediction |
| `register` | 3-col dense list (tag · label · sourceId), detail in `title=` tooltip | NYCHA buildings, schools |
| `comparison` | side-by-side scalar columns with delta | FEMA-AE vs Prithvi-2050 |
| `meta` | definition list of run metadata | model id, prompt hash, latency |
**Synthetic tier** cards (TerraMind predictions, prior-only Lodestone outputs) get a **dashed top-rule** (1px dashed `--tier-synthetic-line`) to telegraph "no observed data here." Comparison cards always render synthetic.
The `<CardGrammarReference>` region renders one stub per variant in `findings.jsx` lines 529–581 — a visual catalog the design team uses to spot-check fidelity. Keep it, gate it on `showGrammar` prop (default true in dev, false in prod).
### Tier system
Four epistemic tiers, encoded redundantly:
| tier | color | glyph | badge | meaning |
|---|---|---|---|---|
| `empirical` | `#0B5394` (8.59:1) | filled square | EMP | observed, ground-truth |
| `modeled` | `#2A6FA8` (5.41:1) | open square | MOD | computed from observations |
| `proxy` | `#6B6B6B` (5.74:1) | dotted ring | PRX | indirect signal, e.g. 311 calls |
| `synthetic` | `#2A6FA8` + dashed | hatched square | SYN | model prior, no observation |
Glyphs are inline SVG, 12×12, black-stroke. See `glyphs.jsx` for the four shapes.
**Accessibility**: tier is *always* encoded by color + glyph + label, never color alone. Modeled and synthetic share a hue; the dashed top-rule and glyph carry the difference.
### Provenance trace
Every Stone has a tree of specialists ("CORN-001: pull FEMA NFHL → CORN-002: parse panel index → ..."). Each specialist has a status: `ok` / `warn` / `error` / `silent`.
**Smart-default rule** (`provenanceMode = "smart"`):
- All-`ok` Stone → **collapsed** by default, single-line summary "12/12 specialists fired clean"
- Any `warn` or `error`**expanded**, full tree visible
- All `silent` → collapsed, single-line "no firings (section omitted from briefing)"
`provenanceMode = "expanded"` forces all expanded; `"collapsed"` forces all collapsed (warn/error still get a count chip).
The trace UI: indented tree, mono ids, status pip (●/▲/■/○) in tier-color or warn/error colors. Specialist names italic-serif. Hovering a specialist row dims the rest. See `trace.jsx` for the leaf and `stones-trace.jsx` for the per-Stone composition.
### Run-health strip
Single row above the Stones, full-width. Shows:
- Total specialists fired / total specialists registered (e.g. `83/100`)
- Per-tier breakdown as four chips: `EMP 41 · MOD 28 · PRX 12 · SYN 2`
- Total runtime (e.g. `3.4s`)
- Cache-hit ratio (e.g. `92%`)
Mono throughout. Background `--paper-deep`, 1px ink-soft top + bottom rule.
### Hover linking
Every card with a `mapLayer` is hoverable. On hover (or focus), the card's `key` becomes the page-level `linkedKey`. The briefing's map frame reads `linkedKey` and:
- Adds `is-link-{layer}` class to its root, which lights up that layer (see `map.jsx` for the CSS rules)
- Renders a small label badge bottom-right: "linked: {layer}"
The same applies in reverse: hovering a layer in the map sets `linkedKey` to the corresponding card key, which gets a 2px accent outline.
In Svelte: lift `linkedKey` to `+page.svelte` as `let linkedKey = $state(null)`, pass it down both branches, and have the cards / layers update it on `pointerenter` / `focus` / `pointerleave` / `blur`.
### Density
`density: "compact" | "comfortable"` (default `comfortable`).
- **Comfortable**: 16px card padding, 14px line-height multiplier 1.4
- **Compact**: 10px card padding, 12px line-height multiplier 1.25, register-card row height 18px (vs 24px)
Pass through to all card bodies; only register/tabular/meta visibly change.
## Interactions & Behavior
- **Card hover**: 200ms `background-color` transition to `--paper-deep`, 2px accent outline if linked
- **Cite button**: opens a small popover with the full citation list (`cites[]`), each row a link to the source PDF/page
- **Provenance toggle**: button with `aria-expanded`, animates the tree open/closed via `details`/`summary` or scoped `max-height` transition (≤200ms)
- **Map layer hover**: sets `linkedKey`, 100ms layer fill opacity transition
- **Reduced motion**: all transitions become 0.01ms via the global rule in `tokens.css`. Don't add motion on top.
- **Keyboard**: every card is `tabindex=0` with `aria-label="{tier} card · {title} · {source}"`. Cite button is real `<button>`. Provenance toggle is real `<button>`.
- **Loading**: cards show a 1px ink-soft skeleton (no spinner); replace with content when the specialist returns
- **Error / silent**: cards with status `error` render a 1-line "specialist failed: {reason}" in tier-error color and stay; cards with status `silent` are omitted entirely (silence over confabulation — design tenet)
## State Management
Svelte 5 runes; lift to `+page.svelte`:
```svelte
<script>
let query = $state(null);
let density = $state("comfortable");
let provenanceMode = $state("smart");
let showComparison = $state(false);
let showGrammar = $state(false); // dev-only toggle
let linkedKey = $state(null);
// Data: load once per query, hydrate from server
let cardsByStone = $derived(loadCards(query));
let runHealth = $derived(summarize(cardsByStone));
</script>
```
Children take props via `$props()`. No Svelte stores unless cross-route.
Data fetching: assume the existing codebase has a `+page.server.ts` load function that runs the 25 specialists and returns the `Card[]` payload. This handoff doesn't change the data layer; it changes the rendering.
## Design Tokens
Port `tokens.css` verbatim. Key values:
**Tier colors** (all WCAG AA on white):
- `--tier-empirical: #0B5394` (8.59:1)
- `--tier-modeled: #2A6FA8` (5.41:1)
- `--tier-proxy: #6B6B6B` (5.74:1)
- `--tier-synthetic: #2A6FA8` (pattern-differentiated)
**Neutrals**:
- `--paper: #FAFAF7` (warm near-white, USGS-report register)
- `--paper-deep: #F2F2EE`
- `--ink: #1A1A1A` · `--ink-secondary: #4A4A4A` · `--ink-tertiary: #6B6B6B`
- `--rule: #1A1A1A` · `--rule-soft: #C9C9C5`
**Accent**:
- `--accent: #B8620A` (for text, AA)
- `--accent-graphical: #D17C00` (for shapes/lines, ≥3:1)
**Type**:
- `--font-sans: "IBM Plex Sans"` (UI, body)
- `--font-serif: "IBM Plex Serif"` (Stone names, hero italic emphasis, oversized stone numerals)
- `--font-mono: "IBM Plex Mono"` (labels, source ids, badges, table cells)
All three are SIL OFL / Apache; self-host or load from Google Fonts. **No system fallbacks for branding** — always specify the Plex stack, fall through only on load failure.
**Spacing** (`--s-1` through `--s-9`): 4 / 8 / 12 / 16 / 24 / 32 / 48 / 64 / 96 px. Use these tokens; don't hand-roll values.
**Type scale** (suggested, not enforced):
- 11px mono labels (`section-label`)
- 12–13px mono row text
- 14px sans card titles (600)
- 16px sans body
- 18px sans deck text
- 26px serif italic Stone names
- 36–52px serif headlines (italic for emphasis)
**Radius**: 0 throughout (this is a civic-tech-clean, USGS-report register; no rounded corners except `1px` on focus rings).
**Shadows**: none. Differentiation by 1px rules and `--paper-deep` fills only.
## Assets
- **Fonts**: IBM Plex Sans / Serif / Mono. Self-host woff2 or Google Fonts.
- **Wordmark**: text + accent bar `▌` prefix, no logo file
- **Tier glyphs**: inline SVG, see `glyphs.jsx`
- **Map raster thumbnails**: hand-drawn SVG approximations using each layer's conventional palette. See `findings.jsx``RasterThumb`. Replace with real raster previews if/when MapLibre tile snapshots are wired up.
- **Real map**: the production map should use **MapLibre GL** with a custom `style.json` that respects the tier palette. Style fragments are sketched in `shell.jsx` comments.
- **No icon library.** No Lucide, no Heroicons. SVG glyphs for tiers, mono characters (⌕, ↗, ▌) for chrome.
- **No emoji.**
## Files in this bundle
```
design_handoff_riprap_findings/
├── CLAUDE_CODE_PROMPT.md ← paste this into Claude Code first
├── README.md ← you are here
└── design_files/
├── Riprap Stone-Grouped UI v0.4.4.html ← main prototype, open in browser
├── Riprap Landing.html
├── Riprap Landing Variants.html
├── tokens.css ← port verbatim
├── styles.css ← reference only
├── findings.jsx ← Findings region (this handoff's centerpiece)
├── briefing.jsx ← long-form prose
├── evidence.jsx ← evidence card stack used in briefing
├── map.jsx ← mini-map with link highlighting
├── trace.jsx
├── stones-trace.jsx
├── stone-evidence.jsx
├── shell.jsx ← header, footer, cold-start
├── glyphs.jsx ← four tier glyphs
├── tweaks-panel.jsx ← prototype tooling, ignore
├── design-canvas.jsx ← prototype tooling, ignore
├── landing-variants.css ← marketing-page exploration, ignore unless asked
└── landing-variants.jsx ← marketing-page exploration, ignore unless asked
```
## Scope
**In scope** for this handoff:
- The Findings region (5 Stones, 12 card variants, run-health strip, smart provenance, hover linking, card grammar reference)
- Briefing prose and map, to the extent they connect to Findings via `linkedKey`
- The marketing **landing page** (see §"Landing page" below)
- v0.4.5 deltas in `V0.4.5_SPEC.md`
**Out of scope**: the methodology PDF, the export-PDF flow.
## Landing page
The landing page is the public-facing entry point at `/` (separate from the app's cold-start state at `/app`). Two design files in `design_files/`:
- `Riprap Landing.html` — final shipping landing page
- `Riprap Landing Variants.html` — three exploratory variants on a design canvas (kept for reference; do not port)
### Landing structure (port `Riprap Landing.html`)
Four-section vertical scroll, max-width 1200px, paper background:
1. **Hero**
- Wordmark top-left (with `▌` accent prefix)
- Headline (52px serif, italic emphasis on *any place*, line-broken into two lines)
- Deck (18px sans, max 70ch, ink-secondary)
- Big query box: "Try:" label + cycling example queries on a fixed dotted-underline rail (rail width pinned, ellipsis fallback for overflow)
- Submit button (ink fill, paper text, mono caps)
2. **"What you'll get back" preview** — 3-pane grid (1.4fr / 1fr / 1fr), bottoms equalized:
- **Excerpt pane**: serif briefing snippet with inline `[N]` citation pins; compact source list at bottom (tier-coded)
- **Evidence cards pane**: 2x2 grid of compact evidence cards, each with tier-coded left rule (2px), claim, source
- **Map pane**: mini SVG map (240x200, 8px paper-grain grid texture) with FEMA-AE polygon fill, HWM contour, FloodNet sensor pin, 311 cluster, address pin, and a compact tier-legend overlay bottom-edge
3. **Five Stones strip** — explanation grid: 5 columns (`repeat(5, 1fr)`), one cell per Stone:
- Oversized italic-serif numerals `01..05` top-right of each cell (in `--rule-soft`, decorative)
- Stone name (serif 22px / 500)
- Role tagline (sans 13px / ink-secondary)
- Italic-serif tag ("what NYC's ground remembers" etc.)
- Dashed rule + mono source list at bottom
4. **Footer** — earns its keep:
- Tier legend (4 swatches: empirical / modeled / proxy / synthetic)
- Build line (mono, ink-tertiary)
### Landing — what's settled (don't redesign)
- The hero dropped the redundant "Riprap" eyebrow; the wordmark already says it. Headline carries the lede.
- Cycling examples ride a single fixed dotted underline (no jump on length change). Pin the rail width; ellipsis-truncate overflow.
- Pane heights are equalized via a flex-1 spacer in the cards pane and `flex: 1` on the map pane.
- Map texture is a subtle 8px paper-grain grid behind the colored layers — gives it a map register, not a diagram register.
- Stones strip uses oversized italic numerals as a typographic register (decorative, in soft-rule color).
- Italic serif is intentional: hero emphasis + Stone tags + Stone numerals. Reads as a deliberate third voice.
### Landing — em-dashes
All user-facing em-dashes have been purged from `Riprap Landing.html`, `landing-variants.css`, and `landing-variants.jsx`. Replacement convention: `, ` (comma-space) for em-dashes used as parenthetical breaks. **Maintain this convention** when porting copy. Em-dashes in source-code comments (non-rendered) are fine.
### Landing — out of scope variants
`Riprap Landing Variants.html` contains three earlier explorations on a design canvas: v1 (minimal pushed harder), v2 (example gallery), v3 (methodology-forward). Kept for reference only; **do not port**. The shipping landing page is `Riprap Landing.html` (a refinement of v1).
## Open questions for the design team
These are deliberately *not* resolved in the prototype; raise them with the designer before locking implementation:
1. Should `register` cards paginate when N > 20, or stay scrollable in a fixed-height card?
2. The `comparison` card is currently always synthetic (FEMA-AE vs Prithvi-2050). Will there be empirical-vs-empirical comparisons (e.g. FEMA-AE vs HWM observed)?
3. Run-health cache-hit ratio — is this surfaced to end users, or is it dev-mode only?
4. Provenance trace expansion: should expanded state survive across query changes, or reset?