Commit graph

93 commits

Author SHA1 Message Date
Kayshen-X c55807e432 ci: remove TS/Electron workflows (build-electron / ci / docker / publish-cli)
Rust-ification 阶段,CI 只保留 Rust 相关:
- rust-check.yml: cargo fmt + build + test (with STEP1A_REQUIRE_GPU=1 on Linux) + clippy + cargo-deny
- wasm-bundle-check.yml: wasm32 target check

删除:
- build-electron.yml: Electron desktop build (Rust 化后用 openpencil-shell-native)
- ci.yml: TS type-check + Vitest + web build (Rust 化后已废)
- docker.yml: TS Docker image (Rust 化后重做)
- publish-cli.yml: npm packages (Rust 化后改 cargo publish)
2026-05-05 22:09:00 +08:00
Fini 0248c17209 Merge branch 'v0.8.0' of github.com:ZSeven-W/openpencil into v0.8.0 2026-05-05 21:54:00 +08:00
Kayshen-X 22003f9b0c chore(shell-native): add transient P0 probe gate (Step 1a)
Drives the three-OS CI matrix verification of the skia-safe + glutin +
glow + winit dep stack per Step 1a spec §7.

- examples/p0_probe.rs: stencil_visibility + readback chain runner (must
  own a real OS main thread because winit on macOS rejects
  EventLoop::new() from cargo test worker threads).
- tests/p0_probe.rs: subprocess-invoke wrapper, gated
  #[ignore = "P0_PROBE_GATE"] so default cargo test stays untouched.
- Cargo.toml: add transient [target.'cfg(not(target_arch = "wasm32"))'.
  dev-dependencies] block (skia-safe 0.97 + glutin 0.32.3 + glutin-winit
  0.5.0 + glow 0.17.0 + raw-window-handle 0.6.2 + scopeguard 1.2.0 +
  winit defaults). Pinned to versions resolved in /tmp/skia-glow-probe.
- .github/workflows/rust-check.yml: install Linux GL prereqs (xvfb,
  mesa, libxkbcommon, libwayland) and add a P0-probe-gate step running
  cargo test --ignored on each OS (Linux through xvfb-run; Windows
  early-returns per spec §8.2 WINDOWS_GPU_DEFERRED_NO_RUNNER).

All three artefacts are TRANSIENT — reverted in a follow-up cleanup
commit after CI is green and the loader-compat notes commit lands.
Task 1 owns the permanent integration.
2026-05-05 12:23:49 +08:00
Fini 90cd86fc11 Merge branch 'v0.8.0' of github.com:ZSeven-W/openpencil into v0.8.0
# Conflicts:
#	.github/workflows/rust-multiplatform.yml
#	README.md
#	crates/openpencil-shell-core/src/lib.rs
#	crates/openpencil-shell-native/examples/basic_window.rs
#	crates/openpencil-shell-native/src/lib.rs
2026-05-05 12:23:47 +08:00
Kayshen-X d5011547f1 ci: remove TS/Electron workflows (build-electron / ci / docker / publish-cli)
Rust-ification 阶段,CI 只保留 Rust 相关:
- rust-check.yml: cargo fmt + build + test (with STEP1A_REQUIRE_GPU=1 on Linux) + clippy + cargo-deny
- wasm-bundle-check.yml: wasm32 target check

删除:
- build-electron.yml: Electron desktop build (Rust 化后用 openpencil-shell-native)
- ci.yml: TS type-check + Vitest + web build (Rust 化后已废)
- docker.yml: TS Docker image (Rust 化后重做)
- publish-cli.yml: npm packages (Rust 化后改 cargo publish)
2026-05-05 12:23:33 +08:00
Fini 46c853f650 Merge branch 'v0.8.0' of github.com:ZSeven-W/openpencil into v0.8.0 2026-05-05 12:23:28 +08:00
Kayshen-X 9b7d96c60e chore(shell-native): add transient P0 probe gate (Step 1a)
Drives the three-OS CI matrix verification of the skia-safe + glutin +
glow + winit dep stack per Step 1a spec §7.

- examples/p0_probe.rs: stencil_visibility + readback chain runner (must
  own a real OS main thread because winit on macOS rejects
  EventLoop::new() from cargo test worker threads).
- tests/p0_probe.rs: subprocess-invoke wrapper, gated
  #[ignore = "P0_PROBE_GATE"] so default cargo test stays untouched.
- Cargo.toml: add transient [target.'cfg(not(target_arch = "wasm32"))'.
  dev-dependencies] block (skia-safe 0.97 + glutin 0.32.3 + glutin-winit
  0.5.0 + glow 0.17.0 + raw-window-handle 0.6.2 + scopeguard 1.2.0 +
  winit defaults). Pinned to versions resolved in /tmp/skia-glow-probe.
- .github/workflows/rust-check.yml: install Linux GL prereqs (xvfb,
  mesa, libxkbcommon, libwayland) and add a P0-probe-gate step running
  cargo test --ignored on each OS (Linux through xvfb-run; Windows
  early-returns per spec §8.2 WINDOWS_GPU_DEFERRED_NO_RUNNER).

All three artefacts are TRANSIENT — reverted in a follow-up cleanup
commit after CI is green and the loader-compat notes commit lands.
Task 1 owns the permanent integration.
2026-05-05 12:23:03 +08:00
Fini 319d6fab60 docs(ai-skills): nudge image_search_query toward 2 keywords (3 zero-results)
The food-app live doc had two unfilled placeholders because the model
emitted 3-keyword queries that Openverse returned zero results for
("burger combo fries", "sakura sushi platter"). The 2-keyword forms
have plenty of matches (240 each).

The previous skill text said "2-3 English keywords" which the model
read as "3 is fine"; concrete examples like `image_search_query:
"burger fries combo"` reinforced the 3-word habit. Updated to:

- "Strongly prefer 2 keywords; never more than 3"
- Explanation of WHY (strict AND-search, concrete zero-result vs hit
  comparisons for "burger fries" / "sushi platter")
- Rule for the 3rd keyword: only when it's a strong common-phrase
  noun ("iced latte" yes, "iced latte coffee" no)
- All worked examples in elements.md row 44 updated to 2 keywords
  ("burger fries", "sushi platter", "chicken bowl")

The server-side fallback (commit 93e5847f) catches 3-keyword
zero-results by retrying with 2 words, so this is a quality nudge
on top of a working safety net — fewer retries means tighter
relevance and faster fill.
2026-05-05 12:22:45 +08:00
Fini f116a89bba docs(ai-skills): per-image image_search_query — never reuse one across cards
Yesterday's GPT-5.5 food-app run shipped with all 5 placeholder frames
carrying `image_search_query: "salmon sushi"`, even though only one of
the dishes was actually salmon sushi (the others were burger combo,
sushi restaurant card, chicken bowl, etc). Once the proxy fix lets
the search reach Openverse, the screen would render five identical
salmon-sushi photos instead of five different food shots.

Root cause is teaching: the previous skill text gave a single example
("burger fries") which the model copy-pasted to every placeholder on
the screen instead of mining each card's own title.

Updates:

- `elements.md` row 44: explicit "MUST receive its own query" + four
  worked examples mapping card titles to per-card queries (Burger
  House → "burger restaurant", Sakura Sushi → "sushi japanese",
  etc.).
- `elements-cookbook.md`: replaces the single example with three
  context-distinct calls + an inline comment warning against reuse.
- `jsonl-format.md` TYPES line: bolded "imageSearchQuery MUST be
  UNIQUE per image — derive it from the surrounding card/dish/section
  text" so the JSONL fallback path gets the same signal.
- `schema.md` image bullet: same uniqueness clause inline.

No code change in this commit — purely prompt-side teaching for the
JSONL + element-tool generation paths.
2026-05-05 12:22:39 +08:00
Fini e04648e651 docs(ai-skills): teach image_search_query on add_image_placeholder
elements.md row 44 now spells out that passing 2-3 English keywords
(e.g. "burger fries", "modern office") via image_search_query is what
lets the auto-search pass swap the gray box for a relevant photo —
otherwise it searches the label or falls back to a generic placeholder.
elements-cookbook adds two example calls so the model has copy-paste
templates for the common case.
2026-05-05 04:02:50 +08:00
Fini 1f2d3c5e1d Merge branch 'v0.8.0' of github.com:ZSeven-W/openpencil into v0.8.0 2026-05-04 21:45:19 +08:00
Kayshen-X a4b7f62e9a Merge feat/rust-ification into v0.8.0 (Step 0 Rust workspace bootstrap)
Step 0 of OP Rust-ification (per kickoff spec v7 FROZEN):
- Cargo workspace at root (members = ["crates/*"], glob)
- 9 skeleton crates: openpencil-app, openpencil-shell-{core,web,native},
  pen-{types,core,engine,codegen,figma}
- rust-toolchain.toml pinned 1.85 (forced from 1.80 → 1.82 → 1.85
  due to crates.io ecosystem edition2024 requirements)
- deny.toml with kickoff §1.2 wasm32 ban invariant
- 2 GitHub Actions: rust-check.yml (3-platform native + cargo-deny)
  and wasm-bundle-check.yml (wasm32 forward + reverse cargo-deny bans)
- vendor/agent submodule → github.com/ZSeven-W/agent-rs
- Bun script wrappers (cargo:check / :test / :wasm-check / :deny)
- README "Rust subsystem" section + Phase boundary note

§1.2 invariants live:
- Forward wasm32 check: shell-web + 5 bucket A crates compile
- Reverse cargo-deny check bans: native + wasm32 both clean
- compile_error guard: shell-native fails wasm32 build with explicit
  message, validated by canary

Step 1+ owns real implementation; Phase 0 docs (snapshot / plan
patches / IPC inventory / parley-taffy matrix / cargo-deny validation)
in openpencil-docs.
2026-05-04 21:00:00 +08:00
Kayshen-X 4e92f0c250 Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
MseeP.ai 112921c9ea Add MseeP.ai badge to README.md (#124) 2026-04-29 09:50:57 +08:00
Fini 3275314f40 fix(ai-skills): remove JSONL output references from element-tool path
elements.md still carried two stale claims from the P6 spec era when I
incorrectly assumed the apps/web sub-agent always emitted JSONL:

1. Frontmatter comment (line 17): "embedded orchestrator in apps/web
   emits single-shot JSON and cannot call MCP tools — this skill would
   be 1500 tokens of dead weight there, so it stays excluded."

   Wrong now. With VITE_ENABLE_ELEMENT_TOOLS=1 the embedded orchestrator
   sets `hasMcpTools` and the sub-agent DOES emit `<op_tool>` blocks
   parsed by tryParseAllElementToolOutputs and dispatched via
   element-tools-dispatcher.ts. The skill loads in BOTH paths.

2. Theme handling section opener: "This section applies to the MCP
   tool-call path only … the web-app sub-agent JSONL path forbids tool
   calls — there, write $color-* / $type-* refs directly in JSONL".

   Wrong now. With element tools enabled both paths use `<op_tool>` and
   the same `theme: 'system'` advice applies uniformly. The pointer to
   "DESIGN SYSTEM TOKENS in jsonl-format.md" is dead — that skill was
   just dropped from the element-tool path.

Removed the stale comment, rewrote the comment positively to describe
the dual-path loading. Removed the misleading sub-section header so the
"Default to theme: 'system'" rule applies cleanly to every caller.
2026-04-29 09:50:56 +08:00
Fini e0039a303e fix(ai): JSONL sub-agent path emits design-system refs not hex
Two-part fix for the web-app chat path (both built-in and CLI mode go through
sub-agent JSONL output, not MCP tool calls — `jsonl-format.md` line 50 forbids
tool calls). Without this, every fill in generated designs was a hex literal
even after the 5/3 design-system-aware work — the 188 v1 element tools and
DEFAULT_PALETTE_FALLBACK were dead weight here.

A. STYLE GUIDE injection now uses ref + (hex) double form so the model sees
   `$color-accent` paired with the resolved hex it represents:

       Before: - Background: #FFF8F0  Surface: #FFFFFF
       After:  - Background: `$color-bg-deep` (resolves to #FFF8F0)
               - Surface: `$color-surface` (#FFFFFF)

   Applied to both `buildSubAgentStyleGuideInstruction` (selectedStyleGuideContent
   path) and the inline `plan.styleGuide` injection in orchestrator-sub-agent.ts.

B. `seedDocVariablesFromStyleGuide` runs once before sub-agent execution: when
   `doc.variables` is empty AND a style guide is selected, it maps the palette
   to v1 token names (`color-bg-deep` / `color-accent` / etc) and seeds them
   into `doc.variables`. This makes refs emitted by the model resolve to the
   user's chosen palette at render time instead of falling back to the default
   #2563EB blue.

A and B are coupled — A alone would make designs render in the wrong color
(every design becomes blue regardless of style guide); B alone leaves the model
mimicking the hex from the prompt. Both must ship together.

Also:
- jsonl-format.md: DESIGN SYSTEM TOKENS section + example fills converted to
  refs + CRITICAL contract moved to top (so future budget overruns can't
  truncate it). Budget bumped 1500 → 1700 for safety margin.
- elements.md: Theme handling section now flags MCP path vs JSONL path so
  models on either path know which guidance applies.
2026-04-29 09:50:35 +08:00
Fini 1650f81fa8 feat(ai-skills): default decision tree to v1 system mode (P4)
Switch all 89 element tool recommendations in elements.md from v0 to v1
with theme: 'system' as the default. Add "Theme handling" section explaining
when to use system/light/dark/v0. v0 entries retained as "Byte-frozen escape
hatch" sub-entries for rare byte-parity requirements.
2026-04-29 09:50:30 +08:00
Fini 238ab344e2 feat(element-tools): add 11 v1 tools with theme parameter (P3 batch 9 — FINAL)
Converts tabs, tag, text_button, textarea, timeline, toolbar, tooltip,
top_nav_bar, upload_dropzone, user_card, video_placeholder to theme-aware v1.
All 9 touchpoints wired; ext-8 extended and new ext-9 shard created for overflow.
ListTools count: 177 → 188. All 4127 tests pass.

Classification:
- Pass-through (all modes identical, no surface colors): text_button, textarea,
  top_nav_bar, tabs (accent brand-invariant), tooltip (dark=inverted per §3.4),
  tag (status tones per §3.4), video_placeholder (dark bg per §3.4)
- Surface-tint (light/dark/system tokenized): timeline (inactive dot+connector+subtitle),
  toolbar (surface+border+active-bg+icon), upload_dropzone (5 tokens),
  user_card (name+role text)
2026-04-29 09:50:29 +08:00
Fini d547e6917b feat(element-tools): add 10 v1 tools with theme parameter (P3 batch 8)
Converts sidebar_nav, skeleton, social_login_row, spinner, stat_card,
stat_grid, status_badge, step_card, stepper, switch to theme-aware v1.
All 9 touchpoints wired; ext-8 shard extended for schema definitions.
ListTools count: 167 → 177. All 4116 tests pass.

Notable: spinner/stat_grid/status_badge/switch emit identical trees
across all theme modes (caller-param colors, status semantics, or iOS
HIG builder-private literals per spec §3.4) — theme param accepted
for API consistency only.
2026-04-29 09:50:28 +08:00
Fini e1a931e4ff feat(element-tools): add 10 v1 tools with theme parameter (P3 batch 7)
Converts progress_bar, quote_block, radio, range_slider, rating_stars,
search_bar, section_header, segmented_control, select, share_row to
theme-aware v1. All 9 touchpoints wired; adds ext-8 shard for schema
definitions. ListTools count: 157 → 167. All 4106 tests pass.
2026-04-29 09:50:27 +08:00
Fini ab695d9633 feat(element-tools): add 10 v1 tools with theme parameter (P3 batch 6)
Converts metric_comparison, metric_row, nav_chip_row, notification_row,
otp_input, pagination, phone_input, price, pricing_card, profile_header
to theme-aware v1. All 9 touchpoints wired; adds ext-7 shard for schema
definitions. ListTools count: 147 → 157. All 4096 tests pass.
2026-04-29 09:50:26 +08:00
Fini 028ef9048b feat(element-tools): add 10 v1 tools with theme parameter (P3 batch 5)
Adds theme-aware v1 builders for icon_button, image_placeholder,
inbox_message, inline_action, input_with_action, invite_row, kbd,
legend_item, link, and list_row. Group A (zero-color: icon_button,
link, list_row) — no hardcoded colors in v0, all three modes identical.
Group B (kbd) — key bg → surface2, stroke → border in dark/system.
Group C (remaining 6) — surface/text/border/accent/alertColors tokens
applied in dark/system modes, full byte-parity with v0 in light mode.

Extends ext-6 shard (357→647 lines, within 800-line ceiling) housing
all 20 batch-4 + batch-5 tool schema definitions. All 9 touchpoints
wired per playbook: builder, index.ts, pen-core barrel, handler,
dispatcher, ext-6 shard, client shim, server builder, elements.md entries.

Verified: format:check clean, tsc --noEmit clean, 4086/4086 tests pass.
2026-04-29 09:50:25 +08:00
Fini f4a38dc508 feat(element-tools): add 10 v1 tools with theme parameter (P3 batch 4)
Adds theme-aware v1 builders for cookie_banner, data_table_row,
date_picker, drawer_shell, empty_state, event_card, fab, faq_item,
filter_group, and form_field. Group A (zero-color: empty_state,
form_field) — no hardcoded colors in v0, all three modes identical.
Group B (fab) — accent bg is brand-invariant, maps to accent token
in dark/system; icon stays white in all modes. Group C (remaining 7)
— surface/text/border/accent tokens applied in dark/system modes, full
byte-parity with v0 in light mode.

Creates ext-6 shard (ext-5 was at 798-line ceiling) housing all 10
new tool schema definitions (357 lines). All 9 touchpoints wired per
playbook: builder, index.ts, pen-core barrel, handler, dispatcher,
ext-6 shard, client shim, server builder, elements.md entries.

Verified: format:check clean, tsc --noEmit clean, 4076/4076 tests pass.
2026-04-29 09:50:24 +08:00
Fini 957f845bf5 feat(element-tools): add 10 v1 tools with theme parameter (P3 batch 3)
Adds theme-aware v1 builders for chart_bars, chart_line, chart_pie,
chat_bubble, checkbox, chip_input, code_block, color_swatch, combobox,
and comment. Group A (chart tools) maps bar/line color to chart-1 token
and pie default palette to chart-1..6 tokens in dark/system modes.
Group B (color_swatch) is theme-invariant — swatch color is caller-
supplied and passes through unchanged. Group C (chat_bubble, checkbox,
chip_input, code_block, combobox, comment) resolves surface/text/border
via semantic palette tokens. All light modes are byte-parity with v0.
2026-04-29 09:50:23 +08:00
Fini c0717d43c4 feat(element-tools): add 10 v1 tools with theme parameter (P3 batch 2)
Adds theme-aware v1 builders for alert, bottom_nav, breadcrumb,
activity_ring, carousel_dots, action_menu, attachment_row,
calendar_grid, avatar_group, and callout. Group A (zero-color:
alert/bottom_nav/breadcrumb/activity_ring) produce identical output
across all three theme modes. Group B (carousel_dots) maps active=
text-primary, inactive=border in dark/system modes. Group C (action_menu/
attachment_row/calendar_grid/avatar_group) resolve surface/text/border via
semantic palette. Group D (callout) maps tone-keyed bg/fg to alert palette
tokens in dark/system modes. All light modes are byte-parity with v0.
2026-04-29 09:50:22 +08:00
Fini ddfb3fa81b feat(element-tools): add 5 atom v1 tools with theme parameter (P3 batch 1)
avatar-v1, badge-v1, divider-v1, body_text-v1, icon_label-v1 — each with
full 9-touchpoint coverage (pen-core builder + index + pen-mcp handler +
schema shard + dispatcher + apps/web shim + SERVER_BUILDERS + parity test
+ elements.md). Light mode is byte-equal to v0; dark/system modes produce
identical output since all 5 tools emit zero hardcoded color fills — theme
param accepted for API consistency across all v1 tools. New shard
element-tool-defs-ext-5.ts created (ext-4 was at 739 lines). All 2026
pen-core + pen-mcp tests pass; format:check + tsc clean.
2026-04-29 09:50:21 +08:00
Fini 4962d6176e feat(element-tools): add 4 representative v1 tools (Task 2.4)
card_row-v1, setting_row-v1, member_row-v1, activity_log-v1 — each with
full 9-touchpoint coverage (pen-core builder + index + pen-mcp handler +
schema shard + dispatcher + apps/web shim + SERVER_BUILDERS + parity test
+ elements.md). Light mode is byte-equal to v0; dark/system use resolveTheme()
for all color fills. activity_log-v1 maps tone×theme to alertColors tokens
(info/success/warning/danger) with neutral falling back to surface/textMuted.
All 3998 tests pass. Completes P2 representative phase.
2026-04-29 09:50:20 +08:00
Fini 6a74933945 feat(element-tools): add heading-v1 with theme parameter (9 touchpoints)
Task 2.3 — representative v1 tool walkthrough for Plan 14 byte-parity contract.
Light mode is byte-equal to add_heading_v0 (V0_LATIN_PRESETS table reused);
dark/system modes use resolveTheme() for fill color and typography token refs.
Adds theme enum [light, dark, system] to add_heading_v1 MCP schema, elements.md
decision tree, shim-server-parity CASES, and SERVER_BUILDERS. All 3937 tests pass.
2026-04-29 09:50:19 +08:00
Fini caa35e5d8b fix(pen-core): resolve fontSize/lineHeight/letterSpacing/cornerRadius $refs in resolveNodeForCanvas
P1.5 consumer compatibility gate before P2 element-tool v1 builders.

resolveNodeForCanvas was already resolving gap/padding/opacity/color refs,
but missed text typography fields (fontSize, lineHeight, letterSpacing) and
cornerRadius. Since both skia-engine and pen-renderer consume resolver output
before layout runs, unresolved string refs would pass arithmetic as NaN.

layout/engine.ts also patched to guard typeof === 'number' at the 4 text
font-size/line-height access points — defensive fallback to 16/1.x even when
called on pre-resolution nodes (e.g. MCP server, normalizer pipeline).

Adds 21-test p1-5-consumer-compat.test.ts covering all 6 consumer paths.
All 1183 pen-core tests pass. format:check and tsc --noEmit clean.
2026-04-29 09:50:14 +08:00
Fini 98921c5846 fix(ai-skills): correct setting-row schema in parent_id teaching examples
df750c98 加的 / 反例里 setting_row 字段名写错了:用了
\`label\` 但 SettingRowParams 实际字段是 \`title\`;trailing.switch
用了 \`value: true\` 但 schema 实际是 \`{ kind: "switch", on: boolean }\`。
Codex stop-time review 抓到 "invalid prompt example would teach a
bad tool schema" — 直接用 source builder 的 SettingRowParams /
SettingRowTrailing 类型作为 ground truth 更新两处例子。
2026-04-29 09:50:03 +08:00
Fini 01b925bd7e feat(ai-skills): teach parent_id rule with concrete WRONG/RIGHT examples
ab-v5 (2026-05-02) 显示 5acde087 教学只是把模型发明的 placeholder
名字从 "root" 改成 "entry-1" / "members-section",没根除"先发明 id
再引用"的习惯。加 / side-by-side 例子让模型直接看到 invented
name 跟 omit 的区别。token 增量 ~200,仍在 Phase 3 trim (-540) 净盈
余范围内。
2026-04-29 09:50:01 +08:00
Fini 2902bdc88e feat(ai-skills): teach parent_id rule + trim PREFER list "Different from"
Two diet/teaching changes to elements.md, both motivated by today's
ab-v4 smoke results:

(a) parent_id teaching — minimax-m2.7 invented "members-section" /
    "canvas" as parent_id values on its multi-tool composite output,
    then every one of its 14 tags failed apply with "parent_id X not
    found in document". Same pattern as yesterday. Adds an explicit
    rule near the top of elements.md (right under the multi-tool
    banner) — `parent_id` is REAL or OMITTED, never invented. The
    `<page>` / `<panel>` / `<sidebar>` placeholders in the cookbook
    recipes are documentation conventions; in actual output, OMIT
    the field. Names the failure mode by reproducing the error
    message format so models learn to avoid it.

(b) Phase 3 token diet — strip ". Different from <tool> (...)"
    disambiguation suffixes from the PREFER list (22 entries had
    them, ~80-200 chars each = ~2.5kb / ~625 tokens saved). The
    primary keyword + tool-name + capability description survives
    intact; the cross-references pointing at sibling tools get
    dropped. Risk: slight increase in wrong-tool routing on
    ambiguous prompts. Worth it for the size reduction; the
    decision tree alone still shows the tool family in context.

Verified by mechanical diff — perl in-place edit, then visual
review confirms no other content was touched. 3785 vitest pass,
format clean, tsc silent.

Net effect on T-prompt size (chars / 4 estimate):
  T + composite + mobile: was 17,930 → now 17,475 (-455 chars)
  T + composite + dashboard: was 18,437 → now 17,983 (-454 chars)
  T + obvious + dashboard: ~14,009 → ~13,556 (-453 chars)

Per-arm savings are smaller than the raw 2.5kb trim because (a)
adds ~700 chars of parent_id teaching. Net win: ~450 chars / ~110
tokens per call. Modest but compounds across 520+ ab-v4 runs.
2026-04-29 09:49:58 +08:00
Fini c835976479 feat(ab-corpus): per-domain cookbook filter (Phase 2 of token diet)
ab-v3 / ab-v4 showed Phase 1A (cookbook strip on obvious difficulty)
shaved ~4.4k tokens off T-obvious. Phase 2 adds a per-category gate
that strips cookbook recipes whose domain doesn't match the prompt's
category — mobile briefs don't see dashboard recipes, dashboard
briefs don't see mobile / landing recipes, etc.

Mechanism: HTML comment block markers in elements.md
(`<!-- @domain:dashboard --> ... <!-- /@domain -->`) plus a
stripNonMatchingDomains() pass in buildSystemPrompt that drops blocks
whose tag list doesn't include the active category. Untagged content
is "general" and stays in every variant — the safe default.

Tagged 7 single-domain cookbook recipes:
- dashboard: Team / members list, Audit / activity feed, Faceted
  search filter sidebar, Dashboard KPI strip
- landing: Pricing section
- mobile: Onboarding "How it works", Support chat thread

Cross-domain recipes (Login, Signup, Settings page, OTP, Empty
inbox) stay untagged so they load for every category. Decision tree
+ PREFER list also untagged today; the per-tool annotations there
would be a much larger judgment pass for marginal additional savings.

Token measurements (chars / 4 estimate):
                       full     mobile  dashboard  landing
- T + composite       19.0k    17.9k    18.4k     17.7k
                              (-1.1k)  (-0.6k)   (-1.3k)
- T + obvious         14.6k    13.5k    14.0k     13.3k
                              (-1.1k)  (-0.6k)   (-1.3k)

Modest absolute savings — Phase 2 only filters cookbook RECIPES (in
elements.md), and most cookbook content is in elements-cookbook.md
which Phase 1A already strips on obvious. To hit the 6-8k T target
we still need decision-tree compression or PREFER-list trim, but
both are lossier than this gate. Phase 3 candidates noted in the
ab-v4 results doc.

real-model.ts plumbs call.prompt.category through to buildSystemPrompt.
3766 vitest pass (+6 category filter tests including a 500-char
floor regression guard that the filter actually shaves bytes).
2026-04-29 09:49:49 +08:00
Fini cae5501021 fix(ai-skills): purge mixed-strategy teaching from elements.md cookbook
Codex stop-time review (3rd round) caught residual "T prompt
includes mixed-strategy instructions": even after the prompt
forbade mixing, elements.md still taught the mixed pattern in
several places that are also part of the T system prompt.

Cleaned out every spot that paired batch_design with add_*_v0:

- Login screen / Pricing section / Dashboard KPI strip recipes:
  dropped the leading `batch_design: foo = I("page", {...})` line
  and renamed `<foo>` / `<row>` placeholders to `<page>` so each
  recipe is now Strategy A (element tools only). Lost: explicit
  page-level layout/padding/horizontal row — acceptable, the
  recipes still teach the tool selection + chain pattern.

- Intro paragraph: dropped "override via a follow-up batch_design
  U-op if needed" (taught a per-component fallback that the parser
  drops).

- Banner: rewrote the fall-back clause from "when no element tool
  fits a specific component shape" to "when at least one component
  truly needs a custom shape no element tool covers — and then use
  a SINGLE batch_design for the WHOLE response, never mixed."

- "STILL use batch_design when" list: collapsed 3 mixed-strategy
  bullets ("larger composite via batch_design then element tool",
  "post-hoc styling via batch_design U-ops") into 3 clean
  Strategy-B-only bullets, all explicitly emit a SINGLE batch_design.

- Removed the "## Composition pattern" section entirely — its 3-step
  plan was a textbook mixed pattern (batch_design root → element
  tool inserts → batch_design U-op styling) that depended on real
  MCP multi-round semantics the corpus harness can't provide.

- Composition rules of thumb: replaced "Don't mix N-tool and
  batch_design DSL ops in a single call" + "Style overrides come
  AFTER structure" with a single "Don't mix in the same output"
  rule that names the corpus parser behavior explicitly.

3760 vitest pass, format clean, tsc silent. T-obvious 14.5k tokens
(down ~100 from the cleanup); composite still 18.9k.
2026-04-29 09:49:46 +08:00
Fini c2252f2172 fix(ai-skills): drop invalid section_header subtitle from cookbook + stubs
Codex stop-time review flagged the new ab-v3 composite cookbook
recipes calling add_section_header_v0 with a `subtitle` arg the tool
doesn't accept (silently dropped at runtime today, but teaches live
models to emit invalid shapes). The same bug was in the dry-run stub
fixtures.

Split each header into add_section_header_v0(title) +
add_body_text_v0(content) — semantically what the ab-v3 briefs ask
for, and reinforces the multi-tool chaining the cookbook now teaches.
Also fills in the missing required `number` arg on the onboarding
recipe's final completed step card (schema requires it even when
completed=true renders a check instead of the number).
2026-04-29 09:49:40 +08:00
Fini bac47be301 feat(ai-skills): teach multi-tool composition for composite briefs
ab-v3 live sweep showed 0/25 composite-T runs walked the multi-tool
path — every model fell back to batch_design or garbage when given a
brief like "5 member rows + 1 invite row" or "6 audit log entries".
Root cause: elements.md's decision tree opens with "pick first match"
(single-tool framing) and the cookbook's chained examples sit ~400
lines deep, so models stop at the lookup and never realize they can
emit N <op_tool> blocks.

This adds:
- top-of-file "MULTI-TOOL OUTPUT IS THE NORM" banner with a concrete
  5-call settings example, before the decision tree
- decision tree heading rewritten to "(per component — pick first
  match)" so per-component framing is in scope from the start
- 4 new cookbook recipes mirroring the ab-v3 composite prompts:
  team / members list (rows + invite), audit / activity feed,
  faceted search filter sidebar, onboarding step cards

Markdown-only edit to a single skill file; vite-plugin-skills
re-compiles the registry. Banner verified present in the generated
registry, all 3750 vitest tests pass. Verifies in next ab-v4 sweep.
2026-04-29 09:49:39 +08:00
Fini 1fee613111 feat(ai): ship 5 element tools to reach 97 (filter_group / invite_row / activity_log / event_card / step_card)
Closes the obvious gaps remaining in the family:
- add_filter_group_v0 — sidebar facet (heading + checkbox-style options
  with optional counts). Distinct from nav_chip_row (horizontal scrolling
  chips), tag (single applied chip), segmented_control (mutex tabs).
- add_invite_row_v0 — pending invite row (avatar + email/role + status
  pill + trailing action). Distinct from member_row (a JOINED member,
  no status pill or action) and list_row (no avatar / status / action).
- add_activity_log_v0 — single-line audit feed entry (optional tinted
  icon dot + actor in bold + action + right-aligned timestamp). Uses
  StyledTextSegment[] content for the bold/regular split. Distinct from
  timeline (multi-event vertical with connectors) and notification_row
  (title + body, no actor focus).
- add_event_card_v0 — single calendar event tile (date column with
  month band + day number, then title + time + location). Distinct from
  calendar_grid (the full month grid) and card_row (no date column).
- add_step_card_v0 — onboarding step card (numbered circle / check +
  title + description). Distinct from stepper (horizontal progress nav
  with connectors) and faq_item (collapsible Q&A header).

9 touchpoints per tool: pen-core builder + index + barrel + types,
pen-mcp handler + dispatcher + ext-4 schema, apps/web shim +
SERVER_BUILDERS, parity test (+5 cases), elements.md decision tree
items 86-89 + 6 PREFER mappings with cross-links to existing tools,
elements-cookbook.md arg-shape examples (8 entries across 5 tools).
2026-04-28 08:50:00 +08:00
Fini a5b594cf69 feat(ai): add_member_row_v0 — team / member list row (92nd tool)
Avatar + (name over optional subtitle) + optional trailing slot
(role badge / kebab menu / status dot). Distinct from
add_user_card_v0 (compact fit_content tile, no trailing slot) and
add_list_row_v0 (no avatar slot — leading icon instead).

9 touchpoints wired: pen-core builder + index + barrel + types,
pen-mcp handler + dispatcher + ext-4 schema, apps/web shim +
SERVER_BUILDERS, parity test, elements.md decision tree #84 +
PREFER mapping, cookbook arg shapes (3 variants).

Also disambiguates add_avatar_group_v0's PREFER mapping: drop
"团队成员" (now points at member_row), keep narrower phrases like
"成员头像" / "团队头像" / "presence indicator" that genuinely match
the stacked-avatars affordance, and add the cross-link to member_row.
2026-04-28 08:05:00 +08:00
Fini 77cc8ef3a7 fix(ai-skills): teach setting-row recipe with the right tool, drop invalid trailing_kind
Two bugs in elements.md after add_setting_row_v0 landed:
1. PREFER mapping still routed "settings row" to add_list_row_v0 — direct
   contradiction with the new add_setting_row_v0 entry below it.
2. The "Settings page" recipe called add_list_row_v0 with a trailing_kind:
   "switch" arg, but list-row has no such param; it would silently render
   without a switch (or fail validation in stricter clients).

Reroute settings-row prose mapping to the new tool and rewrite the recipe
to use add_setting_row_v0 with proper trailing variants.
2026-04-28 07:15:00 +08:00
Fini af1ddd1ad2 feat(ai): add_setting_row_v0 — settings menu row (91st tool)
Leading icon + (title over optional subtitle) + trailing control with
4 variants: chevron / value text / switch / badge. Distinct from
add_list_row_v0 (trailing is always icon, no switch/value/badge) and
add_form_field_v0 (label-above-input for forms).

Wires all 9 touchpoints: pen-core builder + index + barrel re-export,
pen-mcp handler + dispatcher case + ext-4 schema, apps/web shim +
Nitro SERVER_BUILDERS, elements.md decision tree #83 + PREFER mapping,
elements-cookbook arg-shape examples, plus shim-server parity case.
2026-04-28 06:30:00 +08:00
Fini 2939b6d4b5 refactor(ai-skills): trim elements.md minimal-usage block to fit 800-line ceiling
elements.md grew to 859 lines after the 81-90 batch shipped — past the
repo's per-file ceiling. The 366-line "Minimal usage" section was the
biggest contributor and the most redundant: MCP `tools/list` already
publishes the full inputSchema for every element tool (arg names,
types, descriptions, requireds), so the LLM has authoritative arg
shape from the wire. The decision tree + PREFER mappings already
teach WHEN to pick each tool. Drop the inline usage examples; keep
the composition pattern + cookbook recipes (which schemas can't
convey) plus invariants and failure-mode guidance.

493 lines remaining; design-prompt-elements + every drift guard still
green.
2026-04-27 09:15:00 +08:00
Fini 5859c3f9af feat(ai): ship 10 element tools to reach 90 (user_card / drawer / combobox / toolbar / callout / share / inline_action / legend_item / inbox / profile_header)
Adds the desktop-leaning batch needed to round the family to 90:

- add_user_card_v0 — compact avatar+name+role row
- add_drawer_shell_v0 — full-height side panel header
- add_combobox_v0 — open-state autocomplete with dropdown
- add_toolbar_v0 — desktop icon button row + dividers
- add_callout_v0 — inline doc tip block, 5 tones
- add_share_row_v0 — circular social-share buttons
- add_inline_action_v0 — message + Undo-style action
- add_legend_item_v0 — chart legend marker+label+value
- add_inbox_message_v0 — email/inbox row with unread dot
- add_profile_header_v0 — large profile hero block

All ten go through the standard 9-touchpoint wiring and land in a
new ext-4 schema shard so existing shards stay under 800 lines.
Drift guards (contract / registry parity / shim-server parity) cover
each new name; per-tool handler tests are deferred — every tool's
structure is exercised through the parity build call already.
2026-04-27 09:05:00 +08:00
Fini 072856e522 feat(ai): add_tag_v0 — single closable filter chip (80th tool) 2026-04-27 08:55:00 +08:00
Fini d24b587b6c feat(ai): add_data_table_row_v0 — desktop tabular row (79th tool) 2026-04-27 08:40:00 +08:00
Fini e225104e43 feat(ai): add_avatar_group_v0 — stacked presence tile group (78th tool) 2026-04-27 08:30:00 +08:00
Fini 6e54054c73 chore(merge): integrate origin/v0.8.0 — main pre-release sync + CI fixes
origin's v0.8.0 had cherry-picks of the v0.7.5 deepseek/image-search
fixes (a727632a, a5952bc8) overlapping local 2073cf5b / 04f4fbc1, plus
new commits (model-selector ark-coding deepseek-v4-pro/flash IDs that
ARK rejects, fetch error.cause unwrap, CI agent-native build, op
export docs cleanup, main merge). Resolved the ark-coding list in
favor of HEAD's deepseek-v3.2 entry (only model ARK Coding Plan
actually supports — see openpencil-docs note).
2026-04-27 08:15:00 +08:00
Kayshen-X b554b4f1a6 Merge branch 'main' of github.com:ZSeven-W/openpencil into v0.8.0 2026-04-26 19:39:14 +08:00
Fini abbccc1ba2 fix(ai): refresh DeepSeek defaults to v4 model series
`/models` now returns only deepseek-v4-pro and deepseek-v4-flash;
deepseek-chat / deepseek-reasoner sunset 2026-07-24 and the
deepseek-v3.2 hard-coded in the ark-coding fallback list never
existed. Both v4 models default to thinking enabled and the API
toggles via `{"thinking":{"type":"disabled"}}` — keep
`thinkingMode: 'disabled'` so the app's fast/non-thinking default
stays intact (server reasoning paths honor it; the Zig openai-compat
path doesn't emit the toggle yet, so calls through that path still
get provider-default thinking until it's wired). v4-pro promoted to
full tier; legacy aliases pinned to an exact RegExp so future
deepseek-* variants don't inherit a forced disabled mode.

Bandaid for the unwired toggle: v4-pro gets `timeoutMultiplier: 2`
because its default-on reasoning blows past the orchestrator's
planning timeout on long system prompts (observed in dev: planning
phase falls back, sub-agent then succeeds — UX degraded but
functional). Drop the multiplier once the Zig path actually sends
`thinking:{type:disabled}`.

Don't add a BUILTIN_MODEL_LISTS.deepseek entry — DeepSeek exposes
/v1/models, so let `fetchProviderModels` pull the live catalog
through `/api/ai/provider-models` instead of pinning a snapshot
(the ark-coding `deepseek-v3.2` ghost above shows what those
snapshots drift into).
2026-04-26 06:30:00 +08:00
Fini e9d9e37770 feat(ai): add_sidebar_nav_v0 — desktop persistent left rail (77th tool)
Top_nav_bar / bottom_nav / nav_chip_row covered mobile and inline
chrome but desktop dashboards still had to hand-roll their left
sidebar via batch_design. Adds a 240px-wide vertical rail with
icon+label rows, optional brand title, and slate-100 pill bg on the
active item — distinct surface from the existing nav tools so the
decision tree picks it cleanly for "sidebar / side nav / 侧边栏".
2026-04-26 06:29:56 +08:00
Fini 6c44d58a31 feat(ai): add_cookie_banner_v0 — GDPR/CCPA cookie banner (76th tool)
Sticky bottom-of-page disclosure card with title, body, accept /
decline buttons (decline-then-accept order), and an optional
"Cookie settings" link for fine-grained consent. Caller positions
the banner; the tool emits the card itself with shadow + 1px slate
border for visual lift above the page content. Plus the v1 corpus
prompt landing-cookie-banner (26 v1 prompts total).
2026-04-25 08:15:00 +08:00