Skip to content

feat: a showcase page for the archive, and export --ui to write it - #75

Merged
khaneight merged 1 commit into
mainfrom
feat/ui
Aug 27, 2026
Merged

feat: a showcase page for the archive, and export --ui to write it#75
khaneight merged 1 commit into
mainfrom
feat/ui

Conversation

@khaneight

Copy link
Copy Markdown
Owner

Stacked on #74. The interface: published work, the graph, and the work in flight.

sentinel export --out ./content --flat --ui ./showcase
# → showcase/index.html + showcase/bundle.json

Two files. Copy them to a subdomain, an S3 bucket, a docs/ folder — no build
step, no dependencies, one network read (its own JSON). It works offline, under
a strict content policy, and in five years.

Why one HTML file rather than a Vite/React app

You said the host should be flexible. A build toolchain in a Rust repository is
a second dependency tree to keep alive, and this is a graph viewer over a few
hundred nodes — canvas and forty lines of Fruchterman-Reingold do it. If you'd
rather have React, say so and I'll build it; --data already emits the bundle
for anyone who wants to render it themselves.

What it shows

  • the graph, nodes sized by how connected they are
  • what the clone writes from — affirmed traits, evidence behind each, and
    what has been written from it
  • what is in flight — unpublished, awaiting your approval, unconfirmed
    traits, concepts the wiki has named and not written
  • growth — articles and links over time, from meta/progress.jsonl

Work the clone wrote is never rendered as though you wrote it. Different
colour, a count called out at the top, and on selection a note naming the claims
it was written from. The bundle carries extrapolated on every node for exactly
this, and the page branches on it in words, not only in a hue.

Keeping a front end from rotting

Nothing compiles a page like this, and a renamed field in the exporter turns a
panel blank rather than failing anything. So the page declares every field it
reads in one CONTRACT object; it uses that at runtime to say which key is
missing
instead of rendering an empty panel, and tests/ui.rs parses it out of
the HTML and asserts it against a bundle from the real command.

I checked the guard bites — renaming extrapolated to extrapolatd:

the page reads `node.extrapolatd`, which the bundle does not publish.
Fields present: {"domain", "extrapolated", "inbound", ...}

Also asserted: the page is self-contained (no external URL, exactly one
fetch), --ui writes both files, the written page is byte-identical to the
one in the tree, --dry-run writes nothing, and the bundle respects the
approval gate — an unapproved article never appears in the graph.

Two defects found by opening it, not by testing it

Five nodes huddled in the middle of a 1400px canvas: the ideal edge length was a
constant tuned against one archive. Deriving it from the available area and node
count fixed that and then overshot, stringing five nodes across the whole
stage — which fills the space and is much harder to read than a compact cluster.
Bounded above as well as derived.

531 tests (+6), clippy and fmt clean.

🤖 Generated with Claude Code

One self-contained HTML file plus its bundle. `sentinel export --ui <dir>`
writes both; copy them anywhere static. No build step, no dependencies,
one network read — its own `bundle.json` — so it works offline, under a
strict content policy, and in five years when whatever framework was
fashionable today is not.

It shows what a static site generator cannot show from markdown alone:
the link graph, the persona traits the clone writes from, what is still
in flight (unpublished drafts, work awaiting approval, unconfirmed
traits, concepts the wiki has named and not written), and the growth
curve from `meta/progress.jsonl`.

**Articles the clone wrote are never rendered as though their author
wrote them.** Different colour, a count called out above the panel, and
on selection a note naming the claims the piece was written from. The
bundle carries `extrapolated` on every node for exactly this, and the
page branches on it in words rather than only in a hue.

A front end is the easiest thing in a repository to let rot — nothing
compiles it, and a renamed field turns a panel blank rather than failing
anything. So the page declares the fields it reads in one `CONTRACT`
object, uses it at runtime to say which key is missing instead of
rendering an empty panel, and `tests/ui.rs` asserts it against a bundle
generated by the real command. Checked that the guard bites: renaming
`extrapolated` to `extrapolatd` in the contract fails the test naming the
field and listing what the bundle actually publishes.

The page is `include_str!`d rather than a file the user is told to copy,
so a released binary always writes a page matching the bundle it
produces. `--ui` writes its own `bundle.json` beside it, since a flag
that wrote the page without its data would produce a site whose only
content is an error message.

Two layout defects found by opening it rather than by asserting on it.
Five nodes huddled in the middle of a 1400px canvas, because the ideal
edge length was a constant tuned against one archive; it is now derived
from the space and the node count. Then the fix overshot and strung five
nodes across the whole stage, which fills the space and is much harder to
read than a compact cluster — so the spacing is bounded above as well.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@khaneight
khaneight changed the base branch from feat/publish-sources to main August 27, 2026 02:24
@khaneight
khaneight merged commit 930369d into main Aug 27, 2026
6 checks passed
@khaneight
khaneight deleted the feat/ui branch August 27, 2026 02:33
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