Skip to content

feat(ui): a React workspace UI over the hflow-server API - #87

Draft
kstonekuan wants to merge 2 commits into
mainfrom
workspace-ui
Draft

feat(ui): a React workspace UI over the hflow-server API#87
kstonekuan wants to merge 2 commits into
mainfrom
workspace-ui

Conversation

@kstonekuan

@kstonekuan kstonekuan commented Aug 22, 2026

Copy link
Copy Markdown
Contributor

The server already merged (#116). This PR is now only ui/ and the one CI job that builds it — 52 files, no server code — so it can be judged as a frontend on its own.

Still a draft on purpose. The open question is not whether it works, it is whether this is the UI we want. hflow-server publishes a typed schema at /api/openapi.json and nothing is reachable only from a browser, so this app can be rewritten, replaced, or joined by a second client without touching the server. That choice is deliberately reversible, which is why the server did not wait on it.

The first client of the workspace API.

  • Episodes — the corpus as a faceted, sortable table over the catalog's wide episodes view. Every filter compiles to DuckDB SQL server-side; the page renders JSON.
  • Episode dossier — one episode's measurements, check runs, intervals, tags and media, with a timeline over its recorded evidence.
  • Curate — SQL with instant previews, saved queries, and manifest pinning.
  • Runs — the ingest DAG drawn from the served topology, with one run's live per-task state.
  • Pipeline — the generated DAG plus the registered steps, drawn inside the process_batch node that runs them. They have no dependency edges on each other, so the graph nests them rather than inventing a chain, and says so.

Strictly a client: no computation here that the server could not do, and nothing reachable that curl could not reach.

Offline like the server — no CDN, no telemetry, no runtime downloads. Built assets are copied into hflow_server/static/ at package time, which the CI job here verifies actually happens.

React 19, Vite 8, react-router 7, TanStack Query and Table, Radix, CodeMirror 6, lucide. pnpm check and pnpm build clean.

Known follow-up

ui/src/api.ts hand-writes 64 interfaces mirroring the server's contract, and they have already drifted — the server declares kind: StepKind, the copy says kind: string, and most of the contract's 10 Literal unions arrive as bare string. These should be generated from /api/openapi.json instead. That is the change that makes a second UI cheap, so it matters more than it looks.

🤖 Generated with Claude Code

@kstonekuan kstonekuan changed the title feat: workspace UI (M0) — read-only episode browser feat: workspace UI — hflow ui (episodes, runs, pipeline, curation) Aug 22, 2026
@kstonekuan
kstonekuan marked this pull request as ready for review August 22, 2026 04:03
@kstonekuan
kstonekuan marked this pull request as draft August 22, 2026 06:39
@kstonekuan kstonekuan changed the title feat: workspace UI — hflow ui (episodes, runs, pipeline, curation) feat(ui): a React workspace UI over the hflow-server API Aug 22, 2026
@kstonekuan
kstonekuan changed the base branch from main to workspace-api August 22, 2026 19:06
@kstonekuan
kstonekuan force-pushed the workspace-api branch 2 times, most recently from 9d12213 to 91326e9 Compare August 23, 2026 01:02
Base automatically changed from workspace-api to main August 23, 2026 01:05
kstonekuan and others added 2 commits August 22, 2026 18:10
The first client of the JSON API: episodes as a faceted table over the wide
catalog view, an episode dossier with its measurement timeline and media, a
curation studio with SQL previews and pinned manifests, a runs monitor with
the live DAG, and a pipeline page that draws each stage's registered steps
inside the process_batch node that runs them.

Strictly a client. Every screen is a rendering of a documented endpoint, no
computation happens here that the server could not do, and nothing is
reachable that `curl` could not reach -- so this app can be replaced, or
joined by another, without touching the server.

Offline like the server: no CDN, no telemetry, no runtime downloads. Built
assets are copied into hflow_server/static/ at package time, which the CI job
here verifies actually happens.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…PI schema

`api.ts` declared 64 interfaces mirroring the server's contract by hand, and
they had drifted with nothing to catch it:

- `WorkspaceConfig` still read `hflow_ui_version`, a field the server no
  longer sends -- the version line in the rail rendered undefined.
- `WorkspaceCapabilities` never learned about `curation`, so the client could
  not gate on the capability the server publishes for it.
- Of the contract's 10 `Literal` unions, most arrived as bare `string`:
  the server says `kind: StepKind`, the copy said `kind: string`.

`pnpm gen:api` now generates `src/apiSchema.ts` from the server's own
/api/openapi.json, and the payload types are aliases of it. One owner, and
regenerating is a diff instead of an audit. CI regenerates and fails on a
dirty tree, so the two cannot drift again.

Aliasing them surfaced 50 type errors, every one a place the client assumed a
field the server declares nullable. They are fixed here, not silenced:

- Timeline intervals carry `start_s`/`end_s` only when the server could derive
  the episode's start. Unplaceable intervals are separated out and COUNTED in
  the legend rather than plotted at zero.
- Interval rows type every bound nullable, so the relative-seconds table
  renders an em dash instead of computing against `Math.min()` of nothing.
- A task instance or run with no id belongs to no node and cannot be keyed or
  deep-linked; those are skipped at the grouping boundary, with the reason.
- `formatTimestamp`, `shortFingerprint`, `StatusChip` and `VersionChip` take
  the null the server can send and render the same em dash as every other
  absent value.

One narrowing remains by necessity: `PipelineResponse.manifest` is declared
`dict[str, Any]` server-side because hflow.manifest owns that shape and it is
forwarded verbatim, so the generated type is an open object and the local
`PipelineManifest` fills the hole. Typing it server-side would delete that.

Also fixes .gitignore, which still pointed at the pre-rename asset path and so
let three built files be committed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant