octoturge ba2eb4d379 Make the console speak the backend's telemetry protocol
The telemetry store and the printer card described a protocol no backend
ever spoke. The store expected `{ type, data: { printerId, progressPct,
state } }` with a closed five-value state enum; `/ws/telemetry` sends
`{ type: "printer.telemetry", printer: {...} }` with `id`,
`progressPercent` and an open `status`, and prefaces it with a `ready`
frame. The card also displayed `printer.model`, which the printer
resource has never had - it has `vendor`.

Authentication was the other half: the socket now connects with the
subprotocol list `["bearer", token]`, which is what the backend gates
the upgrade on, and never a query parameter. The token is read from the
session on each attempt, because supabase-js refreshes it hourly and a
reconnect replaying the first token would work right up until it
mattered. With no session it does not connect at all, and app.vue drives
connect/disconnect from the session rather than a one-shot onMounted.

Reconnection is now backed off from 1s to 30s instead of a flat 3s. A
browser cannot see why an upgrade failed - a rejected token and an
unreachable server are both just onerror then onclose - so a token the
server will never accept previously retried every 3s for as long as the
tab stayed open.

`disconnect()` no longer reconnects the socket it just closed, via a
`wanted` flag; handlers from a replaced socket are ignored.

Null is no longer rendered as zero. A claimed printer whose gateway has
never reported has no temperature, and 0 degrees is a different claim:
null readings stay out of the graph's history, the labels show a dash,
the progress bar is omitted rather than drawn empty, and a null status
reads "unknown" rather than the old "offline", which asserted something
nobody had observed. `lastSeenAt` is shown, since it is what separates
an idle printer from one nothing has heard from in an hour.

The card takes one `printer` prop instead of a row plus a `live`
overlay - the push and the REST row are the same shape by design, so
pages/index.vue replaces one with the other and @continuum/ui stays
plain Vue that knows nothing of stores or the API. Status styling now
covers all six values of the backend's printer_status enum; paused and
maintenance previously drew as ordinary text.

Types are hand-mirrored in packages/ui/src/types.ts from
continuum-schemas, the convention this project already uses across
TS and Rust. @continuum/ts-types is in another repository and is not
resolvable here; a `link:` out of the repo would break on any checkout
not laid out like this machine's. Field names are verified identical to
the schema's.

Typechecks clean (nuxt typecheck, vue-tsc). Not exercised at runtime;
the protocol it now implements is the one verified live by
continuum-backend's scripts/e2e-edge.sh.
2026-10-18 19:54:28 +00:00

continuum-app

Operator console for the Continuum print farm platform. A single Nuxt 3 SPA (apps/web) shared between the browser and a Tauri 2 desktop shell (apps/desktop), plus a shared component library (packages/ui).

Layout

apps/
  web/        Nuxt 3 SPA (ssr: false) - Supabase auth, Pinia telemetry store
  desktop/    Tauri 2 shell wrapping apps/web's static build
packages/
  ui/         Shared Vue components: PrinterCard, TemperatureGraph, WebRTCStreamView

Getting started

pnpm install
cp .env.example apps/web/.env
pnpm dev:web       # browser dev server on :3000
pnpm dev:desktop    # Tauri dev shell (requires Rust toolchain)

apps/web builds as a static SPA so the exact same apps/web/.output/public bundle is served in the browser and embedded into the Tauri desktop app.

S
Description
Continuum platform: continuum-app
Readme
268 KiB
Languages
Vue 76.7%
TypeScript 22.4%
Rust 0.9%