docs: replace handoff bundle with v0.4.5 spec
Browse filesBumps docs/design_handoff/ from v0.4.4 to v0.4.5. Drops in:
V0.4.5_SPEC.md β nine polish deltas on top of shipped v0.4.4:
1. Status semantics β split `error` into 5 states
(fired / silent_by_design / warned / errored / not_invoked)
2. Capstone meta-card field-mapping (rerolls, grounding pass
count, citations resolved, wall-clock)
3. Provenance roster: full inventory per Stone, not_invoked
slots rendered explicitly
4. Touchstone gains TerraMind LULC + Prithvi-NYC-Pluvial cards
5. Lodestone gains the fine-tuned TTM card alongside zero-shot
6. Drop the "anomaly" tag (redundant after #1)
7. LAYERS panel restructured by Stone
8. Card-to-map hover linking + fitBounds() on register click
9. Stone-tinted accent tokens (5 muted hint colors, 3px left-rule
on Stone headers, 6px dot in cold-start, print β #999)
CLAUDE_CODE_PROMPT.md β points at v0.4.5 spec; v0.4.4 README stays
as reference for everything not changing.
Riprap Stone-Grouped UI v0.4.5.html β the new visual target.
v0.4.4 HTML kept alongside for diff/reference only.
findings.jsx / stones-trace.jsx / map.jsx / shell.jsx / styles.css
/ tokens.css updated to match v0.4.5 deltas.
The implementation pass lands in subsequent commits on this branch.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- docs/design_handoff/CLAUDE_CODE_PROMPT.md +29 -25
- docs/design_handoff/README.md +67 -3
- docs/design_handoff/V0.4.5_SPEC.md +313 -0
- docs/design_handoff/design_files/Riprap Stone-Grouped UI v0.4.5.html +369 -0
- docs/design_handoff/design_files/findings.jsx +74 -39
- docs/design_handoff/design_files/map.jsx +40 -23
- docs/design_handoff/design_files/shell.jsx +5 -3
- docs/design_handoff/design_files/stones-trace.jsx +105 -50
- docs/design_handoff/design_files/styles.css +152 -0
- docs/design_handoff/design_files/tokens.css +20 -0
|
@@ -1,39 +1,43 @@
|
|
| 1 |
-
# Prompt for Claude Code
|
| 2 |
|
| 3 |
-
Copy-paste this whole block into Claude Code as your opening message.
|
| 4 |
|
| 5 |
---
|
| 6 |
|
| 7 |
-
|
| 8 |
|
| 9 |
-
|
| 10 |
|
| 11 |
-
**
|
| 12 |
|
| 13 |
-
|
|
|
|
|
|
|
| 14 |
|
| 15 |
-
|
| 16 |
|
| 17 |
-
|
|
|
|
|
|
|
|
|
|
| 18 |
|
| 19 |
-
|
| 20 |
|
| 21 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 22 |
|
| 23 |
-
|
| 24 |
|
| 25 |
-
|
|
|
|
|
|
|
|
|
|
| 26 |
|
| 27 |
-
|
| 28 |
-
- **No emoji.** No icon font. Tier glyphs are inline SVG; see `glyphs.jsx` for the four shapes (filled square / open square / dotted ring / hatched square).
|
| 29 |
-
- **No Tailwind.** This codebase uses scoped `<style>` blocks per component. Reuse the token CSS variables from step 1 β don't redeclare colors or spacing.
|
| 30 |
-
- **No animation libraries.** All transitions are CSS, β€200ms, and respect `prefers-reduced-motion` (already covered by the global rule in `tokens.css`).
|
| 31 |
-
- **Accessibility is a hard requirement.** Tier is encoded by *color + glyph + label* (never color alone). Focus rings are 3px accent. Every interactive card needs `aria-label`. Provenance toggles are buttons with `aria-expanded`.
|
| 32 |
-
|
| 33 |
-
**What to ignore in the bundle:**
|
| 34 |
-
|
| 35 |
-
- The `Riprap Landing*.html` files are exploratory marketing-page variants, not part of this handoff. Port them only if explicitly asked.
|
| 36 |
-
- `design-canvas.jsx`, `tweaks-panel.jsx`, `landing-variants.*` are prototype-time tooling, not product code.
|
| 37 |
-
- The React component files (`*.jsx`) are reference implementations. Use them to read structure, copy strings/numbers, and verify visual fidelity β don't transpile them.
|
| 38 |
-
|
| 39 |
-
**When in doubt, open the HTML file in a browser** (`Riprap Stone-Grouped UI v0.4.4.html`) and compare your Svelte output side-by-side. Pixel parity on the Findings region is the bar.
|
|
|
|
| 1 |
+
# Prompt for Claude Code Β· v0.4.5 polish session
|
| 2 |
|
| 3 |
+
Copy-paste this whole block into Claude Code as your opening message.
|
| 4 |
|
| 5 |
---
|
| 6 |
|
| 7 |
+
v0.4.4 has shipped to the local development environment. The Findings region with the Five Stones, evidence cards, and Capstone meta-card are live and rendering against real queries via local uvicorn (`uvicorn web.main:app --host 127.0.0.1 --port 7860`). Screenshots from 80 Pioneer Street, Red Hook confirm the architecture working: four Stones producing genuinely different evidence-card kinds, each carrying its tier badge, the briefing prose with citations resolving cleanly, and the map with three tier-encoded layers.
|
| 8 |
|
| 9 |
+
**v0.4.5 is polish.** Nine specific issues observed in production-shaped local runs, plus a new Stone-tinted light-theming layer. **None of the fixes are structural. Don't rebuild anything.** Read the current implementations of `src/lib/components/findings/StoneRegion.svelte`, `src/lib/components/findings/EvidenceCard.svelte`, and `src/lib/components/findings/CapstoneCard.svelte` and apply the deltas described in `V0.4.5_SPEC.md`.
|
| 10 |
|
| 11 |
+
**Important context.**
|
| 12 |
|
| 13 |
+
- Public mirrors (GitHub, HF Space) were deleted pre-hackathon for caution and re-publish during the hackathon window. **The HF Space link is currently delisted; do not reference it.** All references in this work target local development.
|
| 14 |
+
- The codebase is **SvelteKit (Svelte 5 with runes)**. Stay in idiomatic Svelte: `$state`, `$derived`, `$props`, scoped `<style>` blocks. No React. No Tailwind. No animation libraries.
|
| 15 |
+
- The card grammar (header / body / footer / tier badge / source link), the four-section briefing prose, the Mellea reroll status strip, the four-tier color palette and glyphs, the cold-start state, the trust-signal footer, and the PDF template's core layout are **not changing**. Don't touch them.
|
| 16 |
|
| 17 |
+
**Read these files first, in order:**
|
| 18 |
|
| 19 |
+
1. `V0.4.5_SPEC.md` β the nine fixes, with file-level deltas, expected outputs, and acceptance criteria.
|
| 20 |
+
2. `README.md` β v0.4.4 spec for context (card grammar, tier system, data schema, hover-link contract). Reference, not rework.
|
| 21 |
+
3. `design_files/Riprap Stone-Grouped UI v0.4.5.html` β open in browser; this is the **visual target** with all nine v0.4.5 deltas applied.
|
| 22 |
+
4. `design_files/Riprap Stone-Grouped UI v0.4.4.html` β kept only for diff/reference against the previously shipped state.
|
| 23 |
|
| 24 |
+
**Order of work** (matches `V0.4.5_SPEC.md` priority):
|
| 25 |
|
| 26 |
+
1. **Status semantics first.** Split the FSM specialist status into `fired / silent_by_design / warned / errored / not_invoked`. Update the per-Stone summary and top tally aggregate counts to render the breakdown. Update provenance row visual treatment per status. This is the most important fix β it's the one actively misrepresenting system integrity.
|
| 27 |
+
2. **Capstone meta-card field plumbing.** Wire the four metrics to the reconciler's actual state fields. Acceptance: a clean Red Hook run shows `1 reroll Β· 4/4 grounding Β· 4 citations Β· 24.0s`.
|
| 28 |
+
3. **Provenance roster completeness.** Each Stone's expander always shows the full specialist inventory, never a filtered subset. Missing specialists render as `not_invoked` with one-line reasons.
|
| 29 |
+
4. **Touchstone card additions** (TerraMind LULC, Prithvi-NYC-Pluvial). If the specialists aren't firing yet, land the card components anyway so they're ready when the data is.
|
| 30 |
+
5. **Lodestone fine-tuned TTM card.** Add alongside the existing zero-shot card. Footer must include the HF model-card link, RMSE, and AMD MI300X badge.
|
| 31 |
+
6. **Drop the "anomaly" tag.** The new status counts make it redundant.
|
| 32 |
+
7. **LAYERS panel restructure.** Group by Stone, add the four new raster layers (default-off), include the explicit "no map layers β see Findings cards" label under Lodestone.
|
| 33 |
+
8. **Card-to-map hover linking.** Verify and ship the connection. Click-to-fitBounds() on register cards is new in v0.4.5.
|
| 34 |
+
9. **Stone-tinted accent colors.** Add the five `--stone-*` tokens with the proposed values (designer can adjust within constraints). Apply at the recommended placements: 3px left-rule on Stone region headers, 6px dot beside Stone names in the cold-start list, optional row tint on the methodology matrix. Print-media override drops all five to `#999`.
|
| 35 |
|
| 36 |
+
**What to verify before reporting done.**
|
| 37 |
|
| 38 |
+
- A query at 80 Pioneer Street, Red Hook produces a Findings region matching the "v0.4.5 ready looks like" section at the bottom of `V0.4.5_SPEC.md`.
|
| 39 |
+
- No regressions in the briefing prose, the Mellea status strip, the cold-start, the footer, the PDF template, or the existing map layers.
|
| 40 |
+
- New status messages match the engineering-honest voice in `V0.4.5_SPEC.md` Β§1 (no euphemism β `"no entrances within radius"`, not "no data found").
|
| 41 |
+
- Print stylesheet drops Stone tints to neutral gray.
|
| 42 |
|
| 43 |
+
A v0.4.5 implementation session is a few hours of focused polish, not a day. The structural work is already done.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@@ -1,4 +1,12 @@
|
|
| 1 |
-
# Handoff: Riprap Findings Region
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
|
| 3 |
## Overview
|
| 4 |
|
|
@@ -323,9 +331,65 @@ design_handoff_riprap_findings/
|
|
| 323 |
|
| 324 |
## Scope
|
| 325 |
|
| 326 |
-
**In scope** for this handoff:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 327 |
|
| 328 |
-
|
| 329 |
|
| 330 |
## Open questions for the design team
|
| 331 |
|
|
|
|
| 1 |
+
# Handoff: Riprap Findings Region
|
| 2 |
+
|
| 3 |
+
**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.
|
| 4 |
+
|
| 5 |
+
**Read order for the implementer:**
|
| 6 |
+
1. `CLAUDE_CODE_PROMPT.md` β paste into Claude Code
|
| 7 |
+
2. `V0.4.5_SPEC.md` β the nine fixes (now realized in `v0.4.5.html`)
|
| 8 |
+
3. `README.md` (this file) β v0.4.4 reference
|
| 9 |
+
4. `design_files/` β prototypes (both v0.4.4 and v0.4.5 HTMLs are present)
|
| 10 |
|
| 11 |
## Overview
|
| 12 |
|
|
|
|
| 331 |
|
| 332 |
## Scope
|
| 333 |
|
| 334 |
+
**In scope** for this handoff:
|
| 335 |
+
|
| 336 |
+
- The Findings region (5 Stones, 12 card variants, run-health strip, smart provenance, hover linking, card grammar reference)
|
| 337 |
+
- Briefing prose and map, to the extent they connect to Findings via `linkedKey`
|
| 338 |
+
- The marketing **landing page** (see Β§"Landing page" below)
|
| 339 |
+
- v0.4.5 deltas in `V0.4.5_SPEC.md`
|
| 340 |
+
|
| 341 |
+
**Out of scope**: the methodology PDF, the export-PDF flow.
|
| 342 |
+
|
| 343 |
+
## Landing page
|
| 344 |
+
|
| 345 |
+
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/`:
|
| 346 |
+
|
| 347 |
+
- `Riprap Landing.html` β final shipping landing page
|
| 348 |
+
- `Riprap Landing Variants.html` β three exploratory variants on a design canvas (kept for reference; do not port)
|
| 349 |
+
|
| 350 |
+
### Landing structure (port `Riprap Landing.html`)
|
| 351 |
+
|
| 352 |
+
Four-section vertical scroll, max-width 1200px, paper background:
|
| 353 |
+
|
| 354 |
+
1. **Hero**
|
| 355 |
+
- Wordmark top-left (with `β` accent prefix)
|
| 356 |
+
- Headline (52px serif, italic emphasis on *any place*, line-broken into two lines)
|
| 357 |
+
- Deck (18px sans, max 70ch, ink-secondary)
|
| 358 |
+
- Big query box: "Try:" label + cycling example queries on a fixed dotted-underline rail (rail width pinned, ellipsis fallback for overflow)
|
| 359 |
+
- Submit button (ink fill, paper text, mono caps)
|
| 360 |
+
|
| 361 |
+
2. **"What you'll get back" preview** β 3-pane grid (1.4fr / 1fr / 1fr), bottoms equalized:
|
| 362 |
+
- **Excerpt pane**: serif briefing snippet with inline `[N]` citation pins; compact source list at bottom (tier-coded)
|
| 363 |
+
- **Evidence cards pane**: 2x2 grid of compact evidence cards, each with tier-coded left rule (2px), claim, source
|
| 364 |
+
- **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
|
| 365 |
+
|
| 366 |
+
3. **Five Stones strip** β explanation grid: 5 columns (`repeat(5, 1fr)`), one cell per Stone:
|
| 367 |
+
- Oversized italic-serif numerals `01..05` top-right of each cell (in `--rule-soft`, decorative)
|
| 368 |
+
- Stone name (serif 22px / 500)
|
| 369 |
+
- Role tagline (sans 13px / ink-secondary)
|
| 370 |
+
- Italic-serif tag ("what NYC's ground remembers" etc.)
|
| 371 |
+
- Dashed rule + mono source list at bottom
|
| 372 |
+
|
| 373 |
+
4. **Footer** β earns its keep:
|
| 374 |
+
- Tier legend (4 swatches: empirical / modeled / proxy / synthetic)
|
| 375 |
+
- Build line (mono, ink-tertiary)
|
| 376 |
+
|
| 377 |
+
### Landing β what's settled (don't redesign)
|
| 378 |
+
|
| 379 |
+
- The hero dropped the redundant "Riprap" eyebrow; the wordmark already says it. Headline carries the lede.
|
| 380 |
+
- Cycling examples ride a single fixed dotted underline (no jump on length change). Pin the rail width; ellipsis-truncate overflow.
|
| 381 |
+
- Pane heights are equalized via a flex-1 spacer in the cards pane and `flex: 1` on the map pane.
|
| 382 |
+
- Map texture is a subtle 8px paper-grain grid behind the colored layers β gives it a map register, not a diagram register.
|
| 383 |
+
- Stones strip uses oversized italic numerals as a typographic register (decorative, in soft-rule color).
|
| 384 |
+
- Italic serif is intentional: hero emphasis + Stone tags + Stone numerals. Reads as a deliberate third voice.
|
| 385 |
+
|
| 386 |
+
### Landing β em-dashes
|
| 387 |
+
|
| 388 |
+
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.
|
| 389 |
+
|
| 390 |
+
### Landing β out of scope variants
|
| 391 |
|
| 392 |
+
`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).
|
| 393 |
|
| 394 |
## Open questions for the design team
|
| 395 |
|
|
@@ -0,0 +1,313 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Riprap v0.4.5 Β· Polish on v0.4.4 (Findings region)
|
| 2 |
+
|
| 3 |
+
**Status**: design spec, ready for Claude Code implementation.
|
| 4 |
+
**Frame**: surgical polish on the live local SvelteKit app. Nothing structural changes. Reuse v0.4.4 components; apply nine deltas.
|
| 5 |
+
|
| 6 |
+
The v0.4.4 Findings region (five Stones, evidence cards, raster thumbnails, time-series, Capstone meta-card) is shipping correctly. Real queries (e.g., 80 Pioneer Street, Red Hook) render Stones producing genuinely different evidence-card kinds, with provenance collapsing under each Stone, the briefing prose with citations resolving cleanly, and the map with three tier-encoded layers and the address pin. The work below corrects nine specific issues observed in production-shaped local runs.
|
| 7 |
+
|
| 8 |
+
## What v0.4.5 must NOT change
|
| 9 |
+
|
| 10 |
+
- Card grammar (header / body / footer / tier badge / source link)
|
| 11 |
+
- The four-section briefing prose structure
|
| 12 |
+
- The Mellea reroll status strip ("Regenerating to satisfy citation grounding Β· attempt N of N Β· previous draft dimmed below")
|
| 13 |
+
- The four-tier color palette and glyphs
|
| 14 |
+
- The cold-start state with the v0.4.3 Stones one-liner
|
| 15 |
+
- The trust-signal footer
|
| 16 |
+
- The PDF template's core layout (Stone color print handling is the only new consideration)
|
| 17 |
+
- The map's base style and the existing layer rendering for Sandy / FEMA / 311
|
| 18 |
+
|
| 19 |
+
## What v0.4.5 must NOT introduce
|
| 20 |
+
|
| 21 |
+
- New card body variants beyond what v0.4.4 specified
|
| 22 |
+
- Card-level decoration competing with tier badges (Stone color hints are at the Stone region level β at the card level only as a designer-chosen subtle touch like a tinted left-border)
|
| 23 |
+
- Animations on card render
|
| 24 |
+
- New typography or spacing tokens unless something genuinely new emerges from the fixes
|
| 25 |
+
- Mascots, icons, or thematic visual associations for individual Stones beyond their color hint
|
| 26 |
+
|
| 27 |
+
---
|
| 28 |
+
|
| 29 |
+
## 1. Status semantics: split conflated states
|
| 30 |
+
|
| 31 |
+
**Problem.** The top tally currently reads "5 Stones Β· 15/18 functions fired Β· 9 evidence cards Β· 24.0s Β· 3 error" on a query where Mellea grounding passed 4/4 and the briefing came out clean. Two of those three "errors" are not errors at all:
|
| 32 |
+
|
| 33 |
+
- Keystone's `mta_entrance_exposure` returned "no entrances within radius" β that's the specialist working correctly. Absence of nearby subway entrances at 80 Pioneer is a true and useful finding, not a failure.
|
| 34 |
+
- Lodestone's `floodnet_forecast` hit its silent-floor (sensor has only 2 historical events, <5 required) β the four-tier discipline working as intended.
|
| 35 |
+
- Lodestone's `ttm_311_forecast` actually failed (311 history fetch error). That is the only real error.
|
| 36 |
+
|
| 37 |
+
Three different epistemic states are being rendered as one red square.
|
| 38 |
+
|
| 39 |
+
**Fix.** Split the FSM specialist status into five values:
|
| 40 |
+
|
| 41 |
+
| status | meaning | provenance row treatment | counts toward |
|
| 42 |
+
|---|---|---|---|
|
| 43 |
+
| `fired` | completed and produced output the reconciler used | tier-colored square | "fired" tally |
|
| 44 |
+
| `silent_by_design` | completed and correctly produced no output | open square in neutral tone, italicized message | "silent" tally |
|
| 45 |
+
| `warned` | output produced with a non-fatal warning | tier-colored square + small warn sidemark | "fired" tally + "warnings" count |
|
| 46 |
+
| `errored` | failed to complete, no usable output | red square; 1-line collapsed summary; full trace behind click-to-expand (v0.4.2 drilldown pattern) | "errored" tally |
|
| 47 |
+
| `not_invoked` | FSM skipped the specialist (precondition unmet) | hollow gray square; one-line reason | "not invoked" count |
|
| 48 |
+
|
| 49 |
+
**Aggregate count rules.** Top tally:
|
| 50 |
+
```
|
| 51 |
+
5 Stones Β· 15 fired Β· 5 silent Β· 1 errored Β· 24.0s
|
| 52 |
+
```
|
| 53 |
+
(no more rolled-up "errors" number)
|
| 54 |
+
|
| 55 |
+
Per-Stone summary (replaces "0 cards Β· 0 fired Β· 1 error Β· 30ms"):
|
| 56 |
+
```
|
| 57 |
+
Keystone Β· 0 cards Β· 5 silent Β· 30ms
|
| 58 |
+
Lodestone Β· 1 card Β· 3 fired Β· 1 silent Β· 1 errored Β· 1.5s
|
| 59 |
+
```
|
| 60 |
+
|
| 61 |
+
**Status messages β voice.** Engineering-honest, no euphemism. Examples to copy:
|
| 62 |
+
- `"no entrances within radius"`
|
| 63 |
+
- `"sensor has only 2 historical events; forecast omitted (silent-floor: 5)"`
|
| 64 |
+
- `"PLUTO join skipped: queried address not in NYC PLUTO dataset"`
|
| 65 |
+
- `"311 history fetch failed: HTTP 503 at NYC OpenData (3 retries)"`
|
| 66 |
+
|
| 67 |
+
Match v0.4.1βv0.4.4 voice β precise, slightly understated, comfortable with technical detail, engineer-to-engineer.
|
| 68 |
+
|
| 69 |
+
**This is the most important fix in v0.4.5.** It is the one actively misrepresenting system integrity. Do this first.
|
| 70 |
+
|
| 71 |
+
---
|
| 72 |
+
|
| 73 |
+
## 2. Capstone meta-card field-mapping
|
| 74 |
+
|
| 75 |
+
**Problem.** The Capstone meta-card displays:
|
| 76 |
+
```
|
| 77 |
+
mellea reroll: 0 attempts
|
| 78 |
+
grounding checks: 0/4 passed
|
| 79 |
+
citations resolved: 0
|
| 80 |
+
wall-clock: 24.0 s
|
| 81 |
+
```
|
| 82 |
+
|
| 83 |
+
But the briefing has 4 resolved citations ([1] Sandy, [2] NYC311, [3] Ida, [4] Microtopo); the Mellea status strip explicitly shows "attempt 2 of 2"; the briefing rendered clean (so grounding passed). Three of four metrics are showing zero on a clean run. State plumbing, not logic.
|
| 84 |
+
|
| 85 |
+
**Fix.** Wire the four metrics to the reconciler's actual state fields:
|
| 86 |
+
|
| 87 |
+
| display | reads from | expected for this query |
|
| 88 |
+
|---|---|---|
|
| 89 |
+
| `mellea reroll: N attempts` | per-query reroll count from Mellea (likely `melleaState.rerollCount`) | `1` (one reroll on top of initial) |
|
| 90 |
+
| `grounding checks: N/4 passed` | per-query grounding-check results array β count true | `4/4` |
|
| 91 |
+
| `citations resolved: N` | count of resolved citations in the final briefing payload | `4` |
|
| 92 |
+
| `wall-clock: NN.N s` | already correct β no change | `24.0 s` |
|
| 93 |
+
|
| 94 |
+
**Acceptance.** The Capstone meta-card on a clean Red Hook run shows `1 / 4/4 / 4 / 24.0 s`. On a failed-grounding run it shows the actual numbers (e.g. `2 / 3/4 / 4 / 31.0 s`) so the meta-card honestly reports what happened.
|
| 95 |
+
|
| 96 |
+
The Capstone meta-card is the integrity-narration UI for the entire pipeline. Zero on a clean run undersells the system's integrity. Accurate numbers turn it into a proof point β "this query went through 1 reroll to satisfy 4/4 grounding checks, resolved 4 citations to primary sources, and took 24 seconds end-to-end."
|
| 97 |
+
|
| 98 |
+
---
|
| 99 |
+
|
| 100 |
+
## 3. Provenance roster: always show full inventory
|
| 101 |
+
|
| 102 |
+
**Problem.** Expanded Keystone provenance shows only `step-16 Β· mta_entrance_exposure β no entrances within radius (30ms)`. Keystone fires five specialists (MTA, NYCHA, DOE, DOH, PLUTO). Four are missing from the UI.
|
| 103 |
+
|
| 104 |
+
**Fix.** Each Stone's provenance expander shows the **complete roster** of specialists the Stone *could have fired*, with one row per specialist and its status from Β§1. A reader who expands a Stone sees the full inventory β never a filtered subset. This is the auditability contract.
|
| 105 |
+
|
| 106 |
+
If a specialist genuinely didn't run, that's `not_invoked` with a one-line reason. The reader sees the intended roster and understands every absence.
|
| 107 |
+
|
| 108 |
+
**Implementation.** The Stone definition (in `src/lib/data/stones.ts` or wherever the registry lives) should list its full specialist roster. The provenance component renders one row per registry entry, joining against the run's actual specialist outputs. Specialists missing from the run output β `not_invoked` status, one-line reason from the FSM's skip log.
|
| 109 |
+
|
| 110 |
+
---
|
| 111 |
+
|
| 112 |
+
## 4. Touchstone β five cards, not three
|
| 113 |
+
|
| 114 |
+
**Problem.** Touchstone renders three cards (FloodNet, NYC 311, NOAA CO-OPS). v0.4.4 specified five: those three plus **TerraMind LULC** (SYN tier) and **Prithvi-NYC-Pluvial** (MOD tier).
|
| 115 |
+
|
| 116 |
+
**Fix.** Wire the structured outputs of `step_terramind_lulc` and `step_prithvi_live` (now Prithvi v2) to Touchstone cards.
|
| 117 |
+
|
| 118 |
+
**TerraMind LULC card** (Touchstone, SYN tier β synthetic prior, dashed top-rule):
|
| 119 |
+
- Title: `"Land use / land cover Β· TerraMind v1.2"`
|
| 120 |
+
- Body variant: existing `raster` thumbnail (240Γ120, segmentation overlay) + a **horizontal stacked class-mix bar** below the thumbnail showing percentage by LULC class. Use the conventional LULC palette (urban / water / vegetation / barren / wetland) β those colors are visual conventions for the layer itself, not new tier signals.
|
| 121 |
+
- Source: `TerraMind v1.2`
|
| 122 |
+
- Agency: `IBM TerraMind v1.2 Β· Sentinel-2 inputs`
|
| 123 |
+
- Vintage: latest Sentinel scene date for the AOI (e.g. `Sentinel-2 Β· 2024-09-18`)
|
| 124 |
+
- `mapLayer: "terramind-lulc"`
|
| 125 |
+
|
| 126 |
+
**Prithvi-NYC-Pluvial card** (Touchstone, MOD tier):
|
| 127 |
+
- Title: `"Pluvial flood prediction Β· Prithvi-NYC-Pluvial"`
|
| 128 |
+
- Body variant: `raster` thumbnail (flood-mask overlay) **plus a headline scalar above** showing flood percentage of AOI.
|
| 129 |
+
- Source: `Prithvi-NYC-Pluvial`
|
| 130 |
+
- Agency: `NASA-IBM Prithvi v2 Β· NYC fine-tune`
|
| 131 |
+
- Vintage: prediction time + Sentinel scene date
|
| 132 |
+
- `mapLayer: "prithvi-pluvial"`
|
| 133 |
+
|
| 134 |
+
**Layout.** v0.4.4's horizontal scrolling treatment handles five cards on wide viewports; on narrow viewports they stack vertically as designed. No new layout work.
|
| 135 |
+
|
| 136 |
+
**If the specialists aren't firing yet** (UPDATE_STONES.md commits 4 + 5 may still be in flight): surface that as a backend issue, but the card definitions land in v0.4.5 so they're ready when the data is.
|
| 137 |
+
|
| 138 |
+
---
|
| 139 |
+
|
| 140 |
+
## 5. Lodestone β both TTM cards, not just the zero-shot
|
| 141 |
+
|
| 142 |
+
**Problem.** Lodestone shows one time-series card: zero-shot Granite TTM r2 surge nowcast (9.6h, 6-min cadence). The footer correctly notes "Distinct from the fine-tuned Battery surge nowcast" β but that fine-tuned card is missing.
|
| 143 |
+
|
| 144 |
+
**Fix.** Add the fine-tuned TTM card. Both cards live in Lodestone:
|
| 145 |
+
|
| 146 |
+
| | Zero-shot card (existing) | Fine-tuned card (new) |
|
| 147 |
+
|---|---|---|
|
| 148 |
+
| Title | `"Storm surge nowcast at The Battery β 9.6 h horizon (regional)"` | `"Storm surge nowcast at The Battery β 96 h horizon (NYC-specialized fine-tune)"` |
|
| 149 |
+
| Horizon | 9.6 h | 96 h |
|
| 150 |
+
| Cadence | 6-min | hourly |
|
| 151 |
+
| Tier | MOD | MOD |
|
| 152 |
+
| Body | timeseries (existing) | timeseries (96-h forecast) |
|
| 153 |
+
| Source label | `Granite TTM r2 (zero-shot)` | `msradam/Granite-TTM-r2-Battery-Surge` |
|
| 154 |
+
| Footer extras | "regional disclosure" tag | model-card link to HF artifact: `huggingface.co/msradam/Granite-TTM-r2-Battery-Surge`, RMSE `0.157 m`, `β35% vs persistence`, AMD MI300X badge |
|
| 155 |
+
|
| 156 |
+
The fine-tuned card is a load-bearing piece of the Riprap-on-AMD story for the hackathon submission. Its presence as a Lodestone card alongside the zero-shot card is the visible "this system uses NYC-specialized fine-tuned models you can verify on HuggingFace" claim that closes the loop.
|
| 157 |
+
|
| 158 |
+
`step_ttm_battery_surge` in production trace fires at ~1.5s with `context_h=1024 Β· horizon_h=96`. If it's firing but not producing a structured output the card layer can render, surface that as a backend issue before v0.4.5 implementation.
|
| 159 |
+
|
| 160 |
+
---
|
| 161 |
+
|
| 162 |
+
## 6. The "anomaly" tag
|
| 163 |
+
|
| 164 |
+
**Problem.** Provenance expanders show `Hide provenance Β· 1 function Β· anomaly` (Keystone) and `Hide provenance Β· 5 functions Β· anomaly` (Lodestone). Overloaded label: doing different work in each context, partially duplicating row-level info.
|
| 165 |
+
|
| 166 |
+
**Decision: drop the "anomaly" tag.**
|
| 167 |
+
|
| 168 |
+
After Β§1 lands, Stone summaries say `Keystone Β· 0 cards Β· 5 silent Β· 30ms` (expected behavior β no exposed assets at this address) or `Lodestone Β· 1 card Β· 3 fired Β· 1 silent Β· 1 errored Β· 1.5s` (one specialist errored, visible in the count). The status counts make "anomaly" redundant. Less is more.
|
| 169 |
+
|
| 170 |
+
(Alternative β retained for record but not recommended: keep "anomaly" and define it as "Stone-level outcome differs from typical: 0 cards landed despite specialists firing, OR β₯1 specialist errored, OR Stone retried." If chosen, document the rule and apply mechanically. But Β§1's count breakdown carries the same information without the extra label.)
|
| 171 |
+
|
| 172 |
+
---
|
| 173 |
+
|
| 174 |
+
## 7. LAYERS panel β restructure to mirror Stones
|
| 175 |
+
|
| 176 |
+
**Problem.** Production shows the existing three layers (Sandy / FEMA-DEP / 311) in a flat LAYERS panel without Stone grouping. The four new raster layers (TerraMind LULC, TerraMind Buildings, Prithvi-NYC-Pluvial) don't have a home.
|
| 177 |
+
|
| 178 |
+
**Fix.** Restructure the LAYERS panel to mirror the Findings Stones structure:
|
| 179 |
+
|
| 180 |
+
```
|
| 181 |
+
LAYERS
|
| 182 |
+
|
| 183 |
+
βΎ Cornerstone β what NYC's ground remembers
|
| 184 |
+
β§ Sandy Inundation Zone (2012) EMP [on]
|
| 185 |
+
β¨ FEMA / DEP scenarios MOD [on]
|
| 186 |
+
β§ Ida HWM points (2021) EMP [off]
|
| 187 |
+
β₯ Microtopography (HAND/TWI) PRX [off]
|
| 188 |
+
|
| 189 |
+
βΎ Keystone β what's exposed
|
| 190 |
+
β MTA subway entrances EMP [on]
|
| 191 |
+
β NYCHA developments EMP [on]
|
| 192 |
+
β DOE schools EMP [on]
|
| 193 |
+
β DOH hospitals EMP [on]
|
| 194 |
+
β¦ TerraMind Buildings (current) SYN [off]
|
| 195 |
+
|
| 196 |
+
βΎ Touchstone β what's happening now
|
| 197 |
+
β 311 flood complaints PRX [on]
|
| 198 |
+
β FloodNet sensors EMP [on]
|
| 199 |
+
β₯ TerraMind LULC (current) SYN [off]
|
| 200 |
+
β₯ Prithvi-NYC-Pluvial flood pred. MOD [off]
|
| 201 |
+
|
| 202 |
+
βΎ Lodestone β what's coming
|
| 203 |
+
(no map layers β see Findings cards)
|
| 204 |
+
|
| 205 |
+
βΎ Capstone β synthesis
|
| 206 |
+
(not a map layer)
|
| 207 |
+
```
|
| 208 |
+
|
| 209 |
+
**New layer specs**:
|
| 210 |
+
- **TerraMind LULC** (SYN tier): conventional LULC palette (urban/water/vegetation/barren/wetland). Default off. Label includes Sentinel scene date.
|
| 211 |
+
- **TerraMind Buildings** (SYN tier): synthetic-prior glyph. Default off.
|
| 212 |
+
- **Prithvi-NYC-Pluvial** (MOD tier): Modeled-tier color treatment. Label includes Sentinel scene date.
|
| 213 |
+
|
| 214 |
+
The "no map layers β see Findings cards" label under Lodestone is explicit by design: the TTM Battery Surge is a Lodestone card, not a map layer. Naming the absence prevents the reader from looking for it.
|
| 215 |
+
|
| 216 |
+
---
|
| 217 |
+
|
| 218 |
+
## 8. Card-to-map hover linking
|
| 219 |
+
|
| 220 |
+
**Problem.** v0.4.4 specified hover-to-highlight from card β map element. Production may or may not have this wired.
|
| 221 |
+
|
| 222 |
+
**Fix.** Verify and ship the connection:
|
| 223 |
+
|
| 224 |
+
| Card type | Map link | Hover treatment |
|
| 225 |
+
|---|---|---|
|
| 226 |
+
| FEMA card | FEMA AE polygon | layer fill opacity bump (0.4 β 0.6), 100ms |
|
| 227 |
+
| HWM tabular | HWM contour + points | line weight 1px β 2px |
|
| 228 |
+
| Register cards (NYCHA, schools, etc.) | corresponding pins | pins gain 2px accent ring; on click, fitBounds() |
|
| 229 |
+
| FloodNet sensor card | sensor pin | pin glow + accent outline |
|
| 230 |
+
| Raster prediction (TerraMind, Prithvi) | the matching raster layer | layer fill opacity bump |
|
| 231 |
+
| Address card | address pin | pin pulse (single, 200ms) |
|
| 232 |
+
| TTM Battery Surge | (none β not spatial) | no map behavior |
|
| 233 |
+
| Capstone meta-card | (none) | no map behavior |
|
| 234 |
+
|
| 235 |
+
**Implementation.** Page-level `linkedKey` state (already specced in v0.4.4 README). Cards set it on `pointerenter` / `focus`, clear on `pointerleave` / `blur`. Map watches `$derived` of `linkedKey` and applies layer-specific class + label badge ("linked: {layer}") bottom-right.
|
| 236 |
+
|
| 237 |
+
Click-to-fit-bounds for register cards is new in v0.4.5: when clicking a register row inside a register card, the map calls `fitBounds()` on the affected feature(s) with 80px padding, 400ms easing.
|
| 238 |
+
|
| 239 |
+
---
|
| 240 |
+
|
| 241 |
+
## 9. Stone-tinted accent colors β light theming
|
| 242 |
+
|
| 243 |
+
**Problem.** v0.4.3/v0.4.4 prohibited per-Stone color coding to avoid competing with the four-tier epistemic palette. That prohibition is being relaxed: each Stone gets a single muted accent color the design system can apply as a hint.
|
| 244 |
+
|
| 245 |
+
**Five Stone accent tokens** (proposed values β designer can adjust within the constraints below):
|
| 246 |
+
|
| 247 |
+
| token | hex | name | rationale |
|
| 248 |
+
|---|---|---|---|
|
| 249 |
+
| `--stone-cornerstone` | `#7C6F5E` | warm taupe | grounded, soil-adjacent without being literally brown |
|
| 250 |
+
| `--stone-keystone` | `#5E6E7C` | cool slate | structural / architectural reading |
|
| 251 |
+
| `--stone-touchstone` | `#6B7C66` | muted sage | present-tense, alive without being signal-green |
|
| 252 |
+
| `--stone-lodestone` | `#7C6E5E` | softened ochre | forward-pointing warmth without competing with `--accent` |
|
| 253 |
+
| `--stone-capstone` | `#5E5E6E` | neutral indigo-gray | synthetic, cool, narrative |
|
| 254 |
+
|
| 255 |
+
All five sit at Lβ45 in OKLCH, chroma β€0.04. Verified non-overlap with the four tier hues (which sit at chroma 0β0.10 in different L regions).
|
| 256 |
+
|
| 257 |
+
### Constraints (non-negotiable)
|
| 258 |
+
|
| 259 |
+
1. **One color per Stone, five total.**
|
| 260 |
+
2. **Hint-level, not feature-level.** These are not signal-carrying like the tier palette. They are decoration that helps a reader navigate the five-region structure on first encounter.
|
| 261 |
+
3. **Must not compete with the four-tier palette.** Tier badges (filled/open square / dotted ring / hatched square in `#0B5394` / `#2A6FA8` / `#6B6B6B` / `#2A6FA8+stripe`) are the load-bearing epistemic signal. Stone colors must be muted enough or applied lightly enough that they read as decoration. If a reader could mistake a Stone color for a tier badge, it's wrong.
|
| 262 |
+
4. **Not technicolor.** Carto Positron base + IBM Plex + restrained ink palette is quiet and serious. Five primary-bright colors would shatter that.
|
| 263 |
+
5. **Not mascot.** No Stone gets an icon paired with its color. No "Cornerstone is brown because earthy" thematic mapping. The colors differentiate five regions; they don't express character.
|
| 264 |
+
6. **Print/PDF**: all five degrade to neutral gray (`#999`) in `@media print`. Each token has a print-media override.
|
| 265 |
+
7. **WCAG**: any text in or on a Stone color passes contrast. As tints (low-opacity backgrounds), body text stays in standard `--ink`. As foreground accents, contrast against `--paper` is verified.
|
| 266 |
+
|
| 267 |
+
### Recommended placement (one to three locations β pick conservatively)
|
| 268 |
+
|
| 269 |
+
**Primary** (recommend): a **3px left-rule** on each `<StoneRegion>` header strip, in that Stone's color. Subtle, navigational, hard to confuse with tier badges (which are inside cards, not on region headers).
|
| 270 |
+
|
| 271 |
+
**Secondary** (recommend): a **6px colored dot** beside each Stone name in the cold-start "How Riprap is built" five-line list. Helps recognition when the reader later sees the Stones in a query result.
|
| 272 |
+
|
| 273 |
+
**Tertiary** (optional, designer's call): a Stone-tinted **2px left-border on cards within that Stone's strip**. Risk: this brings color to the card chrome, which is where tier badges live. Apply only if the designer judges the chroma low enough that the tint reads as a region cue, not a tier cue. If in doubt, skip.
|
| 274 |
+
|
| 275 |
+
**Methodology page** (recommend): the 5Γ4 tier-Stone matrix uses Stone tints on its row labels (background tint at ~10% opacity, body text in standard ink). The matrix is where Stones-as-architecture is most visually present, and tints help readers parse the rows.
|
| 276 |
+
|
| 277 |
+
### Designer's veto
|
| 278 |
+
|
| 279 |
+
If the designer judges that adding Stone colors anywhere makes the UI busier without making it more legible, they may recommend keeping the existing zero-color Stone treatment with documented rationale. This is permission, not requirement.
|
| 280 |
+
|
| 281 |
+
### Print-media override
|
| 282 |
+
|
| 283 |
+
```css
|
| 284 |
+
@media print {
|
| 285 |
+
:root {
|
| 286 |
+
--stone-cornerstone: #999;
|
| 287 |
+
--stone-keystone: #999;
|
| 288 |
+
--stone-touchstone: #999;
|
| 289 |
+
--stone-lodestone: #999;
|
| 290 |
+
--stone-capstone: #999;
|
| 291 |
+
}
|
| 292 |
+
}
|
| 293 |
+
```
|
| 294 |
+
|
| 295 |
+
The PDF template's hierarchy is preserved by structure (Stone headings, type scale, rules), not by color. Stone tints are ignored in print.
|
| 296 |
+
|
| 297 |
+
---
|
| 298 |
+
|
| 299 |
+
## "v0.4.5 ready" looks like
|
| 300 |
+
|
| 301 |
+
A query at 80 Pioneer Street, Red Hook produces a Findings region where:
|
| 302 |
+
|
| 303 |
+
- Top tally reads `5 Stones Β· 15 fired Β· 5 silent Β· 1 errored Β· 24.0s` (no more 3-error mismatch).
|
| 304 |
+
- Capstone meta-card shows `1 reroll Β· 4/4 grounding Β· 4 citations Β· 24.0s` (no more zeroes).
|
| 305 |
+
- Every Stone's expanded provenance shows its full specialist roster, with each row carrying one of the five status states.
|
| 306 |
+
- Touchstone shows five cards (FloodNet, NYC 311, NOAA CO-OPS, TerraMind LULC, Prithvi-NYC-Pluvial).
|
| 307 |
+
- Lodestone shows two TTM time-series cards (zero-shot 9.6h, fine-tuned 96h with HF model-card link).
|
| 308 |
+
- LAYERS panel is grouped by Stone, with the four new raster layers wired and default-off.
|
| 309 |
+
- Hovering any spatial card lights up the corresponding map element; hovering a map layer outlines the corresponding card.
|
| 310 |
+
- "anomaly" tag is gone.
|
| 311 |
+
- Each Stone region carries a 3px left-rule in its accent color; the cold-start list shows colored dots beside Stone names. No card-level chrome shouts color.
|
| 312 |
+
|
| 313 |
+
The hackathon submission goes in (when public re-hosting completes) with a system that's not just impressive in capability but **transparent in operational state and visually composed as a deliberate architecture**.
|
|
@@ -0,0 +1,369 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!doctype html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="utf-8" />
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
| 6 |
+
<title>Riprap Β· Stone-grouped UI mockup v0.4.5</title>
|
| 7 |
+
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
| 8 |
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
| 9 |
+
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600&family=IBM+Plex+Sans:ital,wght@0,400;0,500;0,600;1,400;1,500&family=IBM+Plex+Serif:wght@400;500;600&display=swap" rel="stylesheet" />
|
| 10 |
+
<link rel="stylesheet" href="tokens.css" />
|
| 11 |
+
<link rel="stylesheet" href="styles.css" />
|
| 12 |
+
<style>
|
| 13 |
+
.stone-mock-page { background: var(--paper); padding: 24px 32px 64px; }
|
| 14 |
+
.stone-mock-page > .stone-mock-section + .stone-mock-section { margin-top: 32px; }
|
| 15 |
+
.stone-mock-head { display: flex; justify-content: space-between; align-items: baseline; padding-bottom: 12px; border-bottom: 2px solid var(--ink); margin-bottom: 18px; }
|
| 16 |
+
.stone-mock-head h1 { font-family: var(--font-serif); font-size: 26px; font-weight: 600; margin: 0; }
|
| 17 |
+
.stone-mock-head .meta { font-family: var(--font-mono); font-size: 11px; color: var(--ink-tertiary); letter-spacing: 0.06em; }
|
| 18 |
+
|
| 19 |
+
/* Stone-grouped evidence layout */
|
| 20 |
+
.stone-ev-layout-head { display: flex; justify-content: space-between; align-items: baseline; padding-bottom: 10px; margin-bottom: 14px; border-bottom: 1px solid var(--rule-soft); }
|
| 21 |
+
.stone-ev-layout-meta { font-family: var(--font-serif); font-style: italic; font-size: 13px; color: var(--ink-tertiary); }
|
| 22 |
+
|
| 23 |
+
.stone-ev-group { background: white; border: 1px solid var(--rule-soft); border-left: 3px solid var(--ink); margin-bottom: 14px; }
|
| 24 |
+
.stone-ev-head { display: grid; grid-template-columns: 1fr auto; gap: 12px; padding: 12px 18px; background: var(--stone-band-bg); border-bottom: 1px solid var(--rule-soft); align-items: baseline; }
|
| 25 |
+
.stone-ev-name-row { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; }
|
| 26 |
+
.stone-ev-name { font-family: var(--font-sans); font-size: 16px; font-weight: 600; margin: 0; color: var(--ink); }
|
| 27 |
+
.stone-ev-role { font-size: 13px; color: var(--ink-secondary); }
|
| 28 |
+
.stone-ev-tag { font-family: var(--font-serif); font-style: italic; font-size: 13px; color: var(--ink-tertiary); }
|
| 29 |
+
.stone-ev-meta { display: flex; gap: 10px; align-items: baseline; font-family: var(--font-mono); font-size: 11px; color: var(--ink-secondary); }
|
| 30 |
+
.stone-ev-count { font-weight: 600; color: var(--ink); }
|
| 31 |
+
.stone-ev-tally { display: inline-flex; align-items: center; gap: 4px; }
|
| 32 |
+
.stone-ev-rail { padding: 12px 14px; display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 10px; align-items: start; }
|
| 33 |
+
.stone-ev-rail > * { min-width: 0; }
|
| 34 |
+
.stone-ev-empty { padding: 16px 18px; color: var(--ink-tertiary); font-style: italic; font-size: 13px; max-width: 60ch; }
|
| 35 |
+
.stone-ev-empty p { margin: 6px 0 0; line-height: 1.55; }
|
| 36 |
+
|
| 37 |
+
/* Mockup-specific: kill sticky map within the long single-page v0.4.4 layout */
|
| 38 |
+
.stone-mock-page .app-region-map { position: static; max-height: none; }
|
| 39 |
+
|
| 40 |
+
/* Trace row primitives (used inside unified bands) */
|
| 41 |
+
.trace-row { display: grid; grid-template-columns: 16px 1fr 70px 90px 70px; gap: 12px; align-items: baseline; padding: 5px 18px; font-family: var(--font-mono); font-size: 12px; color: var(--ink-secondary); }
|
| 42 |
+
.trace-row-group > summary { display: grid; grid-template-columns: 16px 1fr 70px 90px 70px; gap: 12px; align-items: baseline; cursor: pointer; list-style: none; }
|
| 43 |
+
.trace-row-group > summary::-webkit-details-marker { display: none; }
|
| 44 |
+
.trace-name { color: var(--ink); }
|
| 45 |
+
.trace-status { color: var(--ink-tertiary); font-size: 11px; }
|
| 46 |
+
.trace-status-err { color: var(--status-error); }
|
| 47 |
+
.trace-tier { font-size: 11px; }
|
| 48 |
+
.trace-ms { text-align: right; color: var(--ink); }
|
| 49 |
+
.trace-row-error { background: var(--status-error-soft); }
|
| 50 |
+
.trace-row-warn .trace-bullet { color: #B26500; font-weight: 700; }
|
| 51 |
+
.trace-row-silent { color: var(--ink-tertiary); }
|
| 52 |
+
.trace-warn-note, .trace-note, .trace-error-summary { grid-column: 2 / -1; font-size: 11px; color: var(--ink-tertiary); font-style: italic; padding-top: 2px; }
|
| 53 |
+
.trace-error-summary { color: var(--status-error); font-style: normal; }
|
| 54 |
+
|
| 55 |
+
/* Run-health strip */
|
| 56 |
+
.run-health { display: flex; flex-wrap: wrap; align-items: baseline; gap: 8px; padding: 10px 18px; margin-bottom: 14px; background: var(--paper-deep); border: 1px solid var(--rule-soft); border-left: 3px solid var(--ink); font-family: var(--font-mono); font-size: 12px; color: var(--ink-secondary); }
|
| 57 |
+
.run-health-item strong { color: var(--ink); font-weight: 600; }
|
| 58 |
+
.run-health-sep { color: var(--ink-tertiary); }
|
| 59 |
+
.run-health-silent { color: var(--ink-tertiary); }
|
| 60 |
+
.run-health-warn { color: #B26500; }
|
| 61 |
+
.run-health-error { color: var(--status-error); font-weight: 600; }
|
| 62 |
+
|
| 63 |
+
/* Unified Stone band */
|
| 64 |
+
.stone-uni { background: white; border: 1px solid var(--rule-soft); border-left: 3px solid var(--ink); margin-bottom: 14px; }
|
| 65 |
+
.stone-uni-head { display: flex; justify-content: space-between; align-items: baseline; gap: 12px; padding: 12px 18px; background: var(--stone-band-bg); border-bottom: 1px solid var(--rule-soft); }
|
| 66 |
+
.stone-uni-head-left { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; }
|
| 67 |
+
.stone-uni-name { font-family: var(--font-sans); font-size: 16px; font-weight: 600; margin: 0; color: var(--ink); }
|
| 68 |
+
.stone-uni-role { font-size: 13px; color: var(--ink-secondary); }
|
| 69 |
+
.stone-uni-tag { font-family: var(--font-serif); font-style: italic; font-size: 13px; color: var(--ink-tertiary); }
|
| 70 |
+
.stone-uni-agg { font-family: var(--font-mono); font-size: 12px; color: var(--ink-secondary); display: inline-flex; gap: 4px; flex-wrap: wrap; align-items: baseline; }
|
| 71 |
+
.stone-uni-agg-cards { color: var(--ink); font-weight: 600; }
|
| 72 |
+
.stone-uni-agg-num { color: var(--ink); font-weight: 600; }
|
| 73 |
+
.stone-uni-agg-warn { color: #B26500; font-weight: 600; }
|
| 74 |
+
.stone-uni-agg-err { color: var(--status-error); font-weight: 600; }
|
| 75 |
+
.stone-uni-agg-ms { color: var(--ink); font-weight: 600; }
|
| 76 |
+
.stone-uni-agg-sep { color: var(--ink-tertiary); margin: 0 2px; }
|
| 77 |
+
.stone-uni-rail { padding: 12px 14px; display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 10px; align-items: start; }
|
| 78 |
+
.stone-uni-rail > * { min-width: 0; }
|
| 79 |
+
.stone-uni-empty { padding: 14px 18px; color: var(--ink-tertiary); font-size: 13px; max-width: 70ch; }
|
| 80 |
+
.stone-uni-empty p { margin: 6px 0 0; line-height: 1.55; font-style: italic; }
|
| 81 |
+
.stone-uni-trace { border-top: 1px dashed var(--rule-soft); }
|
| 82 |
+
.stone-uni-trace-toggle { width: 100%; display: flex; align-items: baseline; gap: 8px; padding: 8px 18px; background: transparent; border: none; cursor: pointer; text-align: left; font-family: var(--font-mono); font-size: 11px; color: var(--ink-tertiary); letter-spacing: 0.04em; }
|
| 83 |
+
.stone-uni-trace-toggle:hover { background: var(--paper-deep); color: var(--ink-secondary); }
|
| 84 |
+
.stone-uni-trace-caret { display: inline-block; width: 10px; }
|
| 85 |
+
.stone-uni-trace-body { padding: 4px 0 8px; background: var(--paper); border-top: 1px solid var(--rule-soft); }
|
| 86 |
+
|
| 87 |
+
/* ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 88 |
+
v0.4.4 Β· Findings region
|
| 89 |
+
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ */
|
| 90 |
+
.findings { background: var(--paper); }
|
| 91 |
+
.findings-head { display: flex; justify-content: space-between; align-items: baseline; padding-bottom: 12px; border-bottom: 2px solid var(--ink); margin-bottom: 18px; }
|
| 92 |
+
.findings-h2 { font-family: var(--font-serif); font-size: 26px; font-weight: 600; margin: 0; }
|
| 93 |
+
.findings-tagline { font-family: var(--font-mono); font-size: 11px; color: var(--ink-tertiary); letter-spacing: 0.04em; }
|
| 94 |
+
|
| 95 |
+
.f-runhealth { display: flex; flex-wrap: wrap; align-items: baseline; gap: 8px; padding: 10px 18px; margin-bottom: 14px; background: var(--paper-deep); border: 1px solid var(--rule-soft); border-left: 3px solid var(--ink); font-family: var(--font-mono); font-size: 12px; color: var(--ink-secondary); }
|
| 96 |
+
.f-rh-item strong { color: var(--ink); font-weight: 600; }
|
| 97 |
+
.f-rh-sep { color: var(--ink-tertiary); }
|
| 98 |
+
.f-rh-silent { color: var(--ink-tertiary); }
|
| 99 |
+
.f-rh-warn { color: #B26500; }
|
| 100 |
+
.f-rh-err { color: var(--status-error); font-weight: 600; }
|
| 101 |
+
|
| 102 |
+
.f-region { background: white; border: 1px solid var(--rule-soft); border-left: 3px solid var(--ink); margin-bottom: 14px; }
|
| 103 |
+
.f-region-head { display: flex; justify-content: space-between; gap: 12px; padding: 12px 18px; background: var(--stone-band-bg); border-bottom: 1px solid var(--rule-soft); align-items: baseline; flex-wrap: wrap; }
|
| 104 |
+
.f-region-head-left { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; min-width: 0; }
|
| 105 |
+
.f-region-num { font-family: var(--font-mono); font-size: 11px; color: var(--ink-tertiary); letter-spacing: 0.06em; }
|
| 106 |
+
.f-region-name { font-family: var(--font-sans); font-size: 16px; font-weight: 600; margin: 0; color: var(--ink); }
|
| 107 |
+
.f-region-role { font-size: 13px; color: var(--ink-secondary); }
|
| 108 |
+
.f-region-tag { font-family: var(--font-serif); font-style: italic; font-size: 13px; color: var(--ink-tertiary); }
|
| 109 |
+
|
| 110 |
+
.f-tally { font-family: var(--font-mono); font-size: 12px; color: var(--ink-secondary); display: inline-flex; gap: 6px; flex-wrap: wrap; align-items: baseline; }
|
| 111 |
+
.f-tally-strong { color: var(--ink); font-weight: 600; }
|
| 112 |
+
.f-tally-cards { color: var(--ink); font-weight: 600; }
|
| 113 |
+
.f-tally-sep { color: var(--ink-tertiary); }
|
| 114 |
+
.f-tally-warn { color: #B26500; }
|
| 115 |
+
.f-tally-err { color: var(--status-error); }
|
| 116 |
+
.f-tally-silent { color: var(--ink-tertiary); }
|
| 117 |
+
|
| 118 |
+
.f-rail { padding: 14px; display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 12px; align-items: start; }
|
| 119 |
+
.f-rail > * { min-width: 0; }
|
| 120 |
+
.f-rail-capstone { grid-template-columns: minmax(360px, 480px); }
|
| 121 |
+
|
| 122 |
+
.f-silent { padding: 14px 18px; display: flex; flex-direction: column; gap: 6px; max-width: 70ch; }
|
| 123 |
+
.f-silent-tag { font-family: var(--font-mono); font-size: 10px; letter-spacing: 0.12em; text-transform: uppercase; color: var(--ink-tertiary); align-self: flex-start; padding: 2px 6px; border: 1px solid var(--rule-soft); }
|
| 124 |
+
.f-silent-prose { font-family: var(--font-serif); font-style: italic; font-size: 13px; color: var(--ink-tertiary); margin: 0; line-height: 1.55; }
|
| 125 |
+
|
| 126 |
+
.f-prov { border-top: 1px dashed var(--rule-soft); }
|
| 127 |
+
.f-prov-toggle { width: 100%; display: flex; align-items: baseline; gap: 8px; padding: 8px 18px; background: transparent; border: none; cursor: pointer; text-align: left; font-family: var(--font-mono); font-size: 11px; color: var(--ink-tertiary); letter-spacing: 0.04em; }
|
| 128 |
+
.f-prov-toggle:hover { background: var(--paper-deep); color: var(--ink-secondary); }
|
| 129 |
+
.f-prov-caret { display: inline-block; width: 10px; }
|
| 130 |
+
.f-prov-meta { color: var(--ink-tertiary); }
|
| 131 |
+
.f-prov-body { padding: 4px 0 8px; background: var(--paper); border-top: 1px solid var(--rule-soft); }
|
| 132 |
+
|
| 133 |
+
/* βββ Card frame βββ */
|
| 134 |
+
.fc { border: 1px solid var(--rule-soft); background: var(--paper); padding: 12px 14px 10px; display: flex; flex-direction: column; gap: 8px; position: relative; border-top: 2px solid var(--ink); transition: border-color 120ms, box-shadow 120ms; cursor: pointer; }
|
| 135 |
+
.fc-tier-empirical { border-top-color: var(--tier-empirical); }
|
| 136 |
+
.fc-tier-modeled { border-top-color: var(--tier-modeled); }
|
| 137 |
+
.fc-tier-proxy { border-top-color: var(--tier-proxy); }
|
| 138 |
+
.fc-tier-synthetic { border-top-color: var(--tier-synthetic); border-top-style: dashed; }
|
| 139 |
+
.fc:hover, .fc.is-linked { box-shadow: 0 1px 0 var(--ink), 0 0 0 1px var(--ink); }
|
| 140 |
+
.fc.is-compact { padding: 8px 12px 8px; gap: 6px; }
|
| 141 |
+
|
| 142 |
+
.fc-head { display: flex; justify-content: space-between; align-items: center; gap: 8px; }
|
| 143 |
+
.fc-head-source { display: inline-flex; align-items: center; gap: 6px; font-family: var(--font-sans); font-size: 12px; font-weight: 600; color: var(--ink); }
|
| 144 |
+
.fc-head-source-label { letter-spacing: 0.01em; }
|
| 145 |
+
.fc-head-vintage { font-family: var(--font-mono); font-size: 10.5px; color: var(--ink-tertiary); letter-spacing: 0.02em; }
|
| 146 |
+
|
| 147 |
+
.fc-title { font-family: var(--font-sans); font-size: 13.5px; font-weight: 600; line-height: 1.3; margin: 0; color: var(--ink); text-wrap: pretty; }
|
| 148 |
+
.fc.is-compact .fc-title { font-size: 13px; }
|
| 149 |
+
|
| 150 |
+
.fc-body { display: flex; flex-direction: column; gap: 6px; padding: 2px 0; }
|
| 151 |
+
.fc-body-prose { font-size: 12px; line-height: 1.5; color: var(--ink-secondary); margin: 4px 0 0; }
|
| 152 |
+
.fc-body-sub { font-size: 11px; line-height: 1.5; color: var(--ink-tertiary); font-style: italic; }
|
| 153 |
+
.fc.is-compact .fc-body-prose, .fc.is-compact .fc-body-sub { font-size: 11px; }
|
| 154 |
+
|
| 155 |
+
.fc-headline { font-family: var(--font-serif); font-size: 22px; font-weight: 600; line-height: 1.1; }
|
| 156 |
+
.fc.is-compact .fc-headline { font-size: 19px; }
|
| 157 |
+
.fc-subhead { font-family: var(--font-mono); font-size: 11px; color: var(--ink-secondary); letter-spacing: 0.02em; }
|
| 158 |
+
|
| 159 |
+
.fc-table { width: 100%; border-collapse: collapse; font-family: var(--font-mono); font-size: 11px; }
|
| 160 |
+
.fc-table th, .fc-table td { text-align: left; padding: 3px 6px; border-bottom: 1px solid var(--rule-soft); }
|
| 161 |
+
.fc-table th { color: var(--ink-tertiary); font-weight: 500; text-transform: uppercase; letter-spacing: 0.08em; font-size: 9px; }
|
| 162 |
+
|
| 163 |
+
.fc-body-scalars .fc-scalars-row { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; padding: 4px 0; }
|
| 164 |
+
.fc-scalar-cell { display: flex; flex-direction: column; gap: 2px; padding: 6px 4px; background: var(--paper-deep); border: 1px solid var(--rule-soft); }
|
| 165 |
+
.fc-scalar-value { font-family: var(--font-serif); font-size: 18px; font-weight: 600; line-height: 1.05; }
|
| 166 |
+
.fc-scalar-label { font-family: var(--font-mono); font-size: 9.5px; color: var(--ink-tertiary); letter-spacing: 0.04em; }
|
| 167 |
+
|
| 168 |
+
.fc-raster-frame { position: relative; border: 1px solid var(--rule-soft); }
|
| 169 |
+
.fc-illustrative { position: absolute; top: 4px; right: 4px; font-family: var(--font-mono); font-size: 8.5px; letter-spacing: 0.08em; text-transform: uppercase; color: var(--paper); background: rgba(26,26,26,0.7); padding: 1px 5px; }
|
| 170 |
+
.fc-raster-headline { font-family: var(--font-mono); font-size: 11px; color: var(--ink-secondary); }
|
| 171 |
+
.fc-raster-headline span { font-family: var(--font-serif); font-size: 14px; font-weight: 600; }
|
| 172 |
+
|
| 173 |
+
.fc-body-timeseries { gap: 4px; }
|
| 174 |
+
.fc-ts-header { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; }
|
| 175 |
+
.fc-spatial-note { display: inline-block; font-family: var(--font-mono); font-size: 10px; letter-spacing: 0.06em; text-transform: uppercase; color: var(--accent); border: 1px solid var(--accent); padding: 1px 5px; margin-right: 6px; vertical-align: middle; }
|
| 176 |
+
|
| 177 |
+
/* Register composite, dense row layout (v0.4.4 follow-up: cut card height) */
|
| 178 |
+
.fc-body-register .fc-reg-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 0; border: 1px solid var(--rule-soft); }
|
| 179 |
+
.fc-reg-row { display: grid; grid-template-columns: 44px minmax(0, 1fr) auto; gap: 6px 10px; align-items: baseline; padding: 3px 8px; border-bottom: 1px solid var(--rule-soft); font-size: 12px; line-height: 1.35; }
|
| 180 |
+
.fc-reg-row:last-child { border-bottom: none; }
|
| 181 |
+
.fc-reg-row.is-silent { grid-template-columns: 44px minmax(0, 1fr); color: var(--ink-tertiary); font-style: italic; background: var(--paper-deep); padding: 2px 8px; }
|
| 182 |
+
.fc-reg-tag { font-family: var(--font-mono); font-size: 9.5px; letter-spacing: 0.08em; color: var(--ink-tertiary); align-self: center; }
|
| 183 |
+
.fc-reg-label { font-family: var(--font-sans); font-size: 12px; font-weight: 500; color: var(--ink); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
| 184 |
+
.fc-reg-detail { display: none; }
|
| 185 |
+
.fc-reg-tier { display: inline-flex; align-self: center; }
|
| 186 |
+
.fc-reg-source { font-family: var(--font-mono); font-size: 9.5px; color: var(--ink-tertiary); align-self: center; white-space: nowrap; }
|
| 187 |
+
.fc-reg-silent { font-family: var(--font-serif); font-size: 11.5px; color: var(--ink-tertiary); }
|
| 188 |
+
.fc.is-compact .fc-reg-row { padding: 2px 8px; font-size: 11.5px; }
|
| 189 |
+
.fc.is-compact .fc-reg-label { font-size: 11.5px; }
|
| 190 |
+
|
| 191 |
+
/* Comparison */
|
| 192 |
+
.fc-body-comparison .fc-cmp-grid { display: grid; grid-template-columns: 1fr auto 1fr; gap: 8px; align-items: stretch; padding: 4px 0; }
|
| 193 |
+
.fc-cmp-cell { display: flex; flex-direction: column; gap: 4px; padding: 8px 10px; background: var(--paper-deep); border: 1px solid var(--rule-soft); }
|
| 194 |
+
.fc-cmp-cell-tier { display: inline-flex; align-items: center; gap: 5px; font-family: var(--font-mono); font-size: 10px; color: var(--ink-tertiary); letter-spacing: 0.04em; }
|
| 195 |
+
.fc-cmp-cell-label { color: var(--ink-secondary); }
|
| 196 |
+
.fc-cmp-cell-value { font-family: var(--font-serif); font-size: 22px; font-weight: 600; line-height: 1.05; }
|
| 197 |
+
.fc-cmp-cell-aux { font-family: var(--font-mono); font-size: 10px; color: var(--ink-tertiary); }
|
| 198 |
+
.fc-cmp-divider { font-family: var(--font-serif); font-style: italic; font-size: 13px; color: var(--ink-tertiary); align-self: center; padding: 0 4px; }
|
| 199 |
+
.fc-cmp-delta { font-family: var(--font-mono); font-size: 11px; color: var(--accent); padding: 4px 0; border-top: 1px dashed var(--rule-soft); border-bottom: 1px dashed var(--rule-soft); margin: 4px 0; }
|
| 200 |
+
|
| 201 |
+
/* Meta */
|
| 202 |
+
.fc-body-meta .fc-meta-list { display: grid; grid-template-columns: 1fr 1fr; gap: 4px 12px; margin: 0; }
|
| 203 |
+
.fc-meta-row { display: flex; flex-direction: column; gap: 1px; padding: 4px 6px; background: var(--paper-deep); border-left: 2px solid var(--rule-soft); }
|
| 204 |
+
.fc-meta-row dt { font-family: var(--font-mono); font-size: 9.5px; letter-spacing: 0.06em; text-transform: uppercase; color: var(--ink-tertiary); margin: 0; }
|
| 205 |
+
.fc-meta-row dd { font-family: var(--font-sans); font-size: 13px; font-weight: 500; color: var(--ink); margin: 0; }
|
| 206 |
+
|
| 207 |
+
/* Card foot */
|
| 208 |
+
.fc-foot { display: flex; justify-content: space-between; align-items: center; padding-top: 6px; margin-top: auto; border-top: 1px solid var(--rule-soft); }
|
| 209 |
+
.fc-foot-cite { background: transparent; border: 0; padding: 2px 0; cursor: pointer; display: inline-flex; align-items: center; gap: 5px; color: var(--ink-secondary); }
|
| 210 |
+
.fc-foot-cite:hover { color: var(--accent); }
|
| 211 |
+
.fc-foot-docid { font-family: var(--font-mono); font-size: 10px; letter-spacing: 0.02em; }
|
| 212 |
+
.fc-foot-docid-mute { color: var(--ink-tertiary); }
|
| 213 |
+
.fc-foot-arrow { font-family: var(--font-mono); font-size: 11px; color: var(--ink-tertiary); }
|
| 214 |
+
|
| 215 |
+
.fc-tier-badge { display: inline-flex; align-items: center; gap: 4px; font-family: var(--font-mono); font-size: 9.5px; letter-spacing: 0.08em; padding: 2px 5px; border: 1px solid var(--rule-soft); color: var(--ink-secondary); background: var(--paper); }
|
| 216 |
+
.fc-tier-badge-empirical { border-color: var(--tier-empirical); color: var(--tier-empirical); }
|
| 217 |
+
.fc-tier-badge-modeled { border-color: var(--tier-modeled); color: var(--tier-modeled); }
|
| 218 |
+
.fc-tier-badge-proxy { border-color: var(--tier-proxy); color: var(--tier-proxy); }
|
| 219 |
+
.fc-tier-badge-synthetic { border-color: var(--tier-synthetic); color: var(--tier-synthetic); border-style: dashed; }
|
| 220 |
+
|
| 221 |
+
/* Map highlight on hover-link */
|
| 222 |
+
.map-frame.is-link-floodnet::after,
|
| 223 |
+
.map-frame.is-link-fema-ae::after,
|
| 224 |
+
.map-frame.is-link-hwm::after,
|
| 225 |
+
.map-frame.is-link-stormwater::after,
|
| 226 |
+
.map-frame.is-link-prithvi::after,
|
| 227 |
+
.map-frame.is-link-buildings::after,
|
| 228 |
+
.map-frame.is-link-complaints::after,
|
| 229 |
+
.map-frame.is-link-registers::after {
|
| 230 |
+
content: attr(data-link-label); position: absolute; bottom: 8px; right: 8px;
|
| 231 |
+
background: var(--ink); color: var(--paper); font-family: var(--font-mono); font-size: 10px; letter-spacing: 0.08em; text-transform: uppercase;
|
| 232 |
+
padding: 4px 8px; pointer-events: none; z-index: 5;
|
| 233 |
+
}
|
| 234 |
+
.map-frame.is-linked { outline: 2px solid var(--accent-graphical); outline-offset: -2px; }
|
| 235 |
+
|
| 236 |
+
</style>
|
| 237 |
+
</head>
|
| 238 |
+
<body>
|
| 239 |
+
<template id="__bundler_thumbnail">
|
| 240 |
+
<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
|
| 241 |
+
<rect width="100" height="100" fill="#FAFAF7"/>
|
| 242 |
+
<g transform="translate(20 28)">
|
| 243 |
+
<rect x="0" y="0" width="60" height="9" fill="#0B5394"/>
|
| 244 |
+
<rect x="0" y="13" width="48" height="9" fill="#2A6FA8"/>
|
| 245 |
+
<rect x="0" y="26" width="38" height="9" fill="#6B6B6B"/>
|
| 246 |
+
<rect x="0" y="39" width="52" height="9" fill="#D17C00"/>
|
| 247 |
+
</g>
|
| 248 |
+
</svg>
|
| 249 |
+
</template>
|
| 250 |
+
<div id="root"></div>
|
| 251 |
+
|
| 252 |
+
<script src="https://unpkg.com/react@18.3.1/umd/react.development.js" integrity="sha384-hD6/rw4ppMLGNu3tX5cjIb+uRZ7UkRJ6BPkLpg4hAu/6onKUg4lLsHAs9EBPT82L" crossorigin="anonymous"></script>
|
| 253 |
+
<script src="https://unpkg.com/react-dom@18.3.1/umd/react-dom.development.js" integrity="sha384-u6aeetuaXnQ38mYT8rp6sbXaQe3NL9t+IBXmnYxwkUI2Hw4bsp2Wvmx4yRQF1uAm" crossorigin="anonymous"></script>
|
| 254 |
+
<script src="https://unpkg.com/@babel/standalone@7.29.0/babel.min.js" integrity="sha384-m08KidiNqLdpJqLq95G/LEi8Qvjl/xUYll3QILypMoQ65QorJ9Lvtp2RXYGBFj1y" crossorigin="anonymous"></script>
|
| 255 |
+
|
| 256 |
+
<script type="text/babel" src="tweaks-panel.jsx"></script>
|
| 257 |
+
<script type="text/babel" src="glyphs.jsx"></script>
|
| 258 |
+
<script type="text/babel" src="briefing.jsx"></script>
|
| 259 |
+
<script type="text/babel" src="map.jsx"></script>
|
| 260 |
+
<script type="text/babel" src="trace.jsx"></script>
|
| 261 |
+
<script type="text/babel" src="evidence.jsx"></script>
|
| 262 |
+
<script type="text/babel" src="shell.jsx"></script>
|
| 263 |
+
<script type="text/babel" src="stones-trace.jsx"></script>
|
| 264 |
+
<script type="text/babel" src="stone-evidence.jsx"></script>
|
| 265 |
+
<script type="text/babel" src="findings.jsx"></script>
|
| 266 |
+
<script type="text/babel">
|
| 267 |
+
const { useState: useM } = React;
|
| 268 |
+
|
| 269 |
+
const V44_DEFAULTS = /*EDITMODE-BEGIN*/{
|
| 270 |
+
"density": "comfortable",
|
| 271 |
+
"provenance": "smart",
|
| 272 |
+
"query": "redhook",
|
| 273 |
+
"showComparison": false,
|
| 274 |
+
"showGrammar": true
|
| 275 |
+
}/*EDITMODE-END*/;
|
| 276 |
+
|
| 277 |
+
function StoneMock() {
|
| 278 |
+
const [activeCite, setActiveCite] = useM(null);
|
| 279 |
+
const [linkedKey, setLinkedKey] = useM(null);
|
| 280 |
+
const [tweaks, setTweak] = window.useTweaks(V44_DEFAULTS);
|
| 281 |
+
const handleCite = (id) => setActiveCite(id);
|
| 282 |
+
const handleHover = (key) => setLinkedKey(key);
|
| 283 |
+
/* Reflect linkedKey into map-frame data attribute */
|
| 284 |
+
React.useEffect(() => {
|
| 285 |
+
const frame = document.querySelector(".stone-mock-page .map-frame");
|
| 286 |
+
if (!frame) return;
|
| 287 |
+
/* clear */
|
| 288 |
+
["floodnet","fema-ae","hwm","stormwater","prithvi","buildings","complaints","registers"].forEach(k => frame.classList.remove("is-link-" + k));
|
| 289 |
+
frame.classList.toggle("is-linked", !!linkedKey);
|
| 290 |
+
if (linkedKey) {
|
| 291 |
+
frame.classList.add("is-link-" + linkedKey);
|
| 292 |
+
const labels = { floodnet: "FloodNet sensor", "fema-ae": "FEMA Zone AE", hwm: "USGS HWM points", stormwater: "DEP stormwater", prithvi: "Prithvi flood pred.", buildings: "TerraMind buildings", complaints: "311 complaints", registers: "Asset registers" };
|
| 293 |
+
frame.dataset.linkLabel = labels[linkedKey] || linkedKey;
|
| 294 |
+
}
|
| 295 |
+
}, [linkedKey]);
|
| 296 |
+
|
| 297 |
+
return (
|
| 298 |
+
<div className="riprap-root stone-mock-page">
|
| 299 |
+
<window.AppHeader query={"80 Pioneer Street, Red Hook, Brooklyn"} onResetCold={() => {}} onOpenMethodology={() => {}}/>
|
| 300 |
+
|
| 301 |
+
{/* Β§1 Β· Briefing + map */}
|
| 302 |
+
<section className="stone-mock-section" data-screen-label="01 Β· Briefing + map">
|
| 303 |
+
<div className="stone-mock-head">
|
| 304 |
+
<h1>Riprap Flood Exposure Briefing , 80 Pioneer Street</h1>
|
| 305 |
+
<span className="meta">v0.4.5 Β· Stone-grouped UI mockup</span>
|
| 306 |
+
</div>
|
| 307 |
+
<div className="app-shell app-shell-desktop">
|
| 308 |
+
<main id="region-briefing" className="app-region app-region-brief">
|
| 309 |
+
<header className="region-head"><span className="section-label">Briefing</span></header>
|
| 310 |
+
<h1 className="brief-h1">
|
| 311 |
+
<span className="brief-h1-eyebrow">Riprap Flood Exposure Briefing</span>
|
| 312 |
+
<span className="brief-h1-addr">80 Pioneer Street</span>
|
| 313 |
+
<span className="brief-h1-meta">
|
| 314 |
+
<span className="brief-h1-meta-row"><span className="brief-h1-meta-key">borough</span><span className="brief-h1-meta-val">Brooklyn Β· CB6</span></span>
|
| 315 |
+
<span className="brief-h1-meta-row"><span className="brief-h1-meta-key">tract</span><span className="brief-h1-meta-val">36047008500</span></span>
|
| 316 |
+
<span className="brief-h1-meta-row"><span className="brief-h1-meta-key">generated</span><span className="brief-h1-meta-val">2026-05-05 14:22 ET</span></span>
|
| 317 |
+
</span>
|
| 318 |
+
</h1>
|
| 319 |
+
<window.StreamingBriefing onCite={handleCite} replayKey={0}/>
|
| 320 |
+
</main>
|
| 321 |
+
<aside className="app-region app-region-map" aria-label="Map">
|
| 322 |
+
<header className="region-head">
|
| 323 |
+
<span className="section-label">Map</span>
|
| 324 |
+
<span className="region-head-meta">Carto Positron Β· z16</span>
|
| 325 |
+
</header>
|
| 326 |
+
<div className="map-frame">
|
| 327 |
+
<window.RedHookMapMock activeLayers={{ empirical: true, modeled: true, synthetic: true, proxy: true, "prithvi-pluvial": false }} queriedAddress="80 Pioneer Street"/>
|
| 328 |
+
<window.MapLegend activeLayers={{ empirical: true, modeled: true, synthetic: true, proxy: true, "prithvi-pluvial": false }} onToggle={() => {}}/>
|
| 329 |
+
</div>
|
| 330 |
+
</aside>
|
| 331 |
+
<aside className="app-region app-region-cites" aria-label="Citations">
|
| 332 |
+
<window.CitationDrawer activeId={activeCite}/>
|
| 333 |
+
</aside>
|
| 334 |
+
</div>
|
| 335 |
+
</section>
|
| 336 |
+
|
| 337 |
+
{/* Β§2 Β· Findings region Β· v0.4.4 */}
|
| 338 |
+
<section className="stone-mock-section" data-screen-label="02 Findings">
|
| 339 |
+
<window.FindingsRegion
|
| 340 |
+
density={tweaks.density}
|
| 341 |
+
provenanceMode={tweaks.provenance}
|
| 342 |
+
queryKey={tweaks.query}
|
| 343 |
+
showComparison={tweaks.showComparison}
|
| 344 |
+
showGrammar={tweaks.showGrammar}
|
| 345 |
+
onCite={handleCite}
|
| 346 |
+
onHover={handleHover}
|
| 347 |
+
linkedKey={linkedKey}
|
| 348 |
+
/>
|
| 349 |
+
</section>
|
| 350 |
+
|
| 351 |
+
{/* Tweaks panel */}
|
| 352 |
+
<window.TweaksPanel title="Tweaks">
|
| 353 |
+
<window.TweakSection label="Display"/>
|
| 354 |
+
<window.TweakRadio label="Density" value={tweaks.density} onChange={(v) => setTweak("density", v)} options={["comfortable", "compact"]}/>
|
| 355 |
+
<window.TweakSelect label="Query" value={tweaks.query} onChange={(v) => setTweak("query", v)} options={["redhook", "bronx"]}/>
|
| 356 |
+
<window.TweakSection label="Provenance"/>
|
| 357 |
+
<window.TweakSelect label="Default" value={tweaks.provenance} onChange={(v) => setTweak("provenance", v)} options={["smart", "expanded", "collapsed"]}/>
|
| 358 |
+
<window.TweakSection label="Card grammar"/>
|
| 359 |
+
<window.TweakToggle label="Show grammar reference" value={tweaks.showGrammar} onChange={(v) => setTweak("showGrammar", v)}/>
|
| 360 |
+
<window.TweakToggle label="Show comparison card" value={tweaks.showComparison} onChange={(v) => setTweak("showComparison", v)}/>
|
| 361 |
+
</window.TweaksPanel>
|
| 362 |
+
</div>
|
| 363 |
+
);
|
| 364 |
+
}
|
| 365 |
+
|
| 366 |
+
ReactDOM.createRoot(document.getElementById("root")).render(<StoneMock/>);
|
| 367 |
+
</script>
|
| 368 |
+
</body>
|
| 369 |
+
</html>
|
|
@@ -13,8 +13,8 @@ const CARDS_BY_QUERY = {
|
|
| 13 |
redhook: {
|
| 14 |
cornerstone: ["fc-fema", "fc-hwm", "fc-stormwater"],
|
| 15 |
keystone: ["fc-register-rh"],
|
| 16 |
-
touchstone: ["fc-floodnet", "fc-311", "fc-
|
| 17 |
-
lodestone: ["fc-ttm-surge", "fc-npcc4"],
|
| 18 |
capstone: ["fc-mellea-meta"],
|
| 19 |
},
|
| 20 |
bronx: {
|
|
@@ -85,14 +85,13 @@ const CARDS = {
|
|
| 85 |
source: "NYC OpenData", agency: "NYC OpenData Β· multi-agency join",
|
| 86 |
title: "Nearby exposed assets",
|
| 87 |
registers: [
|
| 88 |
-
{ reg: "MTA", tier: "empirical", label:
|
| 89 |
-
{ reg: "NYCHA", tier: "empirical", label:
|
| 90 |
-
{ reg: "
|
| 91 |
-
{ reg: "
|
| 92 |
-
{ reg: "
|
| 93 |
-
{ reg: "PLUTO", tier: "empirical", label: "Lot 36047 / 521 / 7", detail: "BIN 3018472 Β· MX-1", sourceId: "PLUTO-2024v2", vintage: "2024-12", note: null },
|
| 94 |
],
|
| 95 |
-
sub: "5
|
| 96 |
docId: "RIPRAP-EXP-RH80", vintage: "2026-05", citeId: "c-reg-rh",
|
| 97 |
mapKey: "registers",
|
| 98 |
},
|
|
@@ -143,16 +142,33 @@ const CARDS = {
|
|
| 143 |
docId: "NYC311-FLD-CB11", vintage: "2025-12", citeId: "cx7",
|
| 144 |
mapKey: "complaints",
|
| 145 |
},
|
| 146 |
-
"fc-prithvi": {
|
| 147 |
stone: "touchstone", tier: "modeled", variant: "raster-pred",
|
| 148 |
-
source: "Prithvi-NYC", agency: "
|
| 149 |
-
title: "Pluvial flood prediction
|
| 150 |
rasterKind: "prithvi",
|
| 151 |
headline: "0.3% flooded", subhead: "no flooding apparent Β· scene 2026-05-02",
|
| 152 |
sub: "Model interpretation of imagery, not real-time observation. Confidence-mean 0.84 across non-flooded pixels.",
|
| 153 |
-
docId: "PRITHVI-NYC-PLUV-V2-20260502", vintage: "2026-05-02",
|
| 154 |
illustrative: true, citeId: "c-prithvi",
|
| 155 |
-
mapKey: "prithvi",
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 156 |
},
|
| 157 |
"fc-nws": {
|
| 158 |
stone: "touchstone", tier: "empirical", variant: "scalars",
|
|
@@ -171,15 +187,31 @@ const CARDS = {
|
|
| 171 |
/* ββ Lodestone ββ */
|
| 172 |
"fc-ttm-surge": {
|
| 173 |
stone: "lodestone", tier: "modeled", variant: "timeseries",
|
| 174 |
-
source: "Granite TTM r2", agency: "IBM Granite-TimeSeries Β·
|
| 175 |
-
title: "Storm surge nowcast at The Battery
|
| 176 |
timeseries: { hours: 96, peak: { x: 38, y: 47 }, peakLabel: "+47 cm @ +38h" },
|
| 177 |
-
headline: "+47 cm", subhead: "peak surge residual Β·
|
| 178 |
-
sub: "Nowcast applies city-wide via NOAA station 8518750.
|
| 179 |
-
docId: "
|
| 180 |
spatialNote: "regional Β· The Battery, not point-of-query",
|
| 181 |
citeId: "c-ttm",
|
| 182 |
-
mapKey: null,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 183 |
},
|
| 184 |
"fc-npcc4": {
|
| 185 |
stone: "lodestone", tier: "modeled", variant: "forecast",
|
|
@@ -202,10 +234,10 @@ const CARDS = {
|
|
| 202 |
source: "Mellea", agency: "Capstone synthesis Β· grounding check",
|
| 203 |
title: "Briefing reconciliation",
|
| 204 |
metaRows: [
|
| 205 |
-
{ k: "Mellea reroll",
|
| 206 |
-
{ k: "Grounding checks",
|
| 207 |
-
{ k: "Citations resolved",v: "
|
| 208 |
-
{ k: "
|
| 209 |
],
|
| 210 |
sub: "Capstone produces prose, not cards. This meta-card summarizes the reconciler chain that wrote the four-section briefing above.",
|
| 211 |
docId: "RIPRAP-CAP-RH80", vintage: "2026-05-05 14:22 ET", citeId: null,
|
|
@@ -679,10 +711,11 @@ const flatten = (members) => members.flatMap((m) => (m.children ? [m, ...flatten
|
|
| 679 |
|
| 680 |
const StoneTally44 = ({ cardCount, members }) => {
|
| 681 |
const flat = flatten(members);
|
| 682 |
-
const fired = flat.filter((m) => m.status === "
|
| 683 |
-
const silent = flat.filter((m) => m.status === "
|
| 684 |
-
const warn = flat.filter((m) => m.status === "
|
| 685 |
-
const error = flat.filter((m) => m.status === "
|
|
|
|
| 686 |
const ms = members.reduce((acc, m) => Math.max(acc, m.ms || 0), 0);
|
| 687 |
const fmtMs = (x) => (x === 0 ? "β" : x < 1000 ? x + "ms" : (x / 1000).toFixed(1) + "s");
|
| 688 |
return (
|
|
@@ -692,7 +725,8 @@ const StoneTally44 = ({ cardCount, members }) => {
|
|
| 692 |
<span className="f-tally-fired"><span className="f-tally-strong">{fired}</span> fired</span>
|
| 693 |
{silent > 0 && <><span className="f-tally-sep">Β·</span><span className="f-tally-silent"><span className="f-tally-strong">{silent}</span> silent</span></>}
|
| 694 |
{warn > 0 && <><span className="f-tally-sep">Β·</span><span className="f-tally-warn"><span className="f-tally-strong">{warn}</span> warn</span></>}
|
| 695 |
-
{error > 0 && <><span className="f-tally-sep">Β·</span><span className="f-tally-err"><span className="f-tally-strong">{error}</span>
|
|
|
|
| 696 |
<span className="f-tally-sep">Β·</span>
|
| 697 |
<span className="f-tally-ms"><span className="f-tally-strong">{fmtMs(ms)}</span></span>
|
| 698 |
</span>
|
|
@@ -704,11 +738,12 @@ const StoneRegion = ({ stone, cardIds, density, provenanceMode, onCite, onHover,
|
|
| 704 |
const cards = cardIds.map((id) => CARDS[id]).filter(Boolean);
|
| 705 |
const traceCount = flatten(stone.members).length;
|
| 706 |
const flat = flatten(stone.members);
|
| 707 |
-
const
|
|
|
|
| 708 |
const defaultOpen =
|
| 709 |
provenanceMode === "all-expanded" ? true :
|
| 710 |
provenanceMode === "all-collapsed" ? false :
|
| 711 |
-
/* smart */
|
| 712 |
const [traceOpen, setTraceOpen] = useFi(defaultOpen);
|
| 713 |
/* Re-sync if user toggles tweak */
|
| 714 |
useFiMemo(() => setTraceOpen(defaultOpen), [provenanceMode]);
|
|
@@ -763,7 +798,7 @@ const StoneRegion = ({ stone, cardIds, density, provenanceMode, onCite, onHover,
|
|
| 763 |
>
|
| 764 |
<span className="f-prov-caret" aria-hidden="true">{traceOpen ? "βΎ" : "βΈ"}</span>
|
| 765 |
<span className="f-prov-label">{traceOpen ? "Hide" : "Show"} provenance</span>
|
| 766 |
-
<span className="f-prov-meta">Β· {traceCount} function{traceCount === 1 ? "" : "s"}{
|
| 767 |
</button>
|
| 768 |
{traceOpen && (
|
| 769 |
<div className="f-prov-body">
|
|
@@ -779,11 +814,11 @@ const StoneRegion = ({ stone, cardIds, density, provenanceMode, onCite, onHover,
|
|
| 779 |
|
| 780 |
const RunHealth44 = ({ totalCards }) => {
|
| 781 |
const all = window.STONES.flatMap((s) => flatten(s.members));
|
| 782 |
-
const fired = all.filter((m) => m.status === "
|
| 783 |
const total = all.length;
|
| 784 |
-
const silent = all.filter((m) => m.status === "
|
| 785 |
-
const warn = all.filter((m) => m.status === "
|
| 786 |
-
const error = all.filter((m) => m.status === "
|
| 787 |
return (
|
| 788 |
<div className="f-runhealth">
|
| 789 |
<span className="f-rh-item"><strong>5</strong> Stones</span>
|
|
@@ -792,10 +827,10 @@ const RunHealth44 = ({ totalCards }) => {
|
|
| 792 |
<span className="f-rh-sep">Β·</span>
|
| 793 |
<span className="f-rh-item"><strong>{totalCards}</strong> evidence cards</span>
|
| 794 |
<span className="f-rh-sep">Β·</span>
|
| 795 |
-
<span className="f-rh-item"><strong>
|
| 796 |
{silent > 0 && <><span className="f-rh-sep">Β·</span><span className="f-rh-item f-rh-silent">{silent} silent</span></>}
|
| 797 |
-
{warn > 0 && <><span className="f-rh-sep">Β·</span><span className="f-rh-item f-rh-warn">{warn}
|
| 798 |
-
{error > 0 && <><span className="f-rh-sep">Β·</span><span className="f-rh-item f-rh-err">{error}
|
| 799 |
</div>
|
| 800 |
);
|
| 801 |
};
|
|
|
|
| 13 |
redhook: {
|
| 14 |
cornerstone: ["fc-fema", "fc-hwm", "fc-stormwater"],
|
| 15 |
keystone: ["fc-register-rh"],
|
| 16 |
+
touchstone: ["fc-floodnet", "fc-311", "fc-nws", "fc-terramind-lulc", "fc-prithvi-pluvial"],
|
| 17 |
+
lodestone: ["fc-ttm-surge", "fc-ttm-surge-ft", "fc-npcc4"],
|
| 18 |
capstone: ["fc-mellea-meta"],
|
| 19 |
},
|
| 20 |
bronx: {
|
|
|
|
| 85 |
source: "NYC OpenData", agency: "NYC OpenData Β· multi-agency join",
|
| 86 |
title: "Nearby exposed assets",
|
| 87 |
registers: [
|
| 88 |
+
{ reg: "MTA", tier: "empirical", label: null, detail: null, sourceId: null, vintage: null, note: "no entrances within radius" },
|
| 89 |
+
{ reg: "NYCHA", tier: "empirical", label: null, detail: null, sourceId: null, vintage: null, note: "no NYCHA developments within 1.0 mi" },
|
| 90 |
+
{ reg: "DOE", tier: "empirical", label: null, detail: null, sourceId: null, vintage: null, note: "no DOE schools within 1.0 mi" },
|
| 91 |
+
{ reg: "DOH", tier: "empirical", label: null, detail: null, sourceId: null, vintage: null, note: "no acute-care hospitals within 1.0 mi" },
|
| 92 |
+
{ reg: "PLUTO", tier: "empirical", label: null, detail: null, sourceId: null, vintage: null, note: "PLUTO join skipped: queried address not in NYC PLUTO dataset" },
|
|
|
|
| 93 |
],
|
| 94 |
+
sub: "5 specialists Β· 5 silent_by_design Β· 0 cards landed (full inventory shown)",
|
| 95 |
docId: "RIPRAP-EXP-RH80", vintage: "2026-05", citeId: "c-reg-rh",
|
| 96 |
mapKey: "registers",
|
| 97 |
},
|
|
|
|
| 142 |
docId: "NYC311-FLD-CB11", vintage: "2025-12", citeId: "cx7",
|
| 143 |
mapKey: "complaints",
|
| 144 |
},
|
| 145 |
+
"fc-prithvi-pluvial": {
|
| 146 |
stone: "touchstone", tier: "modeled", variant: "raster-pred",
|
| 147 |
+
source: "Prithvi-NYC-Pluvial", agency: "NASA-IBM Prithvi v2 Β· NYC fine-tune",
|
| 148 |
+
title: "Pluvial flood prediction Β· Prithvi-NYC-Pluvial",
|
| 149 |
rasterKind: "prithvi",
|
| 150 |
headline: "0.3% flooded", subhead: "no flooding apparent Β· scene 2026-05-02",
|
| 151 |
sub: "Model interpretation of imagery, not real-time observation. Confidence-mean 0.84 across non-flooded pixels.",
|
| 152 |
+
docId: "PRITHVI-NYC-PLUV-V2-20260502", vintage: "2026-05-02 Β· Sentinel-2",
|
| 153 |
illustrative: true, citeId: "c-prithvi",
|
| 154 |
+
mapKey: "prithvi-pluvial",
|
| 155 |
+
},
|
| 156 |
+
"fc-terramind-lulc": {
|
| 157 |
+
stone: "touchstone", tier: "synthetic", variant: "lulc",
|
| 158 |
+
source: "TerraMind v1.2", agency: "IBM TerraMind v1.2 Β· Sentinel-2 inputs",
|
| 159 |
+
title: "Land use / land cover Β· TerraMind v1.2",
|
| 160 |
+
rasterKind: "lulc",
|
| 161 |
+
classMix: [
|
| 162 |
+
{ k: "urban", pct: 62, color: "#C66" },
|
| 163 |
+
{ k: "water", pct: 18, color: "#5B7FB4" },
|
| 164 |
+
{ k: "vegetation", pct: 12, color: "#5B8A4A" },
|
| 165 |
+
{ k: "barren", pct: 6, color: "#A89A78" },
|
| 166 |
+
{ k: "wetland", pct: 2, color: "#D9C75A" },
|
| 167 |
+
],
|
| 168 |
+
sub: "Synthetic prior. LULC palette is a layer convention, not a tier signal.",
|
| 169 |
+
docId: "TERRAMIND-LULC-20240918", vintage: "Sentinel-2 Β· 2024-09-18",
|
| 170 |
+
citeId: "c-tm-lulc",
|
| 171 |
+
mapKey: "terramind-lulc",
|
| 172 |
},
|
| 173 |
"fc-nws": {
|
| 174 |
stone: "touchstone", tier: "empirical", variant: "scalars",
|
|
|
|
| 187 |
/* ββ Lodestone ββ */
|
| 188 |
"fc-ttm-surge": {
|
| 189 |
stone: "lodestone", tier: "modeled", variant: "timeseries",
|
| 190 |
+
source: "Granite TTM r2 (zero-shot)", agency: "IBM Granite-TimeSeries Β· regional",
|
| 191 |
+
title: "Storm surge nowcast at The Battery β 9.6 h horizon (regional)",
|
| 192 |
timeseries: { hours: 96, peak: { x: 38, y: 47 }, peakLabel: "+47 cm @ +38h" },
|
| 193 |
+
headline: "+47 cm", subhead: "peak surge residual Β· 9.6h horizon Β· 6-min cadence",
|
| 194 |
+
sub: "Regional disclosure. Nowcast applies city-wide via NOAA station 8518750. Distinct from the fine-tuned Battery surge nowcast.",
|
| 195 |
+
docId: "ttm_battery_surge_zeroshot", vintage: "2026-05-05 12:00 ET",
|
| 196 |
spatialNote: "regional Β· The Battery, not point-of-query",
|
| 197 |
citeId: "c-ttm",
|
| 198 |
+
mapKey: null,
|
| 199 |
+
},
|
| 200 |
+
"fc-ttm-surge-ft": {
|
| 201 |
+
stone: "lodestone", tier: "modeled", variant: "timeseries-ft",
|
| 202 |
+
source: "msradam/Granite-TTM-r2-Battery-Surge", agency: "Granite TTM r2 Β· NYC-specialized fine-tune",
|
| 203 |
+
title: "Storm surge nowcast at The Battery β 96 h horizon (NYC-specialized fine-tune)",
|
| 204 |
+
timeseries: { hours: 96, peak: { x: 38, y: 53 }, peakLabel: "+53 cm @ +38h" },
|
| 205 |
+
headline: "+53 cm", subhead: "peak surge Β· 96h horizon Β· hourly cadence",
|
| 206 |
+
sub: "Fine-tuned on NYC tide-gauge history. Trained on AMD MI300X.",
|
| 207 |
+
docId: "ttm_battery_surge_finetune", vintage: "2026-05-05 12:00 ET",
|
| 208 |
+
spatialNote: "regional Β· The Battery, not point-of-query",
|
| 209 |
+
hfModelCard: "huggingface.co/msradam/Granite-TTM-r2-Battery-Surge",
|
| 210 |
+
rmse: "0.157 m",
|
| 211 |
+
skillVsPersistence: "β35% vs persistence",
|
| 212 |
+
hardwareBadge: "MI300X",
|
| 213 |
+
citeId: "c-ttm-ft",
|
| 214 |
+
mapKey: null,
|
| 215 |
},
|
| 216 |
"fc-npcc4": {
|
| 217 |
stone: "lodestone", tier: "modeled", variant: "forecast",
|
|
|
|
| 234 |
source: "Mellea", agency: "Capstone synthesis Β· grounding check",
|
| 235 |
title: "Briefing reconciliation",
|
| 236 |
metaRows: [
|
| 237 |
+
{ k: "Mellea reroll", v: "1 reroll" },
|
| 238 |
+
{ k: "Grounding checks", v: "4 / 4 passed" },
|
| 239 |
+
{ k: "Citations resolved", v: "4" },
|
| 240 |
+
{ k: "Wall-clock", v: "24.0 s" },
|
| 241 |
],
|
| 242 |
sub: "Capstone produces prose, not cards. This meta-card summarizes the reconciler chain that wrote the four-section briefing above.",
|
| 243 |
docId: "RIPRAP-CAP-RH80", vintage: "2026-05-05 14:22 ET", citeId: null,
|
|
|
|
| 711 |
|
| 712 |
const StoneTally44 = ({ cardCount, members }) => {
|
| 713 |
const flat = flatten(members);
|
| 714 |
+
const fired = flat.filter((m) => m.status === "fired" || m.status === "warned").length;
|
| 715 |
+
const silent = flat.filter((m) => m.status === "silent_by_design").length;
|
| 716 |
+
const warn = flat.filter((m) => m.status === "warned").length;
|
| 717 |
+
const error = flat.filter((m) => m.status === "errored").length;
|
| 718 |
+
const notInvoked = flat.filter((m) => m.status === "not_invoked").length;
|
| 719 |
const ms = members.reduce((acc, m) => Math.max(acc, m.ms || 0), 0);
|
| 720 |
const fmtMs = (x) => (x === 0 ? "β" : x < 1000 ? x + "ms" : (x / 1000).toFixed(1) + "s");
|
| 721 |
return (
|
|
|
|
| 725 |
<span className="f-tally-fired"><span className="f-tally-strong">{fired}</span> fired</span>
|
| 726 |
{silent > 0 && <><span className="f-tally-sep">Β·</span><span className="f-tally-silent"><span className="f-tally-strong">{silent}</span> silent</span></>}
|
| 727 |
{warn > 0 && <><span className="f-tally-sep">Β·</span><span className="f-tally-warn"><span className="f-tally-strong">{warn}</span> warn</span></>}
|
| 728 |
+
{error > 0 && <><span className="f-tally-sep">Β·</span><span className="f-tally-err"><span className="f-tally-strong">{error}</span> errored</span></>}
|
| 729 |
+
{notInvoked > 0 && <><span className="f-tally-sep">Β·</span><span className="f-tally-notinvoked"><span className="f-tally-strong">{notInvoked}</span> not invoked</span></>}
|
| 730 |
<span className="f-tally-sep">Β·</span>
|
| 731 |
<span className="f-tally-ms"><span className="f-tally-strong">{fmtMs(ms)}</span></span>
|
| 732 |
</span>
|
|
|
|
| 738 |
const cards = cardIds.map((id) => CARDS[id]).filter(Boolean);
|
| 739 |
const traceCount = flatten(stone.members).length;
|
| 740 |
const flat = flatten(stone.members);
|
| 741 |
+
const hasError = flat.some((m) => m.status === "errored");
|
| 742 |
+
const hasWarn = flat.some((m) => m.status === "warned");
|
| 743 |
const defaultOpen =
|
| 744 |
provenanceMode === "all-expanded" ? true :
|
| 745 |
provenanceMode === "all-collapsed" ? false :
|
| 746 |
+
/* smart */ hasError || hasWarn;
|
| 747 |
const [traceOpen, setTraceOpen] = useFi(defaultOpen);
|
| 748 |
/* Re-sync if user toggles tweak */
|
| 749 |
useFiMemo(() => setTraceOpen(defaultOpen), [provenanceMode]);
|
|
|
|
| 798 |
>
|
| 799 |
<span className="f-prov-caret" aria-hidden="true">{traceOpen ? "βΎ" : "βΈ"}</span>
|
| 800 |
<span className="f-prov-label">{traceOpen ? "Hide" : "Show"} provenance</span>
|
| 801 |
+
<span className="f-prov-meta">Β· {traceCount} function{traceCount === 1 ? "" : "s"}{hasError ? " Β· errored" : hasWarn ? " Β· warned" : ""}</span>
|
| 802 |
</button>
|
| 803 |
{traceOpen && (
|
| 804 |
<div className="f-prov-body">
|
|
|
|
| 814 |
|
| 815 |
const RunHealth44 = ({ totalCards }) => {
|
| 816 |
const all = window.STONES.flatMap((s) => flatten(s.members));
|
| 817 |
+
const fired = all.filter((m) => m.status === "fired" || m.status === "warned").length;
|
| 818 |
const total = all.length;
|
| 819 |
+
const silent = all.filter((m) => m.status === "silent_by_design").length;
|
| 820 |
+
const warn = all.filter((m) => m.status === "warned").length;
|
| 821 |
+
const error = all.filter((m) => m.status === "errored").length;
|
| 822 |
return (
|
| 823 |
<div className="f-runhealth">
|
| 824 |
<span className="f-rh-item"><strong>5</strong> Stones</span>
|
|
|
|
| 827 |
<span className="f-rh-sep">Β·</span>
|
| 828 |
<span className="f-rh-item"><strong>{totalCards}</strong> evidence cards</span>
|
| 829 |
<span className="f-rh-sep">Β·</span>
|
| 830 |
+
<span className="f-rh-item"><strong>24.0s</strong> wall-clock</span>
|
| 831 |
{silent > 0 && <><span className="f-rh-sep">Β·</span><span className="f-rh-item f-rh-silent">{silent} silent</span></>}
|
| 832 |
+
{warn > 0 && <><span className="f-rh-sep">Β·</span><span className="f-rh-item f-rh-warn">{warn} warned</span></>}
|
| 833 |
+
{error > 0 && <><span className="f-rh-sep">Β·</span><span className="f-rh-item f-rh-err">{error} errored</span></>}
|
| 834 |
</div>
|
| 835 |
);
|
| 836 |
};
|
|
@@ -175,34 +175,51 @@ const RedHookMapMock = ({ activeLayers, queriedAddress }) => {
|
|
| 175 |
};
|
| 176 |
|
| 177 |
const MapLegend = ({ activeLayers, onToggle }) => {
|
|
|
|
|
|
|
| 178 |
const layers = [
|
| 179 |
-
{ key: "empirical", tier: "empirical", label: "Sandy Inundation Zone (2012)", source: "NYC OEM" },
|
| 180 |
-
{ key: "modeled",
|
| 181 |
-
{ key: "
|
| 182 |
-
{ key: "
|
|
|
|
| 183 |
];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 184 |
return (
|
| 185 |
-
<div className="map-legend" role="group" aria-label="Map layer toggles">
|
| 186 |
<div className="map-legend-head">
|
| 187 |
-
<span className="section-label">Layers</span>
|
| 188 |
</div>
|
| 189 |
-
{
|
| 190 |
-
<
|
| 191 |
-
|
| 192 |
-
|
| 193 |
-
|
| 194 |
-
|
| 195 |
-
|
| 196 |
-
|
| 197 |
-
|
| 198 |
-
|
| 199 |
-
|
| 200 |
-
|
| 201 |
-
|
| 202 |
-
|
| 203 |
-
|
| 204 |
-
|
| 205 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 206 |
))}
|
| 207 |
</div>
|
| 208 |
);
|
|
|
|
| 175 |
};
|
| 176 |
|
| 177 |
const MapLegend = ({ activeLayers, onToggle }) => {
|
| 178 |
+
/* v0.4.5: restructured by Stone. Each row carries its source-Stone so the
|
| 179 |
+
panel visually mirrors the Findings stack. The tier swatch is unchanged. */
|
| 180 |
const layers = [
|
| 181 |
+
{ key: "empirical", tier: "empirical", stone: "cornerstone", label: "Sandy Inundation Zone (2012)", source: "NYC OEM" },
|
| 182 |
+
{ key: "modeled", tier: "modeled", stone: "cornerstone", label: "FEMA Zone AE Β· preliminary FIRM", source: "FEMA" },
|
| 183 |
+
{ key: "proxy", tier: "proxy", stone: "touchstone", label: "311 flood complaints, 2019β25", source: "NYC 311" },
|
| 184 |
+
{ key: "synthetic", tier: "synthetic", stone: "touchstone", label: "Synthetic LULC (2025-09-14)", source: "TerraMind v1.2" },
|
| 185 |
+
{ key: "prithvi-pluvial", tier: "modeled", stone: "touchstone", label: "Prithvi pluvial prediction", source: "Prithvi-NYC v2" },
|
| 186 |
];
|
| 187 |
+
const stoneOrder = ["cornerstone", "touchstone"];
|
| 188 |
+
const stoneMeta = {
|
| 189 |
+
cornerstone: { name: "Cornerstone", role: "what NYC's ground remembers" },
|
| 190 |
+
touchstone: { name: "Touchstone", role: "what's happening now" },
|
| 191 |
+
};
|
| 192 |
return (
|
| 193 |
+
<div className="map-legend" role="group" aria-label="Map layer toggles, grouped by Stone">
|
| 194 |
<div className="map-legend-head">
|
| 195 |
+
<span className="section-label">Layers Β· by Stone</span>
|
| 196 |
</div>
|
| 197 |
+
{stoneOrder.map((sk) => (
|
| 198 |
+
<div key={sk} className={`map-legend-stone map-legend-stone-${sk}`} data-stone={sk}>
|
| 199 |
+
<div className="map-legend-stone-head">
|
| 200 |
+
<span className={`map-legend-stone-dot map-legend-stone-dot-${sk}`} aria-hidden="true"></span>
|
| 201 |
+
<span className="map-legend-stone-name">{stoneMeta[sk].name}</span>
|
| 202 |
+
<span className="map-legend-stone-role">Β· {stoneMeta[sk].role}</span>
|
| 203 |
+
</div>
|
| 204 |
+
{layers.filter((l) => l.stone === sk).map((l) => (
|
| 205 |
+
<button
|
| 206 |
+
key={l.key}
|
| 207 |
+
type="button"
|
| 208 |
+
className={`map-legend-item ${activeLayers[l.key] ? "is-on" : "is-off"}`}
|
| 209 |
+
onClick={() => onToggle(l.key)}
|
| 210 |
+
aria-pressed={activeLayers[l.key]}
|
| 211 |
+
>
|
| 212 |
+
<span className="map-legend-swatch" aria-hidden="true">
|
| 213 |
+
<TierGlyph tier={l.tier} size={11} color={`var(--tier-${l.tier})`} />
|
| 214 |
+
</span>
|
| 215 |
+
<span className="map-legend-text">
|
| 216 |
+
<span className="map-legend-label">{l.label}</span>
|
| 217 |
+
<span className="map-legend-source">{l.source} Β· <TierBadge tier={l.tier} compact /></span>
|
| 218 |
+
</span>
|
| 219 |
+
<span className="map-legend-toggle" aria-hidden="true">{activeLayers[l.key] ? "ON" : "OFF"}</span>
|
| 220 |
+
</button>
|
| 221 |
+
))}
|
| 222 |
+
</div>
|
| 223 |
))}
|
| 224 |
</div>
|
| 225 |
);
|
|
@@ -73,9 +73,11 @@ const ColdStart = ({ onPick, onSubmit }) => {
|
|
| 73 |
<div className="cold-start-trust">
|
| 74 |
<span className="section-label">How Riprap is built</span>
|
| 75 |
<p className="cold-start-thesis">
|
| 76 |
-
<
|
| 77 |
-
|
| 78 |
-
{" "}<strong>
|
|
|
|
|
|
|
| 79 |
</p>
|
| 80 |
<ul className="cold-start-trust-list">
|
| 81 |
<li>Five named cognitive roles compose ~25 atomic specialists. <a href="#spec-stones">Architecture β</a></li>
|
|
|
|
| 73 |
<div className="cold-start-trust">
|
| 74 |
<span className="section-label">How Riprap is built</span>
|
| 75 |
<p className="cold-start-thesis">
|
| 76 |
+
<span className="cold-start-thesis-stone-dot cold-start-thesis-stone-dot-cornerstone" aria-hidden="true"></span><strong>Cornerstone</strong> remembers.{" "}
|
| 77 |
+
<span className="cold-start-thesis-stone-dot cold-start-thesis-stone-dot-keystone" aria-hidden="true"></span><strong>Keystone</strong> tallies.
|
| 78 |
+
{" "}<span className="cold-start-thesis-stone-dot cold-start-thesis-stone-dot-touchstone" aria-hidden="true"></span><strong>Touchstone</strong> watches.{" "}
|
| 79 |
+
<span className="cold-start-thesis-stone-dot cold-start-thesis-stone-dot-lodestone" aria-hidden="true"></span><strong>Lodestone</strong> projects.
|
| 80 |
+
{" "}<span className="cold-start-thesis-stone-dot cold-start-thesis-stone-dot-capstone" aria-hidden="true"></span><strong>Capstone</strong> writes it all down with citations.
|
| 81 |
</p>
|
| 82 |
<ul className="cold-start-trust-list">
|
| 83 |
<li>Five named cognitive roles compose ~25 atomic specialists. <a href="#spec-stones">Architecture β</a></li>
|
|
@@ -1,5 +1,6 @@
|
|
| 1 |
-
/* Riprap v0.4.
|
| 2 |
-
|
|
|
|
| 3 |
*/
|
| 4 |
|
| 5 |
const { useState: useStV44, useEffect: useEfV44 } = React;
|
|
@@ -9,11 +10,11 @@ const STONES = [
|
|
| 9 |
key: "cornerstone", name: "Cornerstone", role: "the hazard reader",
|
| 10 |
tag: "what NYC's ground remembers",
|
| 11 |
members: [
|
| 12 |
-
{ id: "c1", name: "sandy_inundation.lookup",
|
| 13 |
-
{ id: "c2", name: "usgs_hwm.spatial_join", status: "
|
| 14 |
-
{ id: "c3", name: "fema_firm.lookup", status: "
|
| 15 |
-
{ id: "c4", name: "dep_stormwater.lookup", status: "
|
| 16 |
-
{ id: "c5", name: "prithvi.historical_segment",status: "
|
| 17 |
warning: "deprecation: Prithvi-100M v1 β v2 migration scheduled 2026-Q3" },
|
| 18 |
],
|
| 19 |
},
|
|
@@ -21,68 +22,73 @@ const STONES = [
|
|
| 21 |
key: "keystone", name: "Keystone", role: "the asset register",
|
| 22 |
tag: "what's exposed",
|
| 23 |
members: [
|
| 24 |
-
{ id: "k1", name: "
|
| 25 |
-
|
| 26 |
-
{ id: "
|
| 27 |
-
|
| 28 |
-
{ id: "
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 29 |
],
|
| 30 |
},
|
| 31 |
{
|
| 32 |
key: "touchstone", name: "Touchstone", role: "the live observer",
|
| 33 |
tag: "what's happening now",
|
| 34 |
members: [
|
| 35 |
-
{ id: "t1", name: "floodnet.history",
|
| 36 |
-
{ id: "t2", name: "nyc311.flood_complaints",
|
| 37 |
-
{ id: "t3", name: "
|
| 38 |
-
|
|
|
|
| 39 |
],
|
| 40 |
},
|
| 41 |
{
|
| 42 |
key: "lodestone", name: "Lodestone", role: "the projector",
|
| 43 |
tag: "what's coming",
|
| 44 |
members: [
|
| 45 |
-
{ id: "l1", name: "npcc4.slr_projection",
|
| 46 |
-
{ id: "l2", name: "
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
],
|
| 53 |
-
},
|
| 54 |
-
{ id: "l3", name: "terramind.synthetic_sar", status: "ok", ms: 8200, tier: "synthetic" },
|
| 55 |
-
{ id: "l4", name: "nfip.claims_aggregation", status: "ok", ms: 460, tier: "proxy" },
|
| 56 |
],
|
| 57 |
},
|
| 58 |
{
|
| 59 |
key: "capstone", name: "Capstone", role: "the synthesizer",
|
| 60 |
tag: "writes it all down with citations",
|
| 61 |
members: [
|
| 62 |
-
{ id: "p1", name: "granite.compose_briefing", status: "
|
| 63 |
-
{ id: "p2", name: "mellea.grounding_check", status: "
|
| 64 |
-
{ id: "p3", name: "weasyprint.render_artifact",status: "
|
| 65 |
],
|
| 66 |
},
|
| 67 |
];
|
| 68 |
|
| 69 |
-
const fmtMs = (ms) => ms === 0 ? "
|
| 70 |
const tierColor = (t) => t ? `var(--tier-${t})` : "var(--ink-tertiary)";
|
| 71 |
|
|
|
|
|
|
|
| 72 |
const StoneAggregate = ({ stone }) => {
|
| 73 |
-
const
|
| 74 |
-
const
|
| 75 |
-
const
|
| 76 |
-
const
|
| 77 |
-
const
|
| 78 |
-
const
|
| 79 |
const ms = stone.members.reduce((acc, m) => Math.max(acc, m.ms || 0), 0);
|
| 80 |
return (
|
| 81 |
<span className="stone-band-agg">
|
| 82 |
<span className="stone-band-agg-num">{fired}</span> fired
|
| 83 |
{silent > 0 && <> Β· <span className="stone-band-agg-num">{silent}</span> silent</>}
|
| 84 |
{warn > 0 && <> Β· <span className="stone-band-agg-warn">{warn} warn</span></>}
|
| 85 |
-
{error > 0 && <> Β· <span className="stone-band-agg-err">{error}
|
|
|
|
| 86 |
{" Β· "}<span className="stone-band-agg-ms">{fmtMs(ms)}</span>
|
| 87 |
</span>
|
| 88 |
);
|
|
@@ -103,26 +109,71 @@ const TraceRow = ({ m, indent = 16 }) => {
|
|
| 103 |
</details>
|
| 104 |
);
|
| 105 |
}
|
| 106 |
-
if (m.status === "
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 107 |
return (
|
| 108 |
-
<div className="trace-row trace-row-
|
| 109 |
-
<span className="trace-bullet">
|
| 110 |
<span className="trace-name">{m.name}</span>
|
| 111 |
-
<span className="trace-status trace-status-
|
| 112 |
<span className="trace-tier" style={{ color: tierColor(m.tier) }}>{m.tier || ""}</span>
|
| 113 |
<span className="trace-ms">{fmtMs(m.ms)}</span>
|
| 114 |
-
<span className="trace-
|
|
|
|
| 115 |
</div>
|
| 116 |
);
|
| 117 |
}
|
|
|
|
| 118 |
return (
|
| 119 |
-
<div className=
|
| 120 |
-
<span className="trace-bullet"
|
| 121 |
<span className="trace-name">{m.name}</span>
|
| 122 |
-
<span className="trace-status">
|
| 123 |
<span className="trace-tier" style={{ color: tierColor(m.tier) }}>{m.tier || ""}</span>
|
| 124 |
<span className="trace-ms">{fmtMs(m.ms)}</span>
|
| 125 |
-
{m.warning && <span className="trace-warn-note">{m.warning}</span>}
|
| 126 |
{m.note && <span className="trace-note">{m.note}</span>}
|
| 127 |
</div>
|
| 128 |
);
|
|
@@ -131,11 +182,11 @@ const TraceRow = ({ m, indent = 16 }) => {
|
|
| 131 |
const StoneBand = ({ stone }) => {
|
| 132 |
const [open, setOpen] = useStV44(true);
|
| 133 |
return (
|
| 134 |
-
<section className={`stone-band stone-band-${stone.key}`} aria-labelledby={`band-h-${stone.key}`}>
|
| 135 |
<button className="stone-band-head" aria-expanded={open} onClick={() => setOpen(o => !o)}>
|
| 136 |
<span className="stone-band-head-left">
|
| 137 |
<span id={`band-h-${stone.key}`} className="stone-band-name">{stone.name}</span>
|
| 138 |
-
<span className="stone-band-role">
|
| 139 |
<span className="stone-band-tag">{stone.tag}</span>
|
| 140 |
</span>
|
| 141 |
<StoneAggregate stone={stone}/>
|
|
@@ -150,11 +201,15 @@ const StoneBand = ({ stone }) => {
|
|
| 150 |
};
|
| 151 |
|
| 152 |
const StoneTrace = () => {
|
|
|
|
|
|
|
|
|
|
|
|
|
| 153 |
return (
|
| 154 |
<div className="trace-ui-v44">
|
| 155 |
<header className="stone-trace-head">
|
| 156 |
<span className="section-label">Run trace Β· 5 Stones</span>
|
| 157 |
-
<span className="stone-trace-tally">
|
| 158 |
</header>
|
| 159 |
{STONES.map(s => <StoneBand key={s.key} stone={s}/>)}
|
| 160 |
</div>
|
|
|
|
| 1 |
+
/* Riprap v0.4.5 Β· Stone-banded trace.
|
| 2 |
+
Status enum (v0.4.5): fired / silent_by_design / warned / errored / not_invoked.
|
| 3 |
+
See V0.4.5_SPEC.md Β§1 for the rationale.
|
| 4 |
*/
|
| 5 |
|
| 6 |
const { useState: useStV44, useEffect: useEfV44 } = React;
|
|
|
|
| 10 |
key: "cornerstone", name: "Cornerstone", role: "the hazard reader",
|
| 11 |
tag: "what NYC's ground remembers",
|
| 12 |
members: [
|
| 13 |
+
{ id: "c1", name: "sandy_inundation.lookup", status: "fired", ms: 380, tier: "empirical" },
|
| 14 |
+
{ id: "c2", name: "usgs_hwm.spatial_join", status: "fired", ms: 460, tier: "empirical" },
|
| 15 |
+
{ id: "c3", name: "fema_firm.lookup", status: "fired", ms: 290, tier: "modeled" },
|
| 16 |
+
{ id: "c4", name: "dep_stormwater.lookup", status: "fired", ms: 540, tier: "modeled" },
|
| 17 |
+
{ id: "c5", name: "prithvi.historical_segment",status: "warned", ms: 1240, tier: "modeled",
|
| 18 |
warning: "deprecation: Prithvi-100M v1 β v2 migration scheduled 2026-Q3" },
|
| 19 |
],
|
| 20 |
},
|
|
|
|
| 22 |
key: "keystone", name: "Keystone", role: "the asset register",
|
| 23 |
tag: "what's exposed",
|
| 24 |
members: [
|
| 25 |
+
{ id: "k1", name: "mta_entrance_exposure", status: "silent_by_design", ms: 30, tier: "empirical",
|
| 26 |
+
note: "no entrances within radius" },
|
| 27 |
+
{ id: "k2", name: "nycha.development_join", status: "silent_by_design", ms: 28, tier: "empirical",
|
| 28 |
+
note: "no NYCHA developments within 1.0 mi" },
|
| 29 |
+
{ id: "k3", name: "doe.school_join", status: "silent_by_design", ms: 24, tier: "empirical",
|
| 30 |
+
note: "no DOE schools within 1.0 mi" },
|
| 31 |
+
{ id: "k4", name: "doh.facility_join", status: "silent_by_design", ms: 22, tier: "empirical",
|
| 32 |
+
note: "no acute-care hospitals within 1.0 mi" },
|
| 33 |
+
{ id: "k5", name: "pluto.lot_lookup", status: "silent_by_design", ms: 18, tier: "empirical",
|
| 34 |
+
note: "PLUTO join skipped: queried address not in NYC PLUTO dataset" },
|
| 35 |
],
|
| 36 |
},
|
| 37 |
{
|
| 38 |
key: "touchstone", name: "Touchstone", role: "the live observer",
|
| 39 |
tag: "what's happening now",
|
| 40 |
members: [
|
| 41 |
+
{ id: "t1", name: "floodnet.history", status: "fired", ms: 1240, tier: "empirical" },
|
| 42 |
+
{ id: "t2", name: "nyc311.flood_complaints", status: "fired", ms: 880, tier: "proxy" },
|
| 43 |
+
{ id: "t3", name: "noaa_coops.recent", status: "fired", ms: 410, tier: "empirical" },
|
| 44 |
+
{ id: "t4", name: "terramind.lulc", status: "fired", ms: 2100, tier: "synthetic" },
|
| 45 |
+
{ id: "t5", name: "prithvi_nyc_pluvial", status: "fired", ms: 1820, tier: "modeled" },
|
| 46 |
],
|
| 47 |
},
|
| 48 |
{
|
| 49 |
key: "lodestone", name: "Lodestone", role: "the projector",
|
| 50 |
tag: "what's coming",
|
| 51 |
members: [
|
| 52 |
+
{ id: "l1", name: "npcc4.slr_projection", status: "fired", ms: 320, tier: "modeled" },
|
| 53 |
+
{ id: "l2", name: "ttm_battery_surge.zero_shot",status: "fired", ms: 1500, tier: "modeled" },
|
| 54 |
+
{ id: "l3", name: "ttm_battery_surge.fine_tune",status: "fired", ms: 1480, tier: "modeled" },
|
| 55 |
+
{ id: "l4", name: "floodnet_forecast", status: "silent_by_design", ms: 14, tier: "modeled",
|
| 56 |
+
note: "sensor has only 2 historical events; forecast omitted (silent-floor: 5)" },
|
| 57 |
+
{ id: "l5", name: "ttm_311_forecast", status: "errored", ms: 0, tier: "modeled",
|
| 58 |
+
error: "311 history fetch failed: HTTP 503 at NYC OpenData (3 retries)" },
|
|
|
|
|
|
|
|
|
|
|
|
|
| 59 |
],
|
| 60 |
},
|
| 61 |
{
|
| 62 |
key: "capstone", name: "Capstone", role: "the synthesizer",
|
| 63 |
tag: "writes it all down with citations",
|
| 64 |
members: [
|
| 65 |
+
{ id: "p1", name: "granite.compose_briefing", status: "fired", ms: 3200, tier: "modeled" },
|
| 66 |
+
{ id: "p2", name: "mellea.grounding_check", status: "fired", ms: 480, tier: "modeled" },
|
| 67 |
+
{ id: "p3", name: "weasyprint.render_artifact",status: "fired", ms: 920, tier: null },
|
| 68 |
],
|
| 69 |
},
|
| 70 |
];
|
| 71 |
|
| 72 |
+
const fmtMs = (ms) => ms === 0 ? "β" : ms < 1000 ? ms + "ms" : (ms / 1000).toFixed(1) + "s";
|
| 73 |
const tierColor = (t) => t ? `var(--tier-${t})` : "var(--ink-tertiary)";
|
| 74 |
|
| 75 |
+
const flat04 = (members) => members.flatMap(m => m.children ? [m, ...flat04(m.children)] : [m]);
|
| 76 |
+
|
| 77 |
const StoneAggregate = ({ stone }) => {
|
| 78 |
+
const all = flat04(stone.members);
|
| 79 |
+
const fired = all.filter(m => m.status === "fired" || m.status === "warned").length;
|
| 80 |
+
const silent = all.filter(m => m.status === "silent_by_design").length;
|
| 81 |
+
const warn = all.filter(m => m.status === "warned").length;
|
| 82 |
+
const error = all.filter(m => m.status === "errored").length;
|
| 83 |
+
const notInvoked = all.filter(m => m.status === "not_invoked").length;
|
| 84 |
const ms = stone.members.reduce((acc, m) => Math.max(acc, m.ms || 0), 0);
|
| 85 |
return (
|
| 86 |
<span className="stone-band-agg">
|
| 87 |
<span className="stone-band-agg-num">{fired}</span> fired
|
| 88 |
{silent > 0 && <> Β· <span className="stone-band-agg-num">{silent}</span> silent</>}
|
| 89 |
{warn > 0 && <> Β· <span className="stone-band-agg-warn">{warn} warn</span></>}
|
| 90 |
+
{error > 0 && <> Β· <span className="stone-band-agg-err">{error} errored</span></>}
|
| 91 |
+
{notInvoked > 0 && <> Β· <span className="stone-band-agg-num">{notInvoked}</span> not invoked</>}
|
| 92 |
{" Β· "}<span className="stone-band-agg-ms">{fmtMs(ms)}</span>
|
| 93 |
</span>
|
| 94 |
);
|
|
|
|
| 109 |
</details>
|
| 110 |
);
|
| 111 |
}
|
| 112 |
+
if (m.status === "errored") {
|
| 113 |
+
return (
|
| 114 |
+
<details className="trace-row trace-row-error trace-row-errored" style={{ paddingLeft: indent }}>
|
| 115 |
+
<summary>
|
| 116 |
+
<span className="trace-bullet trace-bullet-errored">β </span>
|
| 117 |
+
<span className="trace-name">{m.name}</span>
|
| 118 |
+
<span className="trace-status trace-status-err">errored</span>
|
| 119 |
+
<span className="trace-tier" style={{ color: tierColor(m.tier) }}>{m.tier || ""}</span>
|
| 120 |
+
<span className="trace-ms">{fmtMs(m.ms)}</span>
|
| 121 |
+
<span className="trace-error-summary">{m.error}</span>
|
| 122 |
+
<span className="trace-error-expand" aria-hidden="true">click to expand</span>
|
| 123 |
+
</summary>
|
| 124 |
+
<div className="trace-error-body">
|
| 125 |
+
<div className="trace-error-line"><span className="trace-error-k">error</span><span>{m.error}</span></div>
|
| 126 |
+
<div className="trace-error-line"><span className="trace-error-k">retries</span><span>3</span></div>
|
| 127 |
+
<div className="trace-error-line"><span className="trace-error-k">elapsed</span><span>{fmtMs(m.ms)}</span></div>
|
| 128 |
+
</div>
|
| 129 |
+
</details>
|
| 130 |
+
);
|
| 131 |
+
}
|
| 132 |
+
if (m.status === "silent_by_design") {
|
| 133 |
+
return (
|
| 134 |
+
<div className="trace-row trace-row-silent-bd" style={{ paddingLeft: indent }}>
|
| 135 |
+
<span className="trace-bullet trace-bullet-silent">β’</span>
|
| 136 |
+
<span className="trace-name">{m.name}</span>
|
| 137 |
+
<span className="trace-status">silent</span>
|
| 138 |
+
<span className="trace-tier" style={{ color: tierColor(m.tier) }}>{m.tier || ""}</span>
|
| 139 |
+
<span className="trace-ms">{fmtMs(m.ms)}</span>
|
| 140 |
+
{m.note && <span className="trace-silent-note">{m.note}</span>}
|
| 141 |
+
</div>
|
| 142 |
+
);
|
| 143 |
+
}
|
| 144 |
+
if (m.status === "not_invoked") {
|
| 145 |
+
return (
|
| 146 |
+
<div className="trace-row trace-row-not-invoked" style={{ paddingLeft: indent }}>
|
| 147 |
+
<span className="trace-bullet trace-bullet-notinvoked">β«</span>
|
| 148 |
+
<span className="trace-name">{m.name}</span>
|
| 149 |
+
<span className="trace-status">not invoked</span>
|
| 150 |
+
<span className="trace-tier" style={{ color: tierColor(m.tier) }}>{m.tier || ""}</span>
|
| 151 |
+
<span className="trace-ms">β</span>
|
| 152 |
+
{m.note && <span className="trace-note">{m.note}</span>}
|
| 153 |
+
</div>
|
| 154 |
+
);
|
| 155 |
+
}
|
| 156 |
+
if (m.status === "warned") {
|
| 157 |
return (
|
| 158 |
+
<div className="trace-row trace-row-warned" style={{ paddingLeft: indent }}>
|
| 159 |
+
<span className="trace-bullet trace-bullet-warned" style={{ color: tierColor(m.tier) }}>β </span>
|
| 160 |
<span className="trace-name">{m.name}</span>
|
| 161 |
+
<span className="trace-status trace-status-warn">warned</span>
|
| 162 |
<span className="trace-tier" style={{ color: tierColor(m.tier) }}>{m.tier || ""}</span>
|
| 163 |
<span className="trace-ms">{fmtMs(m.ms)}</span>
|
| 164 |
+
<span className="trace-warn-sidemark" aria-hidden="true">!</span>
|
| 165 |
+
{m.warning && <span className="trace-warn-note">{m.warning}</span>}
|
| 166 |
</div>
|
| 167 |
);
|
| 168 |
}
|
| 169 |
+
/* fired */
|
| 170 |
return (
|
| 171 |
+
<div className="trace-row trace-row-fired" style={{ paddingLeft: indent }}>
|
| 172 |
+
<span className="trace-bullet trace-bullet-fired" style={{ color: tierColor(m.tier), background: tierColor(m.tier) }}>β </span>
|
| 173 |
<span className="trace-name">{m.name}</span>
|
| 174 |
+
<span className="trace-status">fired</span>
|
| 175 |
<span className="trace-tier" style={{ color: tierColor(m.tier) }}>{m.tier || ""}</span>
|
| 176 |
<span className="trace-ms">{fmtMs(m.ms)}</span>
|
|
|
|
| 177 |
{m.note && <span className="trace-note">{m.note}</span>}
|
| 178 |
</div>
|
| 179 |
);
|
|
|
|
| 182 |
const StoneBand = ({ stone }) => {
|
| 183 |
const [open, setOpen] = useStV44(true);
|
| 184 |
return (
|
| 185 |
+
<section className={`stone-band stone-band-${stone.key}`} aria-labelledby={`band-h-${stone.key}`} data-stone={stone.key}>
|
| 186 |
<button className="stone-band-head" aria-expanded={open} onClick={() => setOpen(o => !o)}>
|
| 187 |
<span className="stone-band-head-left">
|
| 188 |
<span id={`band-h-${stone.key}`} className="stone-band-name">{stone.name}</span>
|
| 189 |
+
<span className="stone-band-role"> Β· {stone.role}</span>
|
| 190 |
<span className="stone-band-tag">{stone.tag}</span>
|
| 191 |
</span>
|
| 192 |
<StoneAggregate stone={stone}/>
|
|
|
|
| 201 |
};
|
| 202 |
|
| 203 |
const StoneTrace = () => {
|
| 204 |
+
const all = STONES.flatMap(s => flat04(s.members));
|
| 205 |
+
const fired = all.filter(m => m.status === "fired" || m.status === "warned").length;
|
| 206 |
+
const silent = all.filter(m => m.status === "silent_by_design").length;
|
| 207 |
+
const error = all.filter(m => m.status === "errored").length;
|
| 208 |
return (
|
| 209 |
<div className="trace-ui-v44">
|
| 210 |
<header className="stone-trace-head">
|
| 211 |
<span className="section-label">Run trace Β· 5 Stones</span>
|
| 212 |
+
<span className="stone-trace-tally">{fired} fired Β· {silent} silent Β· {error} errored Β· 24.0s</span>
|
| 213 |
</header>
|
| 214 |
{STONES.map(s => <StoneBand key={s.key} stone={s}/>)}
|
| 215 |
</div>
|
|
@@ -1083,6 +1083,158 @@
|
|
| 1083 |
.stone-band, .stone-band-head, .treatment-tab { transition: none; }
|
| 1084 |
}
|
| 1085 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1086 |
/* βββββββββββ original v0.4.2 mobile rules ββββββββββ */
|
| 1087 |
@media (max-width: 720px) {
|
| 1088 |
.app-header-inner { grid-template-columns: 1fr; gap: 8px; }
|
|
|
|
| 1083 |
.stone-band, .stone-band-head, .treatment-tab { transition: none; }
|
| 1084 |
}
|
| 1085 |
|
| 1086 |
+
/* ============================================================
|
| 1087 |
+
v0.4.5 β Stone accent layer + new status enum rules
|
| 1088 |
+
Hint-only treatment: a 3-px left-rule per Stone, scoped to
|
| 1089 |
+
.stone-band[data-stone] and .f-region[data-stone]. Tier
|
| 1090 |
+
swatches inside cards/legend rows are unchanged.
|
| 1091 |
+
See V0.4.5_SPEC.md Β§2 for the full rationale.
|
| 1092 |
+
============================================================ */
|
| 1093 |
+
|
| 1094 |
+
/* 3-px left rule on Stone-banded sections */
|
| 1095 |
+
.stone-band[data-stone="cornerstone"] { border-left: 3px solid var(--stone-cornerstone); }
|
| 1096 |
+
.stone-band[data-stone="keystone"] { border-left: 3px solid var(--stone-keystone); }
|
| 1097 |
+
.stone-band[data-stone="touchstone"] { border-left: 3px solid var(--stone-touchstone); }
|
| 1098 |
+
.stone-band[data-stone="lodestone"] { border-left: 3px solid var(--stone-lodestone); }
|
| 1099 |
+
.stone-band[data-stone="capstone"] { border-left: 3px solid var(--stone-capstone); }
|
| 1100 |
+
|
| 1101 |
+
.f-region[data-stone="cornerstone"] { border-left: 3px solid var(--stone-cornerstone); }
|
| 1102 |
+
.f-region[data-stone="keystone"] { border-left: 3px solid var(--stone-keystone); }
|
| 1103 |
+
.f-region[data-stone="touchstone"] { border-left: 3px solid var(--stone-touchstone); }
|
| 1104 |
+
.f-region[data-stone="lodestone"] { border-left: 3px solid var(--stone-lodestone); }
|
| 1105 |
+
.f-region[data-stone="capstone"] { border-left: 3px solid var(--stone-capstone); }
|
| 1106 |
+
|
| 1107 |
+
/* Map Layers panel β Stone group dots + soft inset rule */
|
| 1108 |
+
.map-legend-stone {
|
| 1109 |
+
border-left: 2px solid transparent;
|
| 1110 |
+
padding-left: 10px;
|
| 1111 |
+
margin-bottom: 8px;
|
| 1112 |
+
}
|
| 1113 |
+
.map-legend-stone-cornerstone { border-left-color: var(--stone-cornerstone); }
|
| 1114 |
+
.map-legend-stone-keystone { border-left-color: var(--stone-keystone); }
|
| 1115 |
+
.map-legend-stone-touchstone { border-left-color: var(--stone-touchstone); }
|
| 1116 |
+
.map-legend-stone-lodestone { border-left-color: var(--stone-lodestone); }
|
| 1117 |
+
.map-legend-stone-capstone { border-left-color: var(--stone-capstone); }
|
| 1118 |
+
.map-legend-stone-head {
|
| 1119 |
+
display: flex; align-items: baseline; gap: 6px;
|
| 1120 |
+
margin: 8px 0 4px;
|
| 1121 |
+
font-family: var(--font-sans); font-size: 11px; letter-spacing: 0.04em;
|
| 1122 |
+
}
|
| 1123 |
+
.map-legend-stone-dot {
|
| 1124 |
+
width: 8px; height: 8px; border-radius: 50%;
|
| 1125 |
+
display: inline-block; align-self: center;
|
| 1126 |
+
}
|
| 1127 |
+
.map-legend-stone-dot-cornerstone { background: var(--stone-cornerstone); }
|
| 1128 |
+
.map-legend-stone-dot-keystone { background: var(--stone-keystone); }
|
| 1129 |
+
.map-legend-stone-dot-touchstone { background: var(--stone-touchstone); }
|
| 1130 |
+
.map-legend-stone-dot-lodestone { background: var(--stone-lodestone); }
|
| 1131 |
+
.map-legend-stone-dot-capstone { background: var(--stone-capstone); }
|
| 1132 |
+
.map-legend-stone-name { font-weight: 600; color: var(--ink); text-transform: uppercase; letter-spacing: 0.06em; }
|
| 1133 |
+
.map-legend-stone-role { color: var(--ink-tertiary); font-size: 11px; font-style: italic; font-family: var(--font-serif); }
|
| 1134 |
+
|
| 1135 |
+
/* Cold-start thesis row β colored dots beside Stone names */
|
| 1136 |
+
.cold-start-thesis-stone-dot {
|
| 1137 |
+
width: 7px; height: 7px; border-radius: 50%;
|
| 1138 |
+
display: inline-block; vertical-align: middle;
|
| 1139 |
+
margin-right: 5px; transform: translateY(-1px);
|
| 1140 |
+
}
|
| 1141 |
+
.cold-start-thesis-stone-dot-cornerstone { background: var(--stone-cornerstone); }
|
| 1142 |
+
.cold-start-thesis-stone-dot-keystone { background: var(--stone-keystone); }
|
| 1143 |
+
.cold-start-thesis-stone-dot-touchstone { background: var(--stone-touchstone); }
|
| 1144 |
+
.cold-start-thesis-stone-dot-lodestone { background: var(--stone-lodestone); }
|
| 1145 |
+
.cold-start-thesis-stone-dot-capstone { background: var(--stone-capstone); }
|
| 1146 |
+
|
| 1147 |
+
/* New status enum β replaces 0.4.4 anomaly/silent/warn/error rules.
|
| 1148 |
+
v0.4.5 statuses: fired, silent_by_design, warned, errored, not_invoked. */
|
| 1149 |
+
.trace-row { padding: 8px 0; font-family: var(--font-mono); font-size: 12px; display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
|
| 1150 |
+
.trace-row .trace-name { color: var(--ink); }
|
| 1151 |
+
.trace-row .trace-status { font-size: 10px; letter-spacing: 0.06em; text-transform: uppercase; color: var(--ink-tertiary); }
|
| 1152 |
+
.trace-row .trace-tier { font-size: 10px; letter-spacing: 0.04em; }
|
| 1153 |
+
.trace-row .trace-ms { color: var(--ink-tertiary); margin-left: auto; }
|
| 1154 |
+
.trace-row .trace-bullet { width: 12px; display: inline-block; text-align: center; }
|
| 1155 |
+
|
| 1156 |
+
.trace-row-fired { /* default β bullet color carries tier */ }
|
| 1157 |
+
|
| 1158 |
+
.trace-row-silent-bd .trace-name { color: var(--ink-secondary); }
|
| 1159 |
+
.trace-row-silent-bd .trace-bullet-silent { color: var(--ink-tertiary); }
|
| 1160 |
+
.trace-row-silent-bd .trace-silent-note { color: var(--ink-tertiary); font-style: italic; font-family: var(--font-serif); font-size: 12px; }
|
| 1161 |
+
|
| 1162 |
+
.trace-row-not-invoked .trace-name { color: var(--ink-tertiary); }
|
| 1163 |
+
.trace-row-not-invoked .trace-bullet-notinvoked { color: var(--ink-tertiary); }
|
| 1164 |
+
|
| 1165 |
+
.trace-row-warned {
|
| 1166 |
+
background: var(--status-warning-soft);
|
| 1167 |
+
/* keep stone left-rule; do NOT set border-left here */
|
| 1168 |
+
position: relative;
|
| 1169 |
+
padding-left: 8px;
|
| 1170 |
+
}
|
| 1171 |
+
.trace-row-warned .trace-status-warn { color: var(--status-warning); font-weight: 600; }
|
| 1172 |
+
.trace-row-warned .trace-warn-sidemark {
|
| 1173 |
+
color: var(--status-warning);
|
| 1174 |
+
font-weight: 700;
|
| 1175 |
+
margin-left: 4px;
|
| 1176 |
+
}
|
| 1177 |
+
.trace-row-warned .trace-warn-note { color: var(--ink-secondary); font-family: var(--font-serif); font-style: italic; font-size: 12px; }
|
| 1178 |
+
|
| 1179 |
+
.trace-row-errored {
|
| 1180 |
+
background: var(--status-error-soft);
|
| 1181 |
+
padding-left: 8px;
|
| 1182 |
+
}
|
| 1183 |
+
.trace-row-errored > summary { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; cursor: pointer; list-style: none; }
|
| 1184 |
+
.trace-row-errored > summary::-webkit-details-marker { display: none; }
|
| 1185 |
+
.trace-row-errored .trace-status-err { color: var(--status-error); font-weight: 600; }
|
| 1186 |
+
.trace-row-errored .trace-error-summary { color: var(--ink-secondary); font-style: italic; font-family: var(--font-serif); font-size: 12px; }
|
| 1187 |
+
.trace-row-errored .trace-error-expand { color: var(--ink-tertiary); font-size: 10px; letter-spacing: 0.04em; text-transform: uppercase; }
|
| 1188 |
+
.trace-row-errored[open] .trace-error-expand { display: none; }
|
| 1189 |
+
.trace-row-errored .trace-error-body {
|
| 1190 |
+
margin-top: 6px;
|
| 1191 |
+
padding: 8px 10px;
|
| 1192 |
+
background: rgba(255,255,255,0.6);
|
| 1193 |
+
border-left: 2px solid var(--status-error);
|
| 1194 |
+
font-size: 11px;
|
| 1195 |
+
display: grid; gap: 4px;
|
| 1196 |
+
}
|
| 1197 |
+
.trace-error-line { display: grid; grid-template-columns: 80px 1fr; gap: 8px; }
|
| 1198 |
+
.trace-error-k { color: var(--ink-tertiary); }
|
| 1199 |
+
|
| 1200 |
+
/* Tally text colors */
|
| 1201 |
+
.f-tally-warn { color: var(--status-warning); }
|
| 1202 |
+
.f-tally-err { color: var(--status-error); }
|
| 1203 |
+
.f-tally-notinvoked { color: var(--ink-tertiary); }
|
| 1204 |
+
|
| 1205 |
+
/* Hover link β when an evidence card is "linked" to a hovered map glyph */
|
| 1206 |
+
.f-card.is-linked,
|
| 1207 |
+
.finding-card.is-linked {
|
| 1208 |
+
outline: 2px solid var(--ink);
|
| 1209 |
+
outline-offset: 2px;
|
| 1210 |
+
}
|
| 1211 |
+
|
| 1212 |
+
/* TerraMind LULC class-mix bar (Touchstone synthetic-prior card) */
|
| 1213 |
+
.lulc-bar { display: flex; height: 12px; border-radius: 2px; overflow: hidden; margin-top: 6px; }
|
| 1214 |
+
.lulc-bar-seg { height: 100%; }
|
| 1215 |
+
.lulc-legend { display: flex; flex-wrap: wrap; gap: 8px 14px; margin-top: 8px; font-family: var(--font-mono); font-size: 11px; color: var(--ink-secondary); }
|
| 1216 |
+
.lulc-legend-swatch { display: inline-block; width: 8px; height: 8px; margin-right: 4px; border-radius: 1px; vertical-align: middle; }
|
| 1217 |
+
|
| 1218 |
+
/* Fine-tuned TTM trim line + hardware badge */
|
| 1219 |
+
.ttm-ft-trim { border-top: 1px solid var(--rule-soft); margin-top: 10px; padding-top: 8px; display: grid; gap: 4px; font-family: var(--font-mono); font-size: 11px; color: var(--ink-secondary); }
|
| 1220 |
+
.ttm-ft-row { display: grid; grid-template-columns: 110px 1fr; gap: 8px; }
|
| 1221 |
+
.ttm-ft-k { color: var(--ink-tertiary); }
|
| 1222 |
+
.ttm-hw-badge {
|
| 1223 |
+
display: inline-block; padding: 1px 6px; border: 1px solid var(--rule-soft);
|
| 1224 |
+
border-radius: 2px; font-size: 10px; letter-spacing: 0.04em;
|
| 1225 |
+
font-family: var(--font-mono); color: var(--ink-secondary); background: white;
|
| 1226 |
+
}
|
| 1227 |
+
|
| 1228 |
+
/* Print: drop accents to neutral, drop hover/error tints */
|
| 1229 |
+
@media print {
|
| 1230 |
+
.stone-band[data-stone],
|
| 1231 |
+
.f-region[data-stone],
|
| 1232 |
+
.map-legend-stone { border-left-color: #999 !important; }
|
| 1233 |
+
.cold-start-thesis-stone-dot,
|
| 1234 |
+
.map-legend-stone-dot { background: #999 !important; }
|
| 1235 |
+
.trace-row-warned, .trace-row-errored { background: transparent !important; }
|
| 1236 |
+
}
|
| 1237 |
+
|
| 1238 |
/* βββββββββββ original v0.4.2 mobile rules ββββββββββ */
|
| 1239 |
@media (max-width: 720px) {
|
| 1240 |
.app-header-inner { grid-template-columns: 1fr; gap: 8px; }
|
|
@@ -51,6 +51,16 @@
|
|
| 51 |
--leading-prose: 1.55;
|
| 52 |
--leading-tight: 1.25;
|
| 53 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 54 |
/* ββ Spacing ββ */
|
| 55 |
--s-1: 4px;
|
| 56 |
--s-2: 8px;
|
|
@@ -84,6 +94,16 @@ html, body {
|
|
| 84 |
border-radius: 1px;
|
| 85 |
}
|
| 86 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 87 |
@media (prefers-reduced-motion: reduce) {
|
| 88 |
*, *::before, *::after {
|
| 89 |
animation-duration: 0.01ms !important;
|
|
|
|
| 51 |
--leading-prose: 1.55;
|
| 52 |
--leading-tight: 1.25;
|
| 53 |
|
| 54 |
+
/* ββ Stone accent tokens (v0.4.5) ββ
|
| 55 |
+
Five muted hint-colors keyed to Stones. Lβ45 OKLCH, chroma β€0.04.
|
| 56 |
+
Hint-level decoration; never competes with the four-tier epistemic palette.
|
| 57 |
+
All five degrade to neutral gray in @media print (see below). */
|
| 58 |
+
--stone-cornerstone: #7C6F5E; /* warm taupe */
|
| 59 |
+
--stone-keystone: #5E6E7C; /* cool slate */
|
| 60 |
+
--stone-touchstone: #6B7C66; /* muted sage */
|
| 61 |
+
--stone-lodestone: #7C6E5E; /* softened ochre */
|
| 62 |
+
--stone-capstone: #5E5E6E; /* neutral indigo-gray */
|
| 63 |
+
|
| 64 |
/* ββ Spacing ββ */
|
| 65 |
--s-1: 4px;
|
| 66 |
--s-2: 8px;
|
|
|
|
| 94 |
border-radius: 1px;
|
| 95 |
}
|
| 96 |
|
| 97 |
+
@media print {
|
| 98 |
+
:root {
|
| 99 |
+
--stone-cornerstone: #999;
|
| 100 |
+
--stone-keystone: #999;
|
| 101 |
+
--stone-touchstone: #999;
|
| 102 |
+
--stone-lodestone: #999;
|
| 103 |
+
--stone-capstone: #999;
|
| 104 |
+
}
|
| 105 |
+
}
|
| 106 |
+
|
| 107 |
@media (prefers-reduced-motion: reduce) {
|
| 108 |
*, *::before, *::after {
|
| 109 |
animation-duration: 0.01ms !important;
|