Skip to content

Console template editor - #289

Open
zer0stars wants to merge 11 commits into
masterfrom
template-editor
Open

Console template editor#289
zer0stars wants to merge 11 commits into
masterfrom
template-editor

Conversation

@zer0stars

Copy link
Copy Markdown
Member

The editor for the trim templates the extraction pipeline emits.

Trims are columns, attributes are rows. Values shared across trims render
muted; values that differ render at full contrast, so on the real Camry the eye
lands on powertrain_type and fuel_tank_capacity_gal — the pair the
production record blended into one row describing two different cars.

  • /templates browses device definitions and says which have a template, which
    do not yet, and which carry an id the schema cannot accept
    (subaru_tribeca-(ny/nj)_2008 and 136 others are live today).
  • /templates/<id> edits one. Publish is disabled while the draft would fail
    the worker, and the fault is named in the worker's own words.
  • /templates/new creates one.

The write path

DEFINITIONS_WRITE_TOKEN lives only in src/services/definitions.ts, which
throws if it is ever evaluated in a browser — a guarantee rather than a
convention, and tested both ways. /api/templates/[id] identifies the caller
from the session Console already has, stamps author from it, and refuses a
body that names its own author or version rather than stripping it quietly.

Entitlement follows risk: creating a template harms nobody; editing one that
4,212 vehicles resolve to re-describes all of them. hardwareTemplateId is
curator-only at every tier and has no UI at all.

The If-Match forwarded to the worker is the client's, never the version
the route just read — the latter would rebase a stale editor onto whatever
landed while it was open and return 200.

Needs review

  • An assumption to confirm. The spec says only "its author" may edit an
    unreferenced template, but backfill templates carry no author, which under a
    literal reading makes the whole catalog curator-only. This treats an absent
    author as unowned. One function changes if that is wrong
    (resolveEntitlement).
  • The proposal tier is not built — editing a referenced template you do not
    own needs a proposal store the worker does not have. Those callers get a
    read-only editor that says exactly why.

Prerequisites

Requires definitions-worker
#4 (schema route,
compare-and-swap, generated types) deployed first. Deployment is still gated on
dd-api's Create()/manifest() migration and a production re-run of the
extraction.

Verification

203 tests passing, up from 100. This repo's master is red before this branch:
19 suites / 20 tests fail on stale expectations unrelated to templates (a
toEqual on the whole config object, components that dropped a role). The
failing set is byte-identical before and after this branch. npx tsc --noEmit
reports zero errors under src/. npm run build succeeds.

Note: CLAUDE.md says npm run compile is a typecheck; it is graphql-codegen.

Types are generated from the worker's schema rather than hand-written a third
time, and the vocabulary is fetched at runtime so the editor form cannot offer
an option the validator rejects.

A runtime guard makes the server-only rule a guarantee instead of a
convention: DEFINITIONS_WRITE_TOKEN cannot reach a browser bundle without
throwing. The guard is testable both ways -- definitions.test.ts runs under
@jest-environment node, and definitionsServerOnly.test.ts asserts the module
refuses to load under jsdom.
Trims as columns, attributes as rows, and the two contract invariants enforced
in the model rather than left to the UI: an attribute lives on the template or
on the trims and never both, and clearing removes the key rather than storing
an empty value. Editing one trim's value for a shared attribute pushes it down
first, which is what makes divergence expressible at all.

Tested against the real ten-trim Camry, including the regression that
powertrain_type and fuel_tank_capacity_gal are the divergent rows -- the pair
the production record blended.
Every difference between what someone types and what gets stored produces a
Normalisation the editor shows before save -- a reparsed number, trimmed
whitespace, a cleared attribute that removes the key. The validation mirror
uses the worker's own error strings so one fault is never described two ways;
it is explicitly not authoritative, and the save path still surfaces the
worker's 422 verbatim.

Fixtures move to src/utils/__fixtures__/. Jest's default testMatch treats
everything under a __tests__ directory as a suite, so a fixture module there
fails as a suite with no tests.
Trims as columns, attributes as rows, agreement muted and difference at full
contrast -- the inverse of normal form emphasis, so the pair of attributes the
production Camry record blended is the pair the eye lands on. The divergence
rail offers Move to shared at one distinct value because that is the correct
action under the template-or-trim rule, and a trim with no effective selector
is marked in the header rather than discovered at save.
Three states, all real. A definition with no template says so rather than
erroring -- the import has not run in production and 5,152 of the emitted set
carry no attributes. An id the schema cannot accept says that instead, because
137 of them are live today and subaru_tribeca-(ny/nj)_2008 is one; those are
never asked about, since the answer would be a misleading 404.
The worker has one shared WRITE_TOKEN and takes author on trust. This route
holds the token server-side, identifies the caller from the session Console
already has, stamps author from it, and refuses a body that tries to name its
own author or version rather than stripping it quietly.

Entitlement follows risk: creating a template harms nobody, editing one that
4,212 vehicles resolve to re-describes all of them. hardwareTemplateId is
curator-only at every tier. The If-Match forwarded is the client's, never the
version this route just read -- the latter would rebase a stale editor onto
whatever landed while it was open and return 200.
…anner

Three places the editor leaves a trace instead of acting quietly: what it
stored that differs from what was typed, why a selector-less trim will be
refused before save rather than at it, and what the caller is not allowed to
do and why.
Publish is disabled while the draft would fail the worker, and the fault is
named in the worker's own words. The precondition is the version that was
loaded, held apart from the draft so it cannot move underneath it. A conflict
explains what happened and offers a reload -- it never retries over the top of
somebody else's version.
Publishes immediately and attributed, because nothing points at a template
nobody has created. If-None-Match makes an id that already exists a conflict
with a link to it rather than a silent overwrite.
@vercel

vercel Bot commented Aug 30, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
dimo-developer-console Ready Ready Preview Aug 30, 2026 7:13pm

Request Review

The contract has three generated mirrors (Console TS, the worker's TS,
dd-api's Go). Generating them without a guard is hand-writing them one step
removed: the copies still drift and nothing says so. The worker already fails
`npm test` on drift; Console had the generator and no check.

Two changes make that possible. The generator now reads a vendored copy of
template.schema.json rather than fetching the deployed one: a generator whose
input can change underneath it produces output nobody can reproduce, and drift
is only reviewable when it lands in a diff. `--from <url|path>` rewrites the
vendored schema alongside the types, so a contract change shows both files
moving in one commit.

`--check` then fails when the committed output is stale, wired into `prebuild`
so a Vercel build refuses to ship stale types, and covered by a test that also
asserts the guard fails on a perturbed file -- a check that cannot fail is the
defect it was written to prevent.
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