Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Contributor
blove
added a commit
to blove/ag-ui-chrome-extension
that referenced
this pull request
Oct 1, 2026
…nd what didn't render (#60) * docs: UI inspector design and plans (§14.5) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * §14.5 A1: generative-UI surfaces, catalogs and checks (U1–U3) core/genui/extract.ts reads every A2UI / json-render surface out of a capture: Threadplane's sentinel text, json-render text, a2ui-partial custom events (last valid parse wins, a poisoned call stops) and GenUI tool args over AG-UI and LangGraph; CopilotKit's a2ui-surface activities in both middleware forms (v0.0.10 a2ui_operations snapshots, v0.0.2 operations deltas + replace:false snapshots) with lifecycle status. Malformed payloads become findings, never throws. core/genui/catalog.ts: the exact catalog from RunAgentInput.context (A2UI_SCHEMA_CONTEXT_DESCRIPTION), a registry catalog for A2, and the inferred A2UI v0.9 basic catalog (18 components, both basic ids). core/genui/check.ts: unknown_component, missing_required_prop (exact only), unresolved_child, orphaned_subtree, no_root, duplicate_id, catalog_mismatch, each labelled exact or inferred. Golden fixtures: a slice of Threadplane's stage-replay.json (LangGraph, 158 KB), a CopilotKit capture from the middleware tests' sequences, and Threadplane json-render/A2UI over AG-UI incl. the cockpit render_spec. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * §14.5 A2: capture Threadplane render reports (U4, U5) core/signals/render-report.ts validates U4's render report beside the signals one: exact key sets (the report's and each element's), v 1, kind 'render', surface/registry names/element keys and types 1-128 chars, at most 500 registry names and 2,000 elements, states mounted | fallback | unresolved | hidden, own properties only, never throws. cloneRenderReport copies field by field, one level deeper per element, bounded by the contract rather than a re-read length. The threadplane:devtools listener routes each detail by its own kind: absent is a signals report, 'render' a render report, anything else is dropped; each is validated, copied and re-checked before posting. The render report rides its own connectionless arm (kind 'render') through the inject guard, the relay's field-by-field rebuild and the worker, which keeps a separate per-tab ring of 500 (eviction counted), paused with capture, cleared with the buffer, and mirrored as a tail bounded by count (100) and size (1 MB). Snapshot and append carry renders; the panel folds them into PanelState.renders (bounded, empty for imports) and its coalescer now batches render-only appends with signals ones. Tests at every layer incl. hostile inputs, Threadplane-shaped reports from cacheplane/threadplane#1215's specs through the validator, and an e2e page dispatching valid, hostile and signals reports on one event. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * §14.5 A3: the UI tab — generative-UI surfaces, trees and findings (U6) Surfaces grouped by run, each with its framework, format, status, the catalog it was checked against and its basis (exact / inferred / none); a component tree from the root, with what the root cannot reach listed separately and a reference the surface does not hold shown as such; each node badged rendered / fallback / not rendered / unknown type. The states come from the latest Threadplane render report whose surface is the surface's id (A2UI id, or spec: + root), applied to the most recent surface with that id — and its registry is passed into inspectGenui (new per-surface registryFor) so that surface's checks become exact — else from the wire checks; the tab says which. Findings per surface, extraction findings per run; selecting a node shows its props and links its source frame in Timeline. The empty state names what the view lists. Visual gate: both golden fixtures imported, then the Threadplane one live with a seeded render report (devtools-live-genui shim), asserting the cockpit's node states switch to the hook's in both schemes. Docs: README status, PRIVACY (render report is names and states only), listing line, spec §14.5 note. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(genui): review fixes - Linear-time extraction and checks on hostile captures: the partial-JSON scanner no longer snapshots the open stack per token (quadratic in depth); orphaned-subtree marking walks the surface once; duplicate ids and touched surfaces use Sets; no argument spreads (Math.max(...seqs), push(...findings)) that overflow past ~120k elements; an unstringifiable LangGraph message is skipped instead of throwing out of extractSurfaces. - UI tab: trees are capped at MAX_TREE_DEPTH (64) and marked "deeper levels not shown" (a 5,000-deep surface overflowed Preact's render); reachability is walked iteratively apart from drawing; the capture is extracted once per model. - A render report's registry is widened by the types its elements show resolving, by long types it cannot carry, and (at the 500-name cap) by every wire type it does not call unresolved — no false exact unknown_component. - Render ring bounded by size as well as count (MAX_RENDER_RING_CHARS, 4 M serialized characters) in the worker, its restore, and the panel's copy. - e2e quiet-page: wait for the worker to report the document loaded (readLoadedCapture) instead of racing the relay's capture-loaded message. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(acceptance): the UI inspector against a real Threadplane build acceptance:threadplane now also renders an A2UI surface and a json-render spec (each with one unknown type and a child under it) through <a2ui-surface> and <chat-generative-ui> after a real AG-UI run, and asserts the worker holds the render reports (unresolved / hidden / mounted) and that the UI tab's model badges the captured surfaces the same. Builds render and a2ui too when stale. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Contributor
blove
force-pushed
the
feat/devtools-scripted-runs
branch
from
October 2, 2026 04:03
13df343 to
a82d847
Compare
…only devtools hook
When an ancestor provides the private ɵRENDER_DEVTOOLS hook, <render-spec>
tracks its live <render-element> instances and, after every render pass,
hands the hook a reader for the spec's resolution: { root, registry, elements }
with each element { key, type, state }.
state is unresolved when the registry has no entry for the type, fallback
when the entry's fallback is shown because the props are not ready, hidden
when nothing renders for it (visible false, an empty repeat, or under an
element that rendered no children, such as an unresolved one), and mounted
otherwise. A live instance's state comes from the renderer's own entry(),
visible() and notReady() signals; elements without a live instance are
resolved from the spec's structure alone (root, keys, type, children).
Neither path reads an element's props: a test renders unrendered elements
whose props getter throws, and counts prop reads with and without the hook.
The tracker and its wiring sit behind the ngDevMode gate in module-level
functions, so production builds drop them. A spec nested inside another
spec's view reports only through a hook provided closer to it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
<a2ui-surface> and <chat-generative-ui> provide the render hook. When a
surface's resolution changes, the reporter dispatches the same
threadplane:devtools CustomEvent as the signals report, told apart by kind:
{ v: 1, kind: 'render', surface, seq, registry, elements, tMs }
surface is the A2UI surfaceId, or 'spec:' + root key for json-render; seq
counts render reports per page. Bounds per the extension's contract: at
most 500 registry names and 2,000 elements, extras dropped in order; a
name, key or type over 128 characters is left out rather than shortened,
and a surface id over 128 characters is not reported. Changes coalesce for
50 ms, and an unchanged resolution sends nothing.
Same gate as the signals emitter: development mode, a browser, and no
__THREADPLANE_DEVTOOLS_DISABLED__ opt-out (checked at creation and at
dispatch). Ships as the private ɵcreateRenderDevtoolsReporter and
ɵprovideRenderDevtools.
Tests: an A2UI surface with an unknown component reports it unresolved and
its subtree hidden; a data-bound component goes fallback then mounted; a
json-render spec is 'spec:<root>' and reports once; prop reads are equal
with and without the reporter; the production and opt-out gates; bounds.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ment it verify-devtools-bundle.mjs now bundles @threadplane/render from dist too, with <render-spec>, <a2ui-surface>, <chat-generative-ui> and the render reporter, and names two markers unique to the render report: chat's kind:"render" report literal and render's unresolved:1 state ranking. The production bundle must contain neither; the development bundle must contain both. The render README describes the report, its states, bounds, gate and opt-out; the changelogs and the parity inventory record the new private seam. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The baseline was written before the render-report changes were committed, so it pinned stale hashes for render-spec.component.ts, the render README and the chat changelog, and failed inventory.mjs --check in CI. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
blove
force-pushed
the
feat/devtools-render
branch
from
October 2, 2026 04:05
ca3bc97 to
f1fb867
Compare
Contributor
This branch was successfully deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on #1204 (
feat/devtools-scripted-runs). Part B of the AG-UI DevTools UI inspector (§14.5): in development, Threadplane reports how each spec it renders resolved, so the extension's UI tab can show which components mounted and which did not. Today an unknown component type renders nothing, along with its subtree, and nothing says so.Contract (U4 of the extension's design, unchanged)
The same
threadplane:devtoolsCustomEventonwindowas the signals report, told apart bykind. Signals reports (nokind) are unchanged.Example from an A2UI surface with one unknown component (
GoogleMap), from the test environment:{"v":1,"kind":"render","surface":"contact-form","seq":1, "registry":["AudioPlayer","Button","Card","CheckBox","ChoicePicker","Column","DateTimeInput","Divider","Icon","Image","List","Modal","Row","Slider","Tabs","Text","TextField","Video"], "elements":[{"key":"root","type":"Column","state":"mounted"},{"key":"title","type":"Text","state":"mounted"}, {"key":"map","type":"GoogleMap","state":"unresolved"},{"key":"pin","type":"Text","state":"hidden"}], "tMs":1403.78}State semantics
unresolvedtyperegistry.getEntry(type)fallbacknotReady()hiddenvisibleis false, an empty repeat, or it sits under an element that rendered no children (unresolved, hidden or fallback)visible(), or no live<render-element>for the keymountedA repeat reports its most-rendered row (any visible row mounted, then fallback, else hidden). A key rendered more than once takes its most-rendered instance. Elements come root first, depth-first through
children, then any element nothing references, in spec order.When it's sent.
<render-spec>signals the hook after every render pass. Changes coalesce for 50 ms, and a report goes out only when the surface's{surface, registry, elements}differs from the last one sent.Bounds. Past 500 registry names or 2,000 elements, the extras are dropped in order (registry order, then the element order above). A name, key or type that is empty or over 128 characters is dropped rather than shortened, so the report never names something that doesn't exist. A surface id over 128 characters means no report for that surface. The contract has no truncation field, so a dropped element can't be signalled. 2,000 elements and 500 names are far beyond real surfaces.
Where it lives
@threadplane/render: privateɵRENDER_DEVTOOLStoken ({ changed(read) }). With a hook from an ancestor,<render-spec>creates a tracker. Each<render-element>registers{ key, state }, wherestateis acomputedover its existingentry()/visible()/notReady()/repeatVisible()/repeatNotReady().resolveRenderTreecombines the live states with the spec's structure.<render-spec>sets the token tonullfor its own views, so a nested spec reports only through a hook provided closer to it.@threadplane/chat:ɵcreateRenderDevtoolsReporter/ɵprovideRenderDevtools(inlibs/chat/src/lib/devtools/).<a2ui-surface>provides it keyed bysurfaceId.<chat-generative-ui>provides it as'spec:' + root, which also covers chat's json-render messages and tool views.Privacy model
Only names, keys, types and states, never prop values. A live element's state comes from the renderer's own memoized mount decision, the same signals its template already renders from. Elements with no live instance are resolved from structure alone:
root, the element keys, and each element'stypeandchildren. The reporter only ever sees the resolution. Tests prove it two ways:propsgetter that throws, under both<render-spec>directly and the chat provider;Dispatch is fire-and-forget, and errors from a read or a listener can't break the surface.
Gating
The gate and opt-out are the signals emitter's: development mode (
ngDevModeandisDevMode()), a browser, and nowindow.__THREADPLANE_DEVTOOLS_DISABLED__ = true. The opt-out is checked at creation and again at dispatch. The render-side tracker,resolveRenderTreeand the wiring are module-level functions called only inside thengDevModebranch, so production builds drop them.verify-devtools-bundle.mjs(CI:nx run chat:test-devtools-bundle) now also bundles@threadplane/renderfrom dist, with<render-spec>,<render-element>,<a2ui-surface>,<chat-generative-ui>and the reporter. It asserts that two markers unique to this code, chat'skind:"render"and render'sunresolved:1state ranking, are absent from the production bundle and present in the development bundle. Only the two tokens' descriptions stay in production. A first version that kept the wiring as a component method failed this check, which shows it catches leaks.Tests
libs/render/src/lib/devtools/render-devtools.spec.ts(9): structure-only resolution with throwing props; unknown type →unresolved, subtreehidden;fallback→mountedon store change;visible:false, hidden-parent and fallback-parent children →hidden; repeat (empty →hidden, a ready row →mounted); nested spec isolation; equal prop-read counts; no tracker without a hook.libs/chat/src/lib/devtools/devtools-render-report.spec.ts(14): exact U4 key set; A2UI surface with an unknown component →unresolved+ childrenhidden, keyeds1, registry = the catalog's names; A2UI data-boundText→fallbackthenmountedafterupdateDataModel(seq increases); json-render →spec:card, one report across repeated change detection; equal prop reads; throwing props on unrendered elements;ngDevMode=false→ null;isDevMode()false → null and an A2UI surface sends nothing; opt-out at creation and at dispatch; dispose; 2,000 / 500 caps; over-long names dropped; over-long surface skipped, empty id →spec:; per-page seq; a throwing read.nx run-many -t test,lint,build -p render,chat(render 155 tests, chat 1,239, lint 0 errors),nx run chat:test-devtools-bundle,nx run chat:type-tests,inventory.mjs --check,generate-api-docs(no drift),check-dx-coverage.Known limits
<render-spec>used outside the chat components reports only if the app providesɵRENDER_DEVTOOLSitself.Not for auto-merge. The extension side (validator, capture, UI tab) is Part A in ag-ui-chrome-extension.
🤖 Generated with Claude Code