Skip to content

feat(playground): add a /markdown page demoing the agent edit loop - #3263

Closed
christianhg wants to merge 2 commits into
markdown-apply-editfrom
playground-markdown-loop
Closed

feat(playground): add a /markdown page demoing the agent edit loop#3263
christianhg wants to merge 2 commits into
markdown-apply-editfrom
playground-markdown-loop

Conversation

@christianhg

Copy link
Copy Markdown
Member

What

A /markdown playground page that plays out the agent edit loop with a human as the LLM. Stacked on #3242, whose applyMarkdownEdit it 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:object carriers. The markdown: the agent's view. It follows the editor until the first keystroke freezes a read snapshot; Sync applies the edit through applyMarkdownEdit with a throwing onDegradation (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.

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.
@vercel

vercel Bot commented Sep 10, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated
portable-text-editor-documentation Ready Ready Preview Sep 10, 2026 12:52pm UTC
portable-text-example-basic Ready Ready Preview Sep 10, 2026 12:52pm UTC
portable-text-playground Ready Ready Preview Sep 10, 2026 12:52pm UTC

Request Review

@changeset-bot

changeset-bot Bot commented Sep 10, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: db4932d

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@socket-security

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Added@​tanstack/​router-plugin@​1.168.35991007898100
Added@​tanstack/​react-router@​1.170.33921008797100

View full report

@github-actions

github-actions Bot commented Sep 10, 2026

Copy link
Copy Markdown
Contributor

Bundle Stats

✅ No significant changes.

All scenario measurements (7)

🗺️ @portabletext/editor / @portabletext/editor · @portabletext/editor / @portabletext/editor/behaviors · @portabletext/editor / @portabletext/editor/plugins · @portabletext/editor / @portabletext/editor/selectors · @portabletext/editor / @portabletext/editor/traversal · @portabletext/editor / @portabletext/editor/utils · @portabletext/markdown / @portabletext/markdown · Artifacts

Scenario Kind Bundle (raw / gzip) Gzip change Import time Import change
⚪ @portabletext/editor / @portabletext/editor export 1.09 MB / 254.3 KB None 71 ms -1 ms, -0.7%
⚪ @portabletext/editor / @portabletext/editor/behaviors export 4.0 KB / 1.4 KB None 2 ms -0 ms, -4.7%
⚪ @portabletext/editor / @portabletext/editor/plugins export 5.1 KB / 1.8 KB None 7 ms -0 ms, -1.3%
⚪ @portabletext/editor / @portabletext/editor/selectors export 94.7 KB / 21.7 KB None 8 ms +0 ms, +1.2%
⚪ @portabletext/editor / @portabletext/editor/traversal export 42.8 KB / 11.2 KB None 6 ms +0 ms, +1.0%
⚪ @portabletext/editor / @portabletext/editor/utils export 33.8 KB / 9.1 KB None 6 ms -0 ms, -3.6%
⚪ @portabletext/markdown / @portabletext/markdown export 368.2 KB / 104.2 KB None 46 ms +2 ms, +3.7%

Significant means at least 1.0 KB and 1% gzip, or at least 5 ms and 10% import time.

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