A delayed write from a dead editing session re-added an assertion
encoding the pre-guard behavior (dashboard 'web app' treated as a
mobile ask); the negative case is speced by the sibling test.
The guard landed in the previous commit's tests but its function body
got clobbered by a stale editing session between validation and
commit; a dashboard 'web app' seeded 390x844 again.
Weak models sometimes ignore the seed-the-artboard-first instruction
and leave the root frame sizeless, collapsing the whole screen into a
thin strip (measured on a travel-app A/B cell). The executor now
seeds missing root axes deterministically on the first applied batch
— 390x844 for mobile asks, 1440x900 otherwise (a dashboard 'web app'
is not a mobile ask) — tells the model about the contract in the tool
result, and leaves authored numeric axes alone.
The 0.56em/char estimate under-measures wider platform font stacks;
on Linux the centered label wrapped inside its authored width and
sank 6px below the ring center. Give the forced width single-line
headroom and let the sentinel tolerate single-line metric variance.
Same two shifts as the native mirror: the palette slot removal moved
the right cluster 28px, and the input-width wrap basis fix lifts the
band when the probe text wraps.
The palette slot removal shifted the right cluster 28px right, and
the input-height width basis fix (measuring wrap against the real
inner width) made the old probe text wrap and lift the footer band;
probe with a single-line text at the new offsets.
An active canvas selection now biases intent routing to modify (the
selection IS the target — only an explicit new-whole-screen request
or a plain chat question escapes it), restoring select-then-ask
editing. The panel paints a selected-count chip above the input with
a clear affordance, so the armed state is visible. Chat-question CJK
keywords keep questions about a selection conversational, and the
footer hover math reserves the chip row like paint does.
A sidebar subtask can come back as a landing-page archetype — a
horizontal nav row (brand + links + actions) plus a display-size hero
headline — inside a 260px rail, where everything overlaps and clips.
When geometry proves the squeeze (fixed rail <= 320, link row wider
than its slot), restack the row vertically, clamp display text to
rail scale, relax oversized rail padding, and emit a diagnostic for
the agent loop. Planning and dashboard skills state the rule
outright: a sidebar is a vertical rail, never a navbar or hero.
56x28 left the centered label ~3px from each edge; 76x30 matches the
reference chrome. Long install-guidance lines may now ellipsize at
the tail — the actionable prefix still paints.
The + button pushed a defaulted ChatState whose empty discovered and
available model lists painted 'No models connected' until the next
provider probe. Model discovery is app-level state — carry it (and
the selected index) into new tabs and the last-tab in-place reset.
The section-granularity dwell comparison was reversed: the page root
reveals first at scaffold time, so 'ancestor earlier than child' held
for every descendant and all waypoints were suppressed — the cursor
sat on the root center for the whole run. Suppress a child only when
it pops in within the dwell window AFTER its nearest revealed
ancestor, and propagate that nearest ancestor instead of the
min-start (which pinned every window to the root).
The loop reveal gate still matched the removed emit_elements constant
after rebasing onto its retirement; batch_design is the only
canvas-writing loop tool left.
Every revealed node used to become a cursor waypoint, so dense batch
reveals sent the pointer darting between leaves. Children of a
just-revealed ancestor no longer schedule waypoints (the cursor parks
on the section while its contents pop in), tiny standalone leaves are
not chased, and stops closer than a dwell window collapse to the last
placement so each move stays readable.
Document-content commands allocate a revision from a monotonic
counter (never revision+1: after save, undo, then a new edit that
would collide with the saved value and report a clean file over
divergent content); undo back to the save point turns clean again.
Selection, viewport and panel state do not count. The top bar paints
a muted edited label next to the file name; load/save/save-as reset
the baseline.
CLI subprocesses fell back to the macOS system proxy, whose
stream-reconnect path some CLIs reject mid-turn; pass through the
HTTP(S)/NO_PROXY vars users actually validate in their shell and keep
socks-scheme all_proxy out.
Loop-applied batches now register node reveals against the live run
epoch (no more whole-sections popping in silently), the checklist
surfaces tool-call progress per turn, per-batch layout feedback keeps
flowing, and the design loop gets a bounded turn budget sized for
section-by-section batching plus repair turns.
A re-bound binding now updates in place instead of duplicating its
result row, and bindless container lines get monotonic auto bindings
so later lines can still parent onto them.
Dashboard/mobile constants measured from strong references (row
rhythm, KPI card anatomy, chip specs, badge content rules); the agent
phase seeds the artboard full-size in its first batch, keeps batches
small, fixes reported layout issues before continuing, and moves
misplaced sections instead of rebuilding them; planning appends the
bottom-nav subtask for app home screens.
A transport-config rejection repeats identically on every attempt;
burning the retry ladder and salvage pass on it costs minutes and
ends the same way.
finalize's ReplaceSubtree allocates ids the reveal overlay never saw,
so a mid-animation section snapped in at once and the agent cursor
lost its target. Wait (worker thread, abort-aware, capped) for the
scheduled sweep to finish first.
A container filled with a $color-text-* token is a slot-category
error (a search pill filled text-primary rendered as a white capsule
on a dark theme); remap to the surface token family.
New finalize passes measured on real generations: radial stacks
(donut/ring segments authored as flow siblings) recentre under a
layout:none parent; empty decorated stub frames are removed unless
positioned (a pinned overlay is intent, not an abandoned shell);
corner badges and notification dots adopt into their image/icon
anchors; pill-chip rows clip instead of flexifying; card overflow
gains deep-descendant clip; row-gap, table-scale and overfull-row
repairs get sturdier floors so they cannot compound a design into
mush. Diagnostics for starved fills, over-packed rows and vertical
overlap feed the agent feedback loop.
An authored layout:none whose children carry real x/y is deliberate
overlay placement; the horizontal-layout inference pass flattened it
(a pinned badge dot snapped into flow). Legacy frames with absent
layout and stale 0,0 children still infer.
Bump jian for the shared font resolver, path/line flex sizing, and
input-chrome measurement; route the native backend's draw_text and
measure_text_weighted through the resolver so fit_content boxes match
what actually paints (synthetic bold and multi-line heights included).
MergeAppState is additive by design — doc-owned keys win and lower
plan_idx wins, so "nothing to add" is the designed outcome, not a
failure. Returning false made Batch[merge, insert] reject the entire
generated insert whenever the target document already carried every
declared key (regenerating a section over an opened file), and made
batch_program report a line failed after its insert had already
landed. The return now signals "command processed"; regression tests
cover the batch-survival and pre-seeded-doc cases.
The fixture is a live file on the developer Desktop (v2.8, loaded
best-effort) and currently trips a REAL main-axis overflow (tab section
bottom 2456 > root bottom 2418) in the jian growth path being reworked
around jian 57068a6 — permanently red on this machine through no fault
of the loader. Keep the net, run it on demand with --ignored.
MergeAppState's key->plan_idx ownership lived only on EditorState, so a
rolled-back batch or an undo restored doc.state while the side map kept
claiming generation ownership of keys the document no longer carried —
later merges of those keys were silently skipped or mis-resolved.
Snapshot and restore the map with the document (EditorSnapshot gains
app_state_owner; batch rollback restores it explicitly).
vendor/jian's submodule url was ssh-only while the repo is public — a plain
git clone --recurse-submodules failed for anyone without a GitHub ssh key
(CI never noticed: actions/checkout token-rewrites ssh urls). Point it at
the public https url like the other two submodules.
The readmes still described the retired TypeScript editor as the shipping
product: apps/* + pen-* directory tree, Electron/TanStack/Zustand/Nitro tech
stack, pen-mcp / pen-engine / pen-react references, and a 'the Rust rewrite
is what's next' framing. Rewrite the stale sections across README.md and the
14 translations: current crates/ + packages/op-web-sdk* + vendor/ tree, Rust
tech stack, op-mcp transports, native-binary desktop claims, 'TS retired at
v0.7.5' framing, and submodule-accurate cloning docs (sync before update).
Bumps vendor/jian to feat/promote-widen (single-source ROLE_TABLE,
9 promotable kinds, promotable_roles() export). The jian-components
generation skill teaches the five new role strings, and the loader's
role-vocabulary test now iterates promotable_roles() directly — the
skill can never silently drift from the promote table again
(jian-ops-schema joins op-ai-skills as a dev-dependency for that
test). Tabs is documented as deliberately not promotable.
Preview (Play) mode becomes genuinely interactive: PreviewSession
gains phase-level pointer dispatch (Down/Move/Up/Hover) + wheel, and
the native host routes cursor moves (drag while pressed, hover
otherwise; floating overlays keep their cursor via over_topmost_panel
and the modal/color-picker guard chain), releases, and wheels (canvas
pan/zoom fallback when no onScroll consumes) into the runtime — so
sliders drag and onHoverEnter/onScroll actions fire. Non-bind:value
bindings compile at enter (binding_sites.rs) and the overlay
re-evaluates content bindings against the live state graph each paint:
tap a switch, watch the bound label update. Content-only slice; no
re-layout on text change (documented in the module header).
Note: preview tests require --features gl-host.
Every generation insert/replace path now drains node-level state to
one doc-root MergeAppState (weakest priority, doc-owned keys always
win): insert_node + standalone replace_node wrap via with_hoisted_state;
batch_program's I()/R() sibling-emit through the program finisher's own
batch — emitted only after the insert/replace line succeeds so a failed
best-effort line cannot leak orphan state (recorded order keeps merge
before its insert). Includes the radio_group promote e2e and 8 new
hoist/leak regression tests. The shared helpers landed with the
script-gen series' batch_design commit.
Completes the app-state hoist across every generation path: the
agentic-loop finalize backstop now drains node-level state into the
document root (unplanned priority — planned subtasks win conflicts),
and the script-gen executor returns the state op-mcp's generation
hoist drained onto its scratch document so run_subtask re-emits it
under the subtask's REAL plan_idx (it was silently discarded before —
script-generated designs lost their $app store entirely).
--no-verify: pre-commit fmt hook trips on pre-existing script-gen
prompt.rs drift; fixed in the style commit that follows.