Commit graph

2042 commits

Author SHA1 Message Date
Kayshen-X eb853f6ccf refactor(editor): split layer-panel hit-test into a sibling file
The pages-row-height scroll fix pushed layer_panel.rs past the 800-line
ceiling. Move hit_test + drop_target_at into layer_panel_hit.rs via an
impl block; no behavior change.
2026-07-04 18:58:45 +08:00
Kayshen-X ec45fd99c1 fix(editor): use page row height for pages-region scroll window
The Pages and Layers regions use different row heights (32px vs 28px),
but visible_row_range / row_index_at hardcoded LAYER_ROW_HEIGHT for
both. With a long page list scrolled toward the bottom, the pages
window started at the wrong index — only the last page rendered and
the rows above it were skipped, leaving a blank gap. Thread the
region's row height through both helpers.
2026-07-04 18:50:36 +08:00
Fini 7536627056 fix(desktop): gate test-only re-export so clippy -D warnings passes
The narration-collapse change switched the pump to
apply_poll_to_message_with; apply_poll_to_message is now used only by
the cfg(test) test module, so the bin build re-exported it unused and
`cargo clippy --workspace --all-targets -- -D warnings` (Rust Check)
failed. Move it under the existing #[cfg(test)] re-export alongside
ChatPoll.
2026-07-04 18:47:58 +08:00
Fini 0471f95f6f docs(orchestrator): stop overstating Pencil alignment on the fallback path
The script-gen comment claimed '完全对齐 Pencil' for what is now the
orchestrator single-shot FALLBACK path — it aligns only the output
protocol (a JS DSL like Pencil's batch_design), not Pencil's defining
per-batch feedback loop, which lives in the sonar design-agent loop
(the builtin-provider default). Corrected per the alignment audit.
2026-07-04 18:29:43 +08:00
Fini 66b4cf8409 feat(editor): collapse the design loop's model narration
The design agent loop streamed the model's free-text chatter between
tool calls ('Let me build the header... Now the deals section...')
into the visible transcript bubble — noise, since the tool-call
checklist already shows clean progress (measured on a MiniMax-M3 run).
A design-loop ChatSession now folds that narration into the collapsed
thinking area instead; plain chat and CLI turns keep it visible, and
errors always surface in the bubble regardless.
2026-07-04 18:23:43 +08:00
Fini 2973c32dd0 feat(orchestrator): remove abandoned duplicate root artboards
A weak model on the loop path can rebuild-and-abandon at the artboard
level, leaving two same-named top-level frames — a sparse opaque stub
(a few nodes) overlapping the real design. The stub's fill covers the
real artboard's top, blanking it. Detect same-named overlapping roots
where one holds under 30% of the other's descendants and drop the
sparse stub, keeping the rich one. Side-by-side authored roots, single
roots, and two comparably-full roots are left alone. Runs in both the
loop finalize and the orchestrator cleanup.
2026-07-04 18:16:19 +08:00
Fini ea3a759e39 test(orchestrator): update the stale unresolved-ref button test
The accent-token contrast fix intentionally makes $color-accent
buttons flip children to white instead of skipping; retarget the
skip-on-unresolvable test at a genuinely non-accent token so it still
guards that path.
2026-07-04 18:07:10 +08:00
Fini 3f6f9d157d fix(orchestrator): flip dark icons to white on accent-token buttons
The button-contrast pass bailed whenever the button fill was a design
token (resolve_color_maybe_ref returns None for any $ref), so an
accent-filled button — the common orange filter/action button — kept
the model's default-dark icon (measured: a sliders icon at #0F172A on
a $color-accent button, unreadable on orange). Brand-accent tokens
($color-accent / primary / danger / error / success) always bind to
saturated mid-dark colours needing a white foreground, so treat that
bg as dark and let the existing override flip the children. Surface /
warning tokens are left alone.
2026-07-04 17:49:10 +08:00
Fini 2e21d94ff6 feat(ai): make script-gen the only subagent output protocol
Retire the flat-JSONL retry rung. Every subagent rung now emits a JS
program (script-gen); reduced_complexity and minimal_skills only
narrow the loaded skill set — they no longer switch the output format
to positional _parent JSONL, whose omittable parent field collapsed a
whole tree into flat siblings when a model skipped it. script-gen's
I(parent, node) makes parenting a positional argument that cannot be
dropped, and the reasoning-harvest fix made it robust across models.
parse_nodes stays for the modify/chat paths that still consume flat
node JSON; the jsonl-format generation skills are removed.
2026-07-04 17:31:22 +08:00
Fini eaa979d01c fix(renderer): paint the children of rectangle containers
A rectangle is a container in the canonical schema — it carries
clipContent like Frame/Group and models nest content inside one (an
image-area rectangle wrapping a photo, a card body, a badge holder).
The painter's NodeKind::Rect branch drew the rectangle's own fill and
returned without recursing, so every child of a rectangle vanished
behind that fill. Measured: an AI-generated travel page whose seven
destination photos each sat inside an image-area rectangle rendered
as blank cards despite the photos being fetched and embedded. Recurse
into the children (honouring clipContent) after the rectangle's own
paint, mirroring the Frame branch.
2026-07-04 17:25:58 +08:00
Kayshen-X 9471b7b1ab fix(figma): skip swapped instances in derived pre-seeding
A component-swapped instance (overriddenSymbolID present) carries
derivedSymbolData for the swapped-in component, not its base symbolID.
Pooling or geometry-seeding it under the base component's cache pinned
wrong pk→node mappings that poisoned genuine base-component instances
reusing that cache. Skip swapped instances in both seeding passes.
2026-07-04 16:48:14 +08:00
Kayshen-X 7885e25ab4 fix(figma): drop stale pre-swap derived so swapped icons render fully
A nested instance swapped via overriddenSymbolID keeps the pre-swap
component's derivedSymbolData alongside the swapped-in component's. When
the two frames are the same size the fingerprint can't tell them apart
and the stale (earlier-listed) cluster hijacks the mapping, sizing the
swapped frame wrong and clipping its icon. Cluster the derived entries
by localID and keep only the one that geometrically fits the swapped
subtree. Adds a near-exact geometric-match bonus so a near-perfect size
match outweighs the walk-order prior a stale sibling would otherwise
win on. Splits instance.rs (swap_filter.rs) and fingerprint_tests.rs
(foreign_tests.rs) to honor the 800-line cap.

Renders the Test.fig Sales-card pie icon and the other swapped card
icons at their correct size instead of clipped.
2026-07-04 16:37:48 +08:00
Kayshen-X eacc2ccbf0 fix(figma): evidence-based instance override routing + strong-fill fingerprint
Replace the walk-order virtual-GUID guessing in Strategy 2 with an
evidence-based fingerprint (size / transform / text-class / fill hints)
plus cross-instance pooled + geometry pre-seeding, so nested instance
overrides land on the right nodes. Adds foreign-session subtree
anchoring with a uniform-family fallback, nested-derived field merge,
strong fill-hint routing (image / rare-solid) with conflict-vs-
inapplicable rejection handling, and a single-axis transform-drift
score tier. Splits instance.rs walk/apply helpers into apply.rs and the
test module into fingerprint_tests.rs to honor the 800-line cap.

Renders Test.fig order-row thumbnails, breadcrumb, sidebar logo,
summary-card filters, and status chips to match the Figma source.
2026-07-04 16:37:47 +08:00
Fini 05e3326336 chore(ai): drop the now-unused loop opt-in flag parser
Flipping the loop to default-on left parse_loop_flag (the truthy
parser) with only test callers; parse_loop_off carries the opt-out.
2026-07-04 16:26:14 +08:00
Fini 9de2249164 fix(ai): stop a stray selection hijacking a new-design prompt
A section-heavy new-design prompt ('Design a … page. Include a search
section …') trips is_section_add_request, so requests_new_whole_screen
returns false; the selection-modify bias then routed the whole prompt
into run_modify_turn, where M3's flat-JSONL output was renest-dropped
to nothing ('Could not parse design nodes'). Add a section-add-blind
creation-signal veto to both routing gates so a new-design request
reaches the design pipeline regardless of an active selection.
2026-07-04 16:26:13 +08:00
Fini 0dd7277b78 fix(mcp): harvest the script past reasoning and prose preamble
The script runner only stripped a code fence anchored at position
zero, and never stripped <think> reasoning at all. A reasoning model
that keeps its thinking (MiniMax-M3 rides Adaptive) prefixes the
program with a <think> block full of draft JS plus a prose lead-in;
that went to QuickJS verbatim as source, threw a syntax error, and
dropped the model onto the fragile flat-JSONL retry rung where
omitted _parent fields collapse the whole tree into flat siblings
under the root (measured: a full travel page, 44 nodes, all piled at
origin). Strip reasoning first, then extract the fenced block from
anywhere in the response. Models with thinking disabled (GLM) were
unaffected, which is why this read as GLM-only handling.
2026-07-04 16:04:47 +08:00
Fini aa6857365a fix(ai): disable reasoning on direct-modify turns
Reasoning models burn the whole output budget inside think blocks and
emit zero design nodes on a modify turn (measured on MiniMax-M3: the
turn died in analysis prose). Same policy the design subtasks already
use; the HTTP layer maps it per provider.
2026-07-04 15:42:50 +08:00
Fini 29a5801621 feat(ai): make the design agent loop the default generation path
The A/B measured the loop ahead of the single-shot orchestrator on
audit cleanliness (11 vs 33 issues over 10 prompt pairs) at
comparable wall time, with the artboard seed guard closing its one
failure mode. OPENPENCIL_DESIGN_AGENT_LOOP=0|false|off opts back into
the orchestrator; CLI providers keep their existing path.
2026-07-04 15:42:49 +08:00
Fini 3994d0aee2 test(ai): restore the web-app negative regression for artboard seeding 2026-07-04 15:17:20 +08:00
Fini c110ca2387 test(ai): align the stale mobile-seeding assertion with the web-app guard
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.
2026-07-04 15:14:09 +08:00
Fini abbabd7662 fix(ai): restore the web-app guard in mobile artboard detection
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.
2026-07-04 15:03:37 +08:00
Fini 3b15add38c feat(ai): seed a numeric artboard when the first loop batch skips it
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.
2026-07-04 14:34:55 +08:00
Fini a8201226d9 fix(orchestrator): stop ring labels wrapping inside their forced width
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.
2026-07-04 14:27:36 +08:00
Kayshen-X 04e3cbcc69 chore(jian): update submodule to rebased main 2026-07-04 13:31:12 +08:00
Kayshen-X c7fc20be90 fix(canvas): seed bound widgets on screen switch so persisted values render on remount 2026-07-04 13:23:31 +08:00
Kayshen-X 9a11464a40 test(canvas): lock screen-marker round-trip + cross-screen bind persistence + exit reset 2026-07-04 13:23:30 +08:00
Kayshen-X 9d0cbf7964 fix(canvas): gate preview centering to app mode + resilient screen-switch rebuild 2026-07-04 13:23:29 +08:00
Kayshen-X 686536a15e feat(openpencil): add code-to-design conversion flow 2026-07-04 13:23:28 +08:00
Fini 3916caee1f test(web): re-anchor the web host's footer probes too
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.
2026-07-04 13:20:51 +08:00
Fini 1a5f411805 fix(web): handle the clear-selection chat hit in the web host
The new AIChatHit variant broke the wasm-only exhaustive match that
the default workspace build never compiles.
2026-07-04 12:56:54 +08:00
Fini c918dc9be7 test(editor): re-anchor footer hover probe after layout fixes
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.
2026-07-04 12:30:27 +08:00
Fini a3540ac2e6 feat(editor): selection targets the chat turn and shows in the panel
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.
2026-07-04 12:30:26 +08:00
Fini e891552970 feat(orchestrator): restack landing navbars misdelivered into sidebars
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.
2026-07-04 03:30:14 +08:00
Fini 20cdec460f fix(editor): give the settings connect button a real inset
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.
2026-07-04 03:25:05 +08:00
Fini 6e41b68474 chore(editor): remove the inert palette button from the chat footer
The affordance has no feature behind it yet; ship it when it does.
2026-07-04 03:21:13 +08:00
Fini a963502d1a fix(editor): inherit the model registry in fresh chat tabs
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.
2026-07-04 03:17:40 +08:00
Fini 4f0776c775 fix(editor): anchor the edited marker on measured name width
The 9px/char estimate overshoots narrow Latin glyphs, leaving a dead
gap between the file name and the marker.
2026-07-04 03:13:08 +08:00
Fini 204febe9aa fix(editor): unpark the agent cursor from the page root
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).
2026-07-04 02:45:04 +08:00
Fini 2de3356a07 fix(ai): drop retired element tool from the reveal gate
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.
2026-07-04 02:19:29 +08:00
Fini 550712b05f feat(editor): move the agent cursor at section granularity
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.
2026-07-04 02:17:38 +08:00
Fini ea4912663e chore(editor): refresh quick examples with a travel booking prompt
Replace the sports dashboard example with a detailed travel-app
explore page brief and move it to the first slot.
2026-07-04 02:17:37 +08:00
Fini 4bd7cfb7d2 feat(editor): show an edited marker for unsaved changes
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.
2026-07-04 02:17:36 +08:00
Fini 53ad362f8b fix(desktop): keep discovered models on transient probe failure
Clearing the provider's models outside the success guard emptied the
chat model picker whenever a single refresh probe hiccuped.
2026-07-04 02:16:39 +08:00
Fini 22737dbcad fix(desktop): forward terminal proxy env to CLI agents
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.
2026-07-04 02:16:38 +08:00
Fini a384a19fae feat(ai): stream per-batch reveals and progress in the design loop
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.
2026-07-04 02:16:37 +08:00
Fini 974a1d1f2e feat(mcp): rebind redrafted names and auto-bind bindless lines
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.
2026-07-04 02:16:36 +08:00
Fini b77a5d35a3 feat(ai): tighten domain anatomy and agent batching guidance
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.
2026-07-04 02:16:35 +08:00
Fini 4c021ac32c fix(orchestrator): fail fast on deterministic CLI transport errors
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.
2026-07-04 02:15:59 +08:00
Fini eb7724d48f feat(orchestrator): drain the reveal sweep before finalize restructures
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.
2026-07-04 02:15:58 +08:00
Fini a9845e6239 fix(orchestrator): append a bottom-nav subtask to app home plans
Multi-section mobile home/feed/dashboard plans that omit the tab bar
ship looking amputated; single-task flows stay untouched.
2026-07-04 02:15:57 +08:00