Commit Graph

2 Commits

Author SHA1 Message Date
Indiana
917cd7f89e feat: Cold Spot Detector / Atmospheric Disturbance Index on Device Bay
Real-time anomaly visualization for temperature/pressure readings on the
device-feed dashboard, folklore's two most iconic paranormal markers:
sudden cold spots and rapid barometric swings.

- lib/coldSpot.ts: pure, directly-testable rolling-baseline tracker
  (time-aware EMA, since hardware doesn't report on a fixed schedule),
  cold-spot and pressure-anomaly classifiers, and a composite
  Atmospheric Disturbance Index that rewards correlated anomalies
  (a lone signal caps at 50/100; only both deviating together can
  reach 100) — modeled on evilMeter.ts's threaded-state pattern.
- components/ColdSpotPanel.tsx/.css: frost treatment + sparkline for
  temperature, ripple treatment for pressure, and a crescent-arc
  composite gauge (GhostLog's evil-meter gauge as the visual family
  reference) that only appears once a device has reported both sensors.
- DevicesPage.tsx: owns per-device baseline state, feeds it from
  `reading` frames, falls back to the existing generic row for any
  non-numeric temperature/pressure value.

26 new coldSpot.test.ts cases (warm-up, genuine vs. fluctuation,
correlated-vs-solo index, gappy/out-of-order data) and 7 new
DevicesPage integration tests. Full suite: 302/302 passing, tsc clean,
i18n coverage clean (en/es).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-24 22:00:35 +00:00
Indiana
e886c94d40 feat(frontend): device pairing UI + live telemetry dashboard (Workstream H)
Adds /devices — pair an ESP32 sensor node (POST /api/device), reveal its
raw pairing token exactly once with a hard-to-miss "cannot be shown again"
warning (styled like a real API-key-reveal UI), then a live dashboard
subscribing to /ws/device-feed: the initial `devices` frame seeds paired
devices, and `reading` frames update one row per distinct sensor_type in
place. sensor_type/value/unit are rendered fully generically per the
contract (free-form, open-ended) — an unrecognized sensor_type renders
safely with no special-casing.

- frontend/src/lib/deviceFeed.ts: reconnecting WS client for
  /ws/device-feed, mirroring VeilSocket's backoff shape (receive-only, no
  outbox needed).
- frontend/src/pages/DevicesPage.{tsx,css}: pairing form + one-time token
  reveal + live device-card grid. Leans into "hacker" terminal styling
  (monospace readouts, terminal device cards) over the app's usual gothic
  chrome, per the design spec, while keeping the existing dark/violet
  palette tokens from App.css.
- Route + nav link wired into App.tsx / SeancePage.tsx.
- i18n: new `devices.*` / `nav.devices` keys in en.json + es.json; added a
  coverage-check.mjs domain rule for the dynamic connection-state key,
  mirroring the existing `seance.connection.` rule.

Tests: deviceFeed.test.ts (backoff/reconnect/frame delivery) and
DevicesPage.test.tsx (empty state, name validation, one-time token reveal
and dismissal, live frame updates in place without duplicating rows,
multi-device/multi-sensor rendering, and a mocked unrecognized sensor_type
that must not crash). Full suite: 154 passed (137 pre-existing + 17 new).
`npx tsc -b` and `npm run build` both clean.

Assumption (undocumented in spec): POST /api/device's JSON response shape
is inferred as `{id, name, token, last_seen_at}` since the Contract section
only describes the endpoint in prose. GET /api/device is intentionally not
called — the live dashboard is fully seeded by /ws/device-feed's initial
`devices` frame per the contract, so it's redundant for this page's scope.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-24 01:10:24 +00:00