Commit graph

1941 commits

Author SHA1 Message Date
Fini 1e3fdc91e1 chore(deps): bump jian for main-axis fill_container grow
Points the jian submodule at the layout fix that grows a main-axis
fill_container child (a sidebar footer spacer) into the column remainder.
2026-07-02 21:21:52 +08:00
Fini ef4a2f7623 feat(ai): require populated, complete content in the design loop
Generation density was inconsistent — some runs shipped a full dashboard,
others stopped at a header + four stat cards + a two-row table. A vision-less
model cannot use the get_screenshot completeness check (it never sees the
render), so it declared done on a coherent-but-sparse skeleton. Add a text-based
completeness bar it CAN follow: tables need >=6 realistic rows, a dashboard needs
its full section set (stats + primary table + a secondary section), and every
list/card carries varied data.
2026-07-02 21:21:51 +08:00
Fini 6ee624713f fix(orchestrator): fill-height the app-shell sidebar so its footer sinks
An already-row shell whose sidebar hugs its content (height=fit_content) is
only as tall as its nav, so a space_between / fill_container footer inside it has
no room to sink and floats mid-page (measured: a 260x532 sidebar in a 260x1234
row stranded the profile card 700px above the bottom). ensure_split_shell_is_row
now also promotes the sidebar of an ALREADY-horizontal shell to fill_container
height, not just the freshly-flipped case.
2026-07-02 21:21:50 +08:00
Fini 6306b9e4cf feat(orchestrator): wrap text that overflows its resolved block
A fill_container block keeps min-size 0 so a fixed sibling gets its space, but
that lets it shrink below its fit_content text — the text then overflows into
the next column (measured: a 260px sidebar schedule row painted the client name
over the appointment time). Add a geometry-driven pass: when a text resolves
wider than its parent block, constrain it (width fill_container + textGrowth
fixed-width) so it wraps inside instead of overflowing.
2026-07-02 21:21:49 +08:00
Fini dc10e9bcc5 feat(ai): require image fills on avatar/photo slots
The design-loop prompt teaches the G(...) image-fill op but never required
using it, so a model built avatar/photo frames and left them as empty colored
squares. Add a hard rule: every avatar, photo, thumbnail, hero, or logo slot
must get an image fill via G(id, "search", "<subject>") in the same batch
that creates the frame.
2026-07-02 21:21:48 +08:00
Fini d65d70d21d feat(ai): dashboard domain skill + image-prompt guidance
Add a dashboard domain skill and teach models to emit an imagePrompt alongside
imageSearchQuery so a configured generation model produces a rich image while
stock search stays the fallback.
2026-07-02 21:21:47 +08:00
Fini 7459f07d53 feat(agent): image generate-vs-search enrichment
When a generation profile is configured, generate images from the node-bound
prompt; otherwise search stock placeholders. Both paths keep the prompt/query on
the node so it can be re-generated or re-searched later.
2026-07-02 21:21:46 +08:00
Fini d03c2d7dd3 fix(agent): disable reasoning + raise budget in the design tool-loop
The OpenAI-compat tool-loop body never sent the reasoning-off field, so a GLM/
MiniMax reasoning model spent its whole per-turn budget on hidden reasoning and
truncated the design mid-op. Send thinking:{type:disabled} for those models in
the loop body (matching the single-shot path) and raise the per-turn output
budget for headroom.
2026-07-02 21:21:45 +08:00
Fini 32192b9348 feat(orchestrator): geometry validation + layout repair passes
Resolve real layout geometry to correct table-column overflow and collapsed
fill-container heights, iterating until stable. Add whole-root passes: sidebar
app-shell reshape + content eviction + split-shell row layout, table column
gap/regroup, footer sink, surface discipline, scaffold padding. The design-loop
finalize backstop runs these once at loop end and injects background-contrasting
fills for fill-less text. run.rs: continue past a failed subtask and compute
zero-content before cleanup.
2026-07-02 21:21:44 +08:00
Fini 396f75eb42 feat(orchestrator): structured generation protocols
Add a parent-by-reference program DSL and an executable-script protocol with
best-effort per-line parsing, so a truncated or malformed line drops only that
op instead of the whole design. Route protocol selection in prompt/subagent and
normalize flex keywords in parse. Registers the new modules in lib.
2026-07-02 21:21:43 +08:00
Fini 6eb3527ebb fix(mcp): tolerate weak-model batch_design field dialects
Map Figma/flex auto-layout field names (layoutMode/direction/itemSpacing/
strokeWeight and nested {type,gap,padding} / {Horizontal:{…}} layout objects)
onto the flat schema, recover image src from url/source aliases, normalize
textGrowth/sizing keywords, and quote bareword DSL values — so a single
unfamiliar spelling no longer drops the whole node or op.
2026-07-02 21:21:42 +08:00
Fini 9e9cdec7cf feat(smoke): OPENPENCIL_SMOKE_PROGRAM mode runs a batch_design program headless
Reads a program file, runs it through op_mcp::batch_design_snapshot, applies, saves
the .op (postProcess off = raw structure) — the headless harness for benchmarking
the program-DSL path vs flat JSONL across weak models.
2026-07-02 21:21:41 +08:00
Fini b43cee87e5 feat(orchestrator): program-DSL generation protocol (parent-by-reference)
OPENPENCIL_PROGRAM_GEN makes the sub-agent emit a batch_design program
(binding=I(parent,{...})) instead of flat _parent JSONL; nesting by captured
binding makes weak-model row decomposition / header-only tables near-inexpressible.
Runs the existing op_mcp executor to a section forest. Gated to the full first
attempt; drops the conflicting jsonl-format skill so PROGRAM_FORMAT governs alone.
2026-07-02 21:21:40 +08:00
Fini 618f60df37 fix(mcp): harden batch_design DSL parser against weak-model JSON typos
split_operations now groups by operation-start grammar + bracket depth (not a
quote/bracket state machine), so a stray unbalanced quote can no longer swallow
following operations. parse_json_arg gains lenient repairs for fused close-quote+
comma, missing opening quote on a string value, and unclosed trailing brackets;
normalize_node_shape drops an empty stroke (0-length PenStroke) instead of failing
the whole node. +5 regression tests (369 pass).
2026-07-02 21:21:39 +08:00
Fini b29e50b632 fix(ai): add connect + overall timeouts to builtin LLM HTTP client 2026-07-02 21:21:38 +08:00
Fini b7deb369dc feat(orchestrator): app-shell content gutter + sidebar footer sink 2026-07-02 21:21:37 +08:00
Fini 7956595853 feat(orchestrator): table-repair post-pass + thread root id through cleanup
Weak models emit a table as a header row followed by FLAT row-indexed sibling
cells (R1 Client Cell, R1 Visit, …, R2 Client Cell, …) that render stacked
vertically with full-width status bars. table_repair::regroup_flat_table_rows
groups them into Table→Row→Cell with header-aligned column widths. Detection is
narrow and never guesses (adversarial review caught heuristic-header /
chunk-of-N over-firing on toolbars/feeds): it requires an explicit table header
AND every cell to carry an R{n} row index, aborting on any ragged/ambiguous run.

ReplaceSubtree allocates a fresh root id, so the structural restructures
(app_shell + table_repair) ran via a new apply_root_transform helper that
returns the current root id; run_cleanup_passes threads it into the subsequent
per-root passes instead of the stale id (which they'd otherwise no-op on).
2026-07-02 21:21:36 +08:00
Fini 46fe45a5b0 feat(orchestrator): app-shell restructure for flat sidebar dashboards
Weak models emit a desktop dashboard's sidebar either as a full-width band on a
vertical root or crammed into a horizontal row with every section — both render
broken (the removed bespoke scaffold used to pre-build the two-column root).
app_shell::reshape_sidebar_to_app_shell detects a sidebar dashboard and rewrites
it to a horizontal [sidebar(260) | content-column(fill)] shell, run in the shared
cleanup::run_cleanup_passes finalize point (covers orchestrator + agentic loop).
A layout==vertical|none guard on fix_horizontal_overflow stops it re-widening the
narrowed sidebar. Detection is hardened (strong sidebar token + structural
dashboard-content gate) against restaurant/landing/top-nav/mobile/multi-screen
false-positives; 14 unit + 1 integration test, verified end-to-end via op-smoke.
2026-07-02 21:21:35 +08:00
Fini e830759cab fix(ai): classify product-noun prompts as Design intent
classify_intent matched only creation verbs, so a noun-phrase request like
"Luxury webapp for managing barbershop clients" fell through to the weak chat
loop instead of the orchestrator. Add product/app nouns (webapp/web app/website/
app for/mobile app/admin panel/saas + 网站/网页/小程序/后台) to DESIGN_KEYWORDS.
2026-07-02 21:21:34 +08:00
Fini a4c71ea2c3 fix(ai): disable thinking for reasoning models on design turns
Reasoning models (glm-5.x / minimax) burn their whole token budget on hidden
<think> and emit an empty design when thinking is left on (glm-5.2: thinking≈30k,
text=0). design_turn_thinking_mode forces ThinkingMode::Disabled for any model
whose profile is thinking_disabled, applied across all design-capable paths —
the design-agent loop, the builtin tool-executing chat loop, and sub-agent
spawns. Claude (thinking productive) keeps the chat default.
2026-07-02 21:21:33 +08:00
Fini 4be2355174 chore(render-parity): SSIM/bbox/ΔE per-node diff harness
diff_nodes.py computes windowed-SSIM + bbox-hit + ΔE76 + color_de per node and
classifies each as PASS / PAINT / LAYOUT; prep_parity.sh drives pen2op→.op→
render-shots vs Pencil export_nodes baselines.
2026-07-02 21:21:32 +08:00
Fini fb2ebb62ac fix(loader): .op render-parity — fixed-height clip, status-bar box, path geometry
A clipped frame with an explicit numeric height now honours that height and
clips overflow (layout_repair no longer lets the root grow to content), matching
Pencil's fixed-height artboards. Status-bar shell strokes are suppressed in the
scene path (width-independent) so they don't paint a stray black frame, and
legacy .op path nodes remap geometry→d so authored paths aren't dropped.
2026-07-02 21:21:31 +08:00
Fini 0730e1ccce feat(host): headless render-parity tooling — bundled fonts, margin env, layout dump
Bundle 11 OFL design-font TTFs and register them with jian-skia at startup so
--render-shots reproduces Pencil's glyph metrics without system fonts.
OPENPENCIL_RENDER_MARGIN tightens the export crop to match Pencil export_nodes
(no frame), and OPENPENCIL_DUMP_LAYOUT prints every node's computed rect as JSONL
to diff our layout against Pencil snapshot_layout element-for-element. Bumps
vendor/jian to the bundled-font + Auto=no-wrap render-parity commits.
2026-07-02 21:21:30 +08:00
Fini c179fd2837 refactor(orchestrator): simplify — remove planning mode-rotation ladder, concurrent multi-screen path, and dashboard bespoke scaffold (−6.9k lines)
Data verdict (copy-pencil-verdict.md): weak-model M3 quality comes entirely from the
SEQUENTIAL deterministic core (manifest element-builders + role-resolver + post-passes
+ finalize), exercised at concurrency=1. The 3 scaffold strategies / mode-rotation /
per-subtask retry ladder are removable complexity not paid for by M3. Collapsed to a
single sequential path; concurrent (perf-only) folds to sequential; dashboard folds into
the generic path (cleanup_desktop_dashboard already handles sparse rows). Kept the
deterministic core 100% intact + BufferDocSink/clamp_concurrency (spawn_agents) + the
dashboard normalizer trio (feeds plan_normalize). M3 gate held within noise (50%→ the one
flip re-ran 2/3 PASS; 7/8 prompts identical; no new failures from removed code).
2026-07-02 21:21:29 +08:00
Fini cd2131ce28 feat(smoke): minimal scaffold seed for loop (OPENPENCIL_SMOKE_LOOP_SEED) reusing build_fallback_plan; data-gate harness for orchestrator-vs-loop migration 2026-07-02 21:21:28 +08:00
Fini 958c544293 feat(ai-skills): design-agent loop teaching prefers emit_elements (high-level element kinds) over raw batch_design primitives 2026-07-02 21:21:27 +08:00
Fini 03fbc8fbd8 feat(host): emit_elements loop tool — element-manifest builders (el:kind→role-tagged subtree) as a 15th design tool, reusing op_orchestrator::manifest::parse_manifest 2026-07-02 21:21:26 +08:00
Fini 02f31477b9 feat(ai-skills): budget pin for forced skills (trim_by_budget_pinned) + dual-protocol (type:ref/el:ref) component-composition teaching 2026-07-02 21:21:25 +08:00
Fini cc2846d618 feat(orchestrator): force-include component-composition teaching when library present + el:ref/type:ref protocol-branched manifest 2026-07-02 21:21:24 +08:00
Fini 03d7c9b437 feat(mcp): el:ref component-instance kind in Element-Manifest IR (manifest-arm composition → PenNode::Ref) 2026-07-02 21:21:23 +08:00
Fini 0c2fdf8650 feat(loader): merge library masters onto separate Components page (avoids active-page pollution / dashboard scaffold collision) 2026-07-02 21:21:22 +08:00
Fini a67723fbc4 feat(editor-core): append_components_page_masters — design-kit masters on a hidden Components page, active design page stays clean 2026-07-02 21:21:21 +08:00
Fini 77f60a4efb fix(editor-core): ref slot-children fallback — instance inline children fill empty-master shells (Pencil slot-component pattern)
Pencil's slot-component pattern uses an empty styled master (slot:[...] + zero children)
with the instance supplying content via its own inline children. expand_ref only
populated children from the master, dropping instance content → empty shell → blank
white circle in converted templates. Now falls back to instance children (remapped)
when the master has none. Fixes converted-template stat-card rendering + composed-ref
content. +2 ref_resolve tests +1 variables_resolve nested-token regression guard.
2026-07-02 21:21:20 +08:00
Fini 72dbf535d5 chore(editor-ui): chat panel + checklist working-tree progress (WIP) 2026-07-02 21:21:19 +08:00
Fini 5b612ed6c6 feat(smoke): headless agentic-loop mode (OPENPENCIL_SMOKE_LOOP) + design-library loading for benchmarking 2026-07-02 21:21:18 +08:00
Fini 7b1307d72e feat(ai-skills): integrate harvested Pencil guide depth (web-app/mobile/landing/slides/dashboard/design-system) + component-composition + shader-fill teaching 2026-07-02 21:21:17 +08:00
Fini c80f43dbf5 feat(desktop): chat-session loop bridge (finalize/spawn) + smoke library load + image downscale 2026-07-02 21:21:16 +08:00
Fini ed8f763af4 feat(host): agentic-loop screenshot/finalize/spawn bridge + real vision providers + smoke library env 2026-07-02 21:21:15 +08:00
Fini 31ca7c1168 feat(orchestrator): loop-finalize backstop + concurrent spawn-agents + vision validation providers + component-manifest prompt 2026-07-02 21:21:14 +08:00
Fini 89fbee2095 feat(editor-core): mesh/shader fill state + ref id-remap + chat-session state 2026-07-02 21:21:13 +08:00
Fini 41f46ff0d2 feat(mcp): batch_design ref-node emission + full PenFill passthrough + set_node_stroke_side_width tool + get_editor_state 2026-07-02 21:21:12 +08:00
Fini 8adb09220c fix(lint): typography detector adjustment 2026-07-02 21:21:11 +08:00
Fini e6c14f9550 feat(render): skia mesh-gradient via draw_vertices + SkSL shader paint (shader_cache) 2026-07-02 21:21:10 +08:00
Fini bf4a741d8e feat(loader): mesh/shader fills + .pen→.op legacy normalize hardening + pen2op bin + design-library merge
Bumps jian submodule for mesh-gradient/shader PenFill variants. normalize_legacy_doc
gains 8 .pen-compat repairs (stroke-fill string, gradient fill, icon/prompt nodes,
$ref cornerRadius, single-object fill). New pen2op bin converts Pencil .pen→.op.
library.rs merges a .lib.op's reusable masters+variables into a working doc.
2026-07-02 21:21:09 +08:00
Kayshen-X 89e5699e69 fix(ai): chat panel hover/close/send-slot refinements (#41/#42/#43)
- #41: collapse chevron renders through IconButton so it shows the ghost
  button-hover wash instead of only swapping the icon color
- #42: send/stop share one circle slot (toggle in place); footer cluster
  sits snug with no reserved stop gap; hit-test routes streaming->stop else send
- #43: example cards stay clickable without a connected model (clicking one
  fills the input; sending still requires a model)
- tab close glyph shows only while the tab is hovered (inset still reserved
  on the active tab so the title doesn't reflow)
2026-07-02 21:21:08 +08:00
Kayshen-X fc58a4601d chore: bump jian submodule (node promotion for batch_design) 2026-07-02 21:21:07 +08:00
Kayshen-X a7d73ebb62 feat(ai): pencil-style agentic design tool-loop, multi-chat tabs, #27 panel restyle
Built-in design generation now runs as an agentic MCP tool-loop (reusing the
agent-rs BuiltInProvider), gated behind OPENPENCIL_DESIGN_AGENT_LOOP / the
Settings experimental toggle; the orchestrator stays the default.

- design-agent system prompt + in-process design toolset (parity-locked with
  the MCP surface) + flag-gated Intent::Design routing
- spawn_agents execution as sequential sub-loops + live creation-mode badges
  (per-agent glow + 'N/M designing...' header)
- new MCP tools: get_guidelines, ToolSearch, get_screenshot, get_editor_state,
  export_nodes, spawn_agents; style-guide local audit
- #27 AI panel restyle: rounded tool cards + green check-rings, gray user
  bubbles, model-pill bottom toolbar, header, empty-state pills, the
  PARALLEL AGENTS (agent_team_size) 1x-6x chip dropdown
- multi-chat tabs: ChatSessions model (Deref-to-active) + tab row UI
  (switch / close / + / Cmd+T) with each run bound to its tab

Large checkpoint commit spanning the working tree (Rust shell crates).
2026-07-02 21:21:06 +08:00
Kayshen-X 2ccb063b28 feat(ai): drain the generation overlay gracefully when a design turn ends
On natural completion the design-turn teardown now calls
agent_indicators::finish_if_epoch instead of end_if_epoch: the already
queued reveals keep playing at the queue cadence and the whole overlay
(cursor + borders) clears itself once the last one lands, so the tail of
a slow 160ms stream is no longer snapped away mid-reveal.

A user stop / abort still tears down immediately via end_if_epoch, so a
cancelled generation does not keep animating. Wired across the
orchestrator run guard, the web design session, the host-core
DesignSession drop, and the desktop sub-agent / design-loop teardown.

Updates the desktop lifecycle tests to match: the frame-clear test now
drives past the drain window before asserting the overlay cleared, and a
shared registry test-lock serializes reveal-streaming turns against the
exact animation-deadline assertion (the registry is process-global).
2026-07-02 21:21:05 +08:00
Kayshen-X 05986b1eab feat(canvas): animate AI design generation with an agent pointer cursor
Each agent gets an arrow cursor that flies between the nodes it places
along the reveal schedule, arriving as each node scale-pops in; the
element currently being placed carries a breathing border. Replaces the
old dashed-border reveal fade (canvas_agent_overlay removed).

Reveal cadence is slowed to a uniform 160ms queue and retained for the
whole run, so the cursor parks on the last placement between streamed
chunks instead of fading out. Splits agent_indicators tests into a
sibling file to stay under the 800-line cap.
2026-07-02 21:02:27 +08:00
Kayshen-X ad52b1386b ci(wasm): install pinned modern binaryen instead of apt v108
The WASM bundle build (#56) and op-web-sdk bundle workflows install binaryen
via apt, which on the ubuntu runner is v108 (2022) — too old for rustc 1.94's
wasm feature set. It lacks the --enable-bulk-memory-opt flag (the original
'Unknown option' failure) AND cannot validate the memory.copy/fill opcodes
rustc now emits, so wasm-opt rejects the bundle with 'all used features should
be allowed'. Download a pinned binaryen version_123 release (matches the local
dev toolchain) and prepend it to PATH. Verified locally: the full
check-wasm-bundle.sh pipeline passes with binaryen 123 against a fresh
rustc-1.94 wasm build.
2026-07-02 02:05:42 +08:00