ADR-0017: The Artifacts and Proposals areas: renderers by kind, and diffs by revision¶
Status: Accepted Date: 2026-10-12 Deciders: Alex Nodeland
Context¶
lattice RFC-0002 gives the app an Artifacts area, each artifact with its renderer, its revisions and their diffs, and its open proposals, and a Proposals area, every open proposal in the workspace to accept or reject. ui renders artifacts through "a registry of renderers keyed by artifact kind, which an application extends with its own", and a kind without one as formatted JSON. The workspace's stream already holds each artifact's kind and latest version, and every proposal (ADR-0016); it doesn't carry an artifact's data, which artifactr's REST surface reads.
Decision¶
uirenders artifacts through a registry keyed by kind: aRenderershows a kind's data, and says what text its revisions' changes are read from and how finely, by word or by line.markdown(field)is one, for a kind whose Markdown is in a field, through the sameMarkdowncomponent as the chat, so no HTML ever renders;jsonis every other kind's, its data formatted. A kind named for something every object has, such asconstructor, is still JSON.- The app registers the kinds it knows: the template's
note, as Markdown in itstext. An application with kinds of its own adds them tosrc/renderers.ts. - Diffs are jsdiff's, made in
client(changes(before, after, by)), and shown byui'sDiff, which marks what was added and removed for screen readers as well as by colour and line. jsdiff stops at an edit of 2,000 words or lines, or after 200 milliseconds, and a revision past either is "too large to compare". An artifact's page works its revisions' changes out once each time it reads them, in version order, not on every event the stream brings. - An artifact's page reads its revisions over REST, each with its whole data, oldest first, whose last is the artifact as it is now; artifactr types that read, and not the artifact's own. It reads them again when the workspace's log moves the artifact past the latest it has, and an artifact artifactr doesn't have, or has no revision of, is not found at once: a read refused with a 4xx is never tried again. Its open proposals come from the stream.
- The Artifacts area lists the workspace's artifacts that aren't archived, from the stream, each by kind and latest version.
- The Proposals area reads the stream, like Threads: every proposal, the open ones first, each with a link to the artifact it would change.
- Each area's feature makes its own note, through the scripted agent, so it runs again on one application: an accepted proposal's change in the note and its latest revision; accepting on the note's page; rejecting in the Proposals area.
Options considered¶
| Option | For | Against |
|---|---|---|
| A registry by kind, JSON otherwise (chosen) | A new kind shows at once; an application adds its own | A kind's look is code in the app |
| A renderer guessed from the data's shape | Nothing to register | A guess: a text field isn't always Markdown |
Diffs in client, rendered by ui (chosen) |
ui stays rendering what it is given |
Two places to read |
| Revisions over REST (chosen) | Each has its whole data, typed | One read per artifact opened |
| Rebuilding data from the log's patches | No REST read | The stream carries no artifact's data to patch |
Consequences¶
- Easier: the admin UI, and any application's own screens, render artifacts through the same registry.
- Harder: a deployment with its own kinds builds the app with its renderers, until the template carries them (phase 7).