Skip to content

Overview

The UI library (@clockwyrks/ui, in packages/ui) is the shared frontend code for The Test Cabinet’s two GUIs: the public site and the web console. It hosts the entire routed gallery application plus the primitives those GUIs render, so both are thin hosts over one application.

It ships no service and runs in no process of its own. Each GUI mounts the shared app inside its own router and supplies it a data source, and the two hosts differ only in where that data comes from and which capabilities they enable.

A host imports only the entries it needs.

EntryContents
@clockwyrks/uiPresentational primitives, the rating model, and model-id helpers.
@clockwyrks/ui/appThe full routed gallery application and its data context.
@clockwyrks/ui/clientThe transport-agnostic client interfaces and their React contexts.
@clockwyrks/ui/transportThe HTTP transports the web console mounts.
@clockwyrks/ui/tokens.cssThe --tcab-* theme token defaults.

One routed application serves every host. It covers the gallery a reader browses, the run-execution surface an operator launches, watches, reviews and publishes runs from, and the notification subsystem. It reads its data and its capabilities from context, so a host varies it by what it provides rather than by swapping screens. Route paths are built through the exported route builders, so each path is defined in one place.

Each host builds the gallery data value from its own source. The static site builds it from the build-time public snapshot. The web console builds it live from a backend.

A canExecute flag on that value gates the run-execution surface, including authentication and notifications. Optional capability members gate the rest. arena is present on a host that can run adversarial matches and tournaments. A host that omits one hides the corresponding surface.

The value also resolves a run’s media to loadable URLs, whichever host is asking: proof-of-implementation media, an asset-generation run’s regenerated, target and preview images with its action log, a voxel run’s per-part .glb and rig.json, a particle run’s system.json, and case-scoped validation baselines. The site resolves these from snapshot assets, a console from the backend for a published run and from the artifact service for a produced one.

A run’s inputs resolve one variant of one exact case version rendered for one engine, using the run’s own recorded version and engine. A console resolves them from the backend’s version and specs routes. The site resolves them from the snapshot’s case document for that version.

Listing pages are answered through a paged, filtered, sorted query the host implements. A console forwards it to the backend’s offset endpoint. The site answers it from its in-memory summary index with the same semantics, so paging is identical on either host.

A test case’s detail page is anchored to one selected coordinate: a version, a variant of that version, and an engine that version supports. The coordinate lives in the URL and travels across the whole page, so everything describing the deliverable describes the same one and any selection is a link someone else can open. Selection is canonical: an unknown version resolves to the latest, and a variant or engine the selected version does not declare resolves to that version’s default. A run is launched against the resolved coordinate.

A description ships with the version it describes, so the page shows the anchored version’s own description. While an older version is anchored, the page names the latest version and links to it, and the link re-anchors the page to that version.

The page’s run aggregations scope relative to the anchored coordinate rather than selecting one of their own, and that scope lives in the URL alongside it. A view widened across engines lists each engine separately rather than folding them, since runs under different engines measure different work.

A case’s changelog and errata cover every version regardless of the anchor.

Every listing of runs renders one shared log. A listing that includes runs still in flight lists them apart from the finished rows, since a run with no record yet has nothing to sort or page by.

A run’s duration counts from its startedAt and from nothing else, which keeps queued time out of it, and the count begins when the run reaches starting. It advances off one clock the whole log shares, and the log holds that clock only while a duration is moving.

An unpublished run is deletable and a published one is not, which is the rule the backend enforces and the only one there is. A surface offering the action decides from the run’s own publish state, read off the record or the summary card it has already resolved, rather than from the console’s produced worklist — that worklist is a cache of the runs it has caught up with, and it lags a run whose record is still being written, which a canceled run’s is for as long as its driver takes to stop the harness, drain telemetry and hand the partial record back. A surface holding nothing but a run id falls back to the worklist.

The affordance is hidden only where the host can delete no run at all, and is shown disabled with its reason wherever the host could delete but this run currently cannot be, so a state that will pass on its own is visible rather than absent.

A cancellation’s follow-up read is deferred accordingly: every refresh fired when the cancel returns is premature, so the console watches the canceled runs for the records their drivers hand back and re-reads as they land, whether the cancellation came from one run’s control or from a bulk sweep.

A produced asset is rendered interactively rather than as a still image. A voxel run mounts the voxel runtime’s rig in a React Three Fiber canvas, where its joints are posed and its model-authored animations played. A particle run mounts the particle runtime’s player and simulates the effect live. Each 3D view falls back to the emitted preview image where WebGL is unavailable or reduced motion is requested, so a run stays reviewable.

./client declares the backend and worker client interfaces the console is written against, plus the React contexts that supply them and the authentication context. The app depends only on these interfaces.

./transport is the single implementation of the backend wire protocol: the HTTP backend and execution clients, the HTTP arena client, and the helpers that read the artifact, arena, snapshot, and Grafana URLs the backend reports from GET /config. The web console mounts these transports.

A produced run’s artifacts do not change once created, so the app resolves each by URL through a process-wide cache that survives a component unmounting. Leaving a view and returning to it re-reads the cache rather than the network, and a cache hit renders without a loading state. A failed fetch is evicted from the cache so it is retried.

A surface shows a loading state while its data is in flight, and names an entity as not found once the read has settled without it. A read that failed is reported as a failure, distinct from both.

Those three states decide only what a surface with nothing to show renders. Data already resolved is rendered whatever the latest read did: a read that failed over a list already on screen is stale data, reported beside the rows rather than in place of them. A source keeps what it has resolved rather than emptying it on a refresh that failed, and a surface reads its own data before it reads that source’s load state.

A resolver reports the same three outcomes to the surface above it. It resolves to nothing only where the store answered and holds no such entity — the store’s own 404 — and fails for every other unanswered read, so no host turns an unreachable store into an absence.

A form’s numeric input holds what the operator typed, including nothing at all, so a value is replaced by clearing the field and typing. The form reports an empty or out-of-range field as invalid and refuses to submit it, and the backend validates the same bounds.

Every save, launch and publish reports its outcome, covering the failure that stopped it and the progress and success of one that ran.

Every question a destructive control asks is asked through the themed modal rather than the browser’s own alert() and confirm(), so a destructive action confirms before it runs and its question carries more than a line of plain text.

The dialog is modal and its confirmation is awaited at the call site. Both a confirmation and an alert may carry details beyond the question itself.

Components are themed through the --tcab-* CSS custom properties. The tokens.css entry supplies working defaults, and an app may override any property in its own global styles. Every palette value comes from a token.