feat(playground): add a /markdown page demoing the agent edit loop - #3263
feat(playground): add a /markdown page demoing the agent edit loop#3263christianhg wants to merge 2 commits into
/markdown page demoing the agent edit loop#3263Conversation
The playground was a single page: `main.tsx` rendered one `App` and every demo or repro competed for space on it. Sub pages now come from files in `src/routes/`, compiled by `@tanstack/router-plugin` into `routeTree.gen.ts`. The generated file is committed because the build script runs `tsc -b` before `vite build`, so it must exist without the plugin having run; the plugin is pinned exactly so a fresh install regenerates it byte-identically. The old `App` body becomes the index route, unchanged: same DOM order and class names, `playgroundMachine` still created per page mount (so navigating away and back resets editor state). The shared shell (outer layout and `Footer`) moves to the root route, which also gets a styled `notFoundComponent`. The header renders nav links to the pages; the first sub page, `/minimal`, is a bare editor with no toolbar or plugins. A `vercel.json` rewrite serves `index.html` for unknown paths so path URLs deep-link on the Vercel deploy. The `pnpm-lock.yaml` diff includes an `@babel/types` re-dedupe forced by the plugin's transitive dependencies; a minimal install reproduces it.
The page plays out the markdown agent edit loop with a human as the
LLM. Three panes: the document (a standalone `EditorProvider` with the
playground schema, toolbar, and page-local rendering lifted from the
machine-coupled `Editor` component), the markdown (the agent's view),
and the Portable Text JSON (storage).
The markdown pane owns one state machine, a pure reducer. In
`following` it regenerates via `portableTextToMarkdown(value,
{schema})` on every `mutation`, keeping the `{markdown, value}` pair
that produced the pane. The first keystroke freezes that pair as the
read snapshot. Sync runs `applyMarkdownEdit(snapshot.value,
editedMarkdown, ...)` with a throwing `onDegradation` (strict by
default; a rejected edit writes nothing and offers a non-strict
"Write anyway" that reruns with a collecting callback), then writes
the result through `update value`, which emits no `mutation`, so the
loop cannot echo. A `mutation` arriving while editing flips to
`conflict`, where only Re-read is offered: the loop starts over
instead of merging. Because mutations are debounced (1s flush), the
flip alone leaves a stale-write window, so the sync path also
deep-compares the live engine value (`getSnapshot().context.value`)
against the snapshot before writing and diverts to `conflict` on
mismatch. Errors (degradation, conflict, `invalid value` from the
sync machine) render as cards rising from the markdown pane's bottom.
The JSON pane makes key preservation visible: after each sync,
`_key`s absent from the snapshot highlight as fresh, with a fresh
versus adopted count line; a typo-sized edit shows zero fresh keys.
The reducer transitions are pinned by a node harness (red on the
pre-fix reducer for the hygiene transitions); browser behavior is
not covered by tests, the playground has no test infrastructure.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
Bundle Stats✅ No significant changes. All scenario measurements (7)🗺️
Significant means at least 1.0 KB and 1% gzip, or at least 5 ms and 10% import time. |
What
A
/markdownplayground page that plays out the agent edit loop with a human as the LLM. Stacked on #3242, whoseapplyMarkdownEditit demonstrates; the first commit revives the file-based routing from the closed #3241 unchanged, so the reviewable novelty is the second commit.Three panes. The document: a standalone editor with the playground's full schema and toolbar, so custom objects (ticker, mention, fact-box) are one click away and serialize as
json:objectcarriers. The markdown: the agent's view. It follows the editor until the first keystroke freezes a read snapshot; Sync applies the edit throughapplyMarkdownEditwith a throwingonDegradation(a rejected edit writes nothing; "Write anyway" is the non-strict fork), and a mutation mid-edit flips to conflict, where the only way out is Re-read: the loop starts over, it does not merge. Because mutations flush on a 1s debounce, Sync also deep-compares the live engine value against the snapshot before writing and diverts to conflict on mismatch; the flip alone leaves a stale-write window. Errors rise as cards from the pane's bottom. The Portable Text pane shows storage: after each sync, freshly minted_keys highlight amber with a fresh-versus-adopted count, so a typo fixed through markdown visibly costs zero keys.Verification
The reducer's transitions and the seed's carrier serialization are pinned by a Node harness against this branch (the hygiene transitions proven red on the pre-fix reducer). Browser behavior is source-traced, not machine-tested; this sandbox cannot launch Chromium. On the preview: insert a ticker, edit the markdown around its carrier, Sync, and watch the JSON pane stay quiet.