Listening for events…

Displaying Dexes on the Web

How to put TerraPulse's dexes in front of a website viewer — and why the way the data is already sorted does most of the work.

Status: design note / proposal. Nothing here is built yet (see What it takes to build). It exists so that when we do build a public dex surface, the shape is decided once and stays consistent across all six families. Companion to docs/dex-data-model.md (the model) and docs/design-handoff.md (the house look).


The one idea

A dex is already a website. Every dex is an indexed list of slots, and each slot holds exactly one thing — one earthquake, one year of dairy, one tide station, one asteroid. That is the same structure every good data site converges on by hand:

  • a roster you can scan (the list of things), and
  • a detail page you open for one thing (everything known about it).

We don't have to design that split — the storehouse already built it. Each kind keeps a storehouse_index.json (the roster) rebuilt from a directory of per-slot dossiers (the detail records). The roster row is already a summary card: it carries the slot's name, how many observations it has accreted, how many sensor hits it drew, and its time span. The dossier is already a detail page's worth of content: identity, intrinsic properties, the full time-series the slot accreted, and cited provenance.

So the job isn't "figure out how to present this data." The job is "point the site's existing visual vocabulary at a structure that was built to be walked."


Two views fall out for free

Storehouse artifact Becomes Existing site analogue
storehouse_index.json roster (array of slot summaries, sorted by last_fix_utc) Roster view — a scannable list/grid of every slot in a kind web/src/pages/articles/index.astro (dotted-leader list) + the features.ts card grid
One per-slot dossier (data/<family>_storehouse/<kind>/<slot_id>.json) Slot view — one page per thing, the fat record a single web/src/pages/articles/<slug>.astro

That is the whole architecture. Everything below is how to dress those two views in the house style.


Map the data model to the page

The dossier is layered (spelled out in docs/celestialdex-framework.md and docs/movingplatformdex-framework.md). Each layer has an obvious home on the page:

Dossier layer What it is Where it goes on the page
Identity event_id, name, designation, catalog id, kind Hero title + eyebrow. This is the <h1> and the "TerraPulse Research — " tag.
Intrinsic properties what the thing is — orbital elements, cutoff rigidity, EF rating, station altitude The 4-big-stat strip (the site's signature grid-cols-2 md:grid-cols-4 band). Estimated values get a soft badge.
The time-series array the ordered series the slot accreted — close approaches, decay track, flux history, a platform's track The star of the page. One chart / map / timeline. This is what makes a dex page more than a fact sheet.
Provenance / sources the cited sources folded into this one slot A source-chip row + a footer credit. Every datum traces back — see [feedback_provenance_every_datum].
Provisional / soft flags WARN:-level data-quality notes, has_series=false, provisional=true Small honest badges ("registry-only", "ingestion in progress"), never hidden.

The roster row is the same content, pre-summarized. event_storehouse._index_entry() already computes per slot: name, n_fixes, total_sensor_hits, first_fix_utc, last_fix_utc. That is a card without writing a card — a title, two big numbers, and a date range.


The roster view

A page per kind: every slot, scannable. Two ready-made layouts, both already on the site:

  • Dotted-leader list (like articles/index.astro) — name … date range, with the count and a one-line descriptor beneath. Best for text-heavy kinds and long rosters.
  • Card grid (like the features.ts feature cards) — each slot a tile with its name, its headline stat (n_fixes or the intrinsic marquee number), and a span. Best for kinds where each slot has a strong single number.

What the roster gives the viewer that a paper never could: the whole population at once. All 16,377 catalogued objects. All 154 magnetic observatories. All 431 gravitational-wave chirps. Sorted, counted, and each one a door. Controls to add, in rough priority:

  1. Sort — by last_fix_utc (default, "what moved most recently"), by name, by the marquee stat.
  2. Search / filter — by name, by kind_subtype, by a property band (magnitude, rigidity, year).
  3. A roster headline — the site's 4-stat strip applied to the kind: total slots, date span, most-recent update, source count. The kinds counts dict in the index gives you the totals for free.

The slot view (dossier detail)

One page per slot, and it mirrors an article page exactly:

  1. Hero — identity. Big serif <h1> with the slot name, the pulsing-dot eyebrow, the byline (kind · span · sources).
  2. Stat strip — the intrinsic properties as four big numbers. This is where the StatCard component earns its keep.
  3. The series — the accreted time-series, visualized. A decaying satellite's altitude falling over 72 days; a station's flux history; an asteroid's close approaches on a timeline. This is the payload; give it the width.
  4. Provenance footer — the cited sources, the DuckDB/PostgreSQL lineage, the honest flags.

The reason this is compelling and not just tidy: a slot page shows one real thing accumulating measured reality over time. That's a narrative the reader can hold, and it's true by construction — the dex only ever organizes measured data ([feedback_measured_reality_only]).


Per-family display idioms

The two-view architecture is universal; the primary visualization differs by what a slot is. Lead with the idiom that matches the family:

Family A slot is Primary visual Roster shines as
Eventdex (eq, tor, gw) one discrete event point on a map + a moment on a timeline a time-sorted feed — "what happened, when"
Yeardex (dairy, landuse) one year of a subject the value on a year axis; small-multiples across years a settled historical series — frozen, complete
Locationdex (neutron_monitor, tide_gauge) one fixed place a map of pins; the station's series on click a network map — the single most obviously compelling roster
CelestialObjectDex (neo, satcat, satellite_decay) one celestial object orbit / decay track (no Earth map — lat/lon are null) a catalogue you can page through by property
YearLocationdex (drought, water-quality) one place × period cell a choropleth or place×time heatmap a grid you can slice by year or by place
MovingPlatformDex (mobile_radiosonde) one moving platform + its track the track drawn on a map a fleet of paths

Locationdex is the recommended first build: "154 stations as pins on a map, click one to see its measured series" is instantly legible, needs no explanation, and shows off the provenance model (each pin cites its network).


Live vs frozen: the velocity badge

docs/paper-taxonomy.md splits data by velocity — fast-moving live streams vs slow-moving frozen aggregates. The dex framework draws the same line ("events are live, years are frozen") and every live kind carries a refresh_and_store() "live edge" on a scheduler cadence. Surface it:

  • Live-edge kinds get the site's existing pulsing accent dot and a "live" badge — the roster is a standing monitor, updating on its cadence.
  • Frozen kinds get a quiet "complete through <year>" stamp — the roster is a closed historical record.

This one badge tells the viewer whether they're looking at a heartbeat or an archive, which is exactly the expectation-setting the taxonomy exists to provide.


Design constraints to inherit (non-negotiable)

Any dex surface must adopt the house rules from docs/design-handoff.md:

  • Chonky / high-contrast is intentional. A primary user has low vision. Big type, the 4-stat idiom, WCAG AA (≥ 4.5:1 text, ≥ 3:1 large). When in doubt, bolder and larger, never finer or denser. Do not regress this for the sake of fitting more slots on screen — paginate instead. (See [feedback_chonky_accessibility].)
  • The type system: Libre Baskerville display serif, Montserrat body, JetBrains Mono for all data and timestamps — slot ids, coordinates, counts, and UTC times render in the mono face.
  • Provenance on every datum. No number appears without a traceable source; the slot view's footer is the natural home for the lineage ([feedback_provenance_every_datum]).
  • Measured reality only. If a kind ever carries a modelled field (e.g. a CME's WSA-ENLIL arrival forecast), it must be visibly flagged as model context, never shown as a measured value ([feedback_measured_reality_only]).

What it takes to build

Today there is no HTTP surface for dexes. The only read path is the in-process Python library src/terrapulse/monitor/dex_reader.py (read_slots, read_aggregate, read_archive), built for the paper repoint campaign. The website's only research surface is the hand-authored /articles Astro pages driven by features.ts. So the enabling work, smallest-first:

  1. A roster endpointGET /api/v1/dexes/<kind> returning the index roster (thin wrapper over dex_reader.read_slots(kind), projected to the summary columns). This is the single unlock; everything visual sits on top of it.
  2. A slot endpointGET /api/v1/dexes/<kind>/<slot_id> returning one dossier.
  3. Two Astro page templates — a roster template and a slot template, per the two views above, reusing FeatureEyebrow, StatCard, and the hero/stat-strip vocabulary.
  4. Static vs live generation — frozen kinds can be static-generated at build time (they don't change); live-edge kinds fetch the roster at request time so the "live" badge tells the truth.

Suggested rollout

Prove the pattern on one Locationdex kind (neutron_monitor, 64 stations) — a map of pins backed by the roster endpoint, each pin opening a slot page with its measured flux series. It's small, it's visually self-explanatory, and it exercises every piece (roster + slot + provenance + the live badge) without committing the whole platform. Once that page feels right, the same two templates carry every other kind — because every other kind is already sorted the same way.


References

  • docs/dex-data-model.md — the slot model and the six families.
  • docs/celestialdex-framework.md, docs/movingplatformdex-framework.md — the layered dossier shape.
  • docs/paper-taxonomy.md — data velocity × window type (the live/frozen badge).
  • docs/design-handoff.md — palette, type, and the accessibility floor.
  • src/terrapulse/monitor/dex_reader.py — the read library a roster API would wrap.
  • src/terrapulse/monitor/event_storehouse.py_index_entry(), the roster-row fields.
Live Feed