openpencil/packages/pen-core/src/element-builders
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
..
action-menu-v1.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
action-menu.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
activity-log-v1.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
activity-log.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
activity-ring-v1.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
activity-ring.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
alert-v1.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
alert.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
attachment-row-v1.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
attachment-row.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
avatar-group-v1.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
avatar-group.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
avatar-v1.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
avatar.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
badge-v1.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
badge.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
body-text-v1.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
body-text.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
bottom-nav-v1.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
bottom-nav.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
breadcrumb-v1.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
breadcrumb.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
calendar-grid-v1.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
calendar-grid.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
callout-v1.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
callout.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
card-row-v1.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
card-row.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
carousel-dots-v1.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
carousel-dots.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
chart-bars-v1.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
chart-bars.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
chart-line-v1.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
chart-line.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
chart-pie-v1.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
chart-pie.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
chat-bubble-v1.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
chat-bubble.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
checkbox-v1.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
checkbox.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
chip-input-v1.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
chip-input.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
cjk-detect.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
code-block-v1.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
code-block.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
color-swatch-v1.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
color-swatch.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
combobox-v1.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
combobox.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
comment-v1.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
comment.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
cookie-banner-v1.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
cookie-banner.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
data-table-row-v1.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
data-table-row.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
date-picker-v1.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
date-picker.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
divider-v1.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
divider.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
drawer-shell-v1.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
drawer-shell.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
empty-chart-v1.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
empty-chart.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
empty-state-v1.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
empty-state.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
event-card-v1.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
event-card.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
fab-v1.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
fab.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
faq-item-v1.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
faq-item.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
filter-group-v1.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
filter-group.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
form-field-v1.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
form-field.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
heading-v1.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
heading.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
helpers.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
icon-button-v1.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
icon-button.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
icon-label-v1.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
icon-label.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
image-placeholder-v1.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
image-placeholder.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
inbox-message-v1.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
inbox-message.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
index.ts Merge feat/rust-ification into v0.8.0 (Step 0 Rust workspace bootstrap) 2026-05-04 21:00:00 +08:00
inline-action-v1.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
inline-action.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
input-with-action-v1.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
input-with-action.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
invite-row-v1.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
invite-row.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
kbd-v1.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
kbd.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
legend-item-v1.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
legend-item.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
link-v1.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
link.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
list-row-v1.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
list-row.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
member-row-v1.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
member-row.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
metric-comparison-v1.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
metric-comparison.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
metric-row-v1.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
metric-row.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
modal-shell-v1.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
modal-shell.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
nav-chip-row-v1.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
nav-chip-row.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
notification-row-v1.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
notification-row.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
otp-input-v1.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
otp-input.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
pagination-v1.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
pagination.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
phone-input-v1.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
phone-input.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
price-v1.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
price.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
pricing-card-v1.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
pricing-card.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
profile-header-v1.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
profile-header.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
progress-bar-v1.ts Merge feat/rust-ification into v0.8.0 (Step 0 Rust workspace bootstrap) 2026-05-04 21:00:00 +08:00
progress-bar.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
quote-block-v1.ts Merge feat/rust-ification into v0.8.0 (Step 0 Rust workspace bootstrap) 2026-05-04 21:00:00 +08:00
quote-block.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
radio-v1.ts Merge feat/rust-ification into v0.8.0 (Step 0 Rust workspace bootstrap) 2026-05-04 21:00:00 +08:00
radio.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
range-slider-v1.ts Merge feat/rust-ification into v0.8.0 (Step 0 Rust workspace bootstrap) 2026-05-04 21:00:00 +08:00
range-slider.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
rating-stars-v1.ts Merge feat/rust-ification into v0.8.0 (Step 0 Rust workspace bootstrap) 2026-05-04 21:00:00 +08:00
rating-stars.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
README.md Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
resolve-theme.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
search-bar-v1.ts Merge feat/rust-ification into v0.8.0 (Step 0 Rust workspace bootstrap) 2026-05-04 21:00:00 +08:00
search-bar.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
section-header-v1.ts Merge feat/rust-ification into v0.8.0 (Step 0 Rust workspace bootstrap) 2026-05-04 21:00:00 +08:00
section-header.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
segmented-control-v1.ts Merge feat/rust-ification into v0.8.0 (Step 0 Rust workspace bootstrap) 2026-05-04 21:00:00 +08:00
segmented-control.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
select-v1.ts Merge feat/rust-ification into v0.8.0 (Step 0 Rust workspace bootstrap) 2026-05-04 21:00:00 +08:00
select.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
setting-row-v1.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
setting-row.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
share-row-v1.ts Merge feat/rust-ification into v0.8.0 (Step 0 Rust workspace bootstrap) 2026-05-04 21:00:00 +08:00
share-row.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
sidebar-nav-v1.ts Merge feat/rust-ification into v0.8.0 (Step 0 Rust workspace bootstrap) 2026-05-04 21:00:00 +08:00
sidebar-nav.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
skeleton-v1.ts Merge feat/rust-ification into v0.8.0 (Step 0 Rust workspace bootstrap) 2026-05-04 21:00:00 +08:00
skeleton.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
social-login-row-v1.ts Merge feat/rust-ification into v0.8.0 (Step 0 Rust workspace bootstrap) 2026-05-04 21:00:00 +08:00
social-login-row.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
spinner-v1.ts Merge feat/rust-ification into v0.8.0 (Step 0 Rust workspace bootstrap) 2026-05-04 21:00:00 +08:00
spinner.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
stat-card-v1.ts Merge feat/rust-ification into v0.8.0 (Step 0 Rust workspace bootstrap) 2026-05-04 21:00:00 +08:00
stat-card.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
stat-grid-v1.ts Merge feat/rust-ification into v0.8.0 (Step 0 Rust workspace bootstrap) 2026-05-04 21:00:00 +08:00
stat-grid.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
status-badge-v1.ts Merge feat/rust-ification into v0.8.0 (Step 0 Rust workspace bootstrap) 2026-05-04 21:00:00 +08:00
status-badge.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
step-card-v1.ts Merge feat/rust-ification into v0.8.0 (Step 0 Rust workspace bootstrap) 2026-05-04 21:00:00 +08:00
step-card.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
stepper-v1.ts Merge feat/rust-ification into v0.8.0 (Step 0 Rust workspace bootstrap) 2026-05-04 21:00:00 +08:00
stepper.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
switch-v1.ts Merge feat/rust-ification into v0.8.0 (Step 0 Rust workspace bootstrap) 2026-05-04 21:00:00 +08:00
switch.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
tabs-v1.ts Merge feat/rust-ification into v0.8.0 (Step 0 Rust workspace bootstrap) 2026-05-04 21:00:00 +08:00
tabs.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
tag-v1.ts Merge feat/rust-ification into v0.8.0 (Step 0 Rust workspace bootstrap) 2026-05-04 21:00:00 +08:00
tag.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
text-button-v1.ts Merge feat/rust-ification into v0.8.0 (Step 0 Rust workspace bootstrap) 2026-05-04 21:00:00 +08:00
text-button.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
textarea-v1.ts Merge feat/rust-ification into v0.8.0 (Step 0 Rust workspace bootstrap) 2026-05-04 21:00:00 +08:00
textarea.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
timeline-v1.ts Merge feat/rust-ification into v0.8.0 (Step 0 Rust workspace bootstrap) 2026-05-04 21:00:00 +08:00
timeline.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
toast-v1.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
toast.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
toolbar-v1.ts Merge feat/rust-ification into v0.8.0 (Step 0 Rust workspace bootstrap) 2026-05-04 21:00:00 +08:00
toolbar.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
tooltip-v1.ts Merge feat/rust-ification into v0.8.0 (Step 0 Rust workspace bootstrap) 2026-05-04 21:00:00 +08:00
tooltip.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
top-nav-bar-v1.ts Merge feat/rust-ification into v0.8.0 (Step 0 Rust workspace bootstrap) 2026-05-04 21:00:00 +08:00
top-nav-bar.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
upload-dropzone-v1.ts Merge feat/rust-ification into v0.8.0 (Step 0 Rust workspace bootstrap) 2026-05-04 21:00:00 +08:00
upload-dropzone.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
user-card-v1.ts Merge feat/rust-ification into v0.8.0 (Step 0 Rust workspace bootstrap) 2026-05-04 21:00:00 +08:00
user-card.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
video-placeholder-v1.ts Merge feat/rust-ification into v0.8.0 (Step 0 Rust workspace bootstrap) 2026-05-04 21:00:00 +08:00
video-placeholder.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00

element-builders

Pure tree-build functions (one per N-tool add_X_v0 MCP tool) that produce PenNode subtrees from typed parameters. Shared between pen-mcp handlers (external MCP clients like Claude Code / Codex / Gemini CLI) and the browser-side client shim in apps/web/src/services/ai/element-tool-shims/.

Why a shared module

The N-tool system has three executable paths that all need to produce identical trees for the same args:

 ┌────────────────────┐       ┌────────────────────┐       ┌────────────────────┐
 │  pen-mcp handler   │       │ apps/web shim      │       │ Nitro server bld   │
 │  (external clients │       │ (browser-side      │       │ (/api/mcp/exec-    │
 │   via stdio/HTTP)  │       │  client shim)      │       │  tool HTTP fallbk) │
 └─────────┬──────────┘       └─────────┬──────────┘       └─────────┬──────────┘
           │                            │                            │
           ▼                            ▼                            ▼
           ┌──────────────────────────────────────────────────────────┐
           │              @zseven-w/pen-core/element-builders         │
           │  (buildHeading, buildCardRow, buildTopNavBar, …, 50×)    │
           └──────────────────────────────────────────────────────────┘

If all three paths import the same buildX here, the tree is drift-free by construction — no registry can silently emit a different shape.

Module layout

  • index.ts — barrel; every new builder must be re-exported here
  • helpers.ts — assignIdsRecursively, buildScrollWrapper, ElementTree
  • cjk-detect.ts — detectCjkScript, cjkFontFamily (Noto Sans SC/JP/KR dispatch)
  • <name>.ts — one file per tool (50 today, as of 2026-04-22), each exporting build<Name> + its params type

What a builder is

A pure function that takes typed params and returns an ElementTree:

import type { ElementTree } from './helpers.js';

export interface MyThingParams {
  label: string;
  icon?: string;
}

export function buildMyThing(params: MyThingParams): ElementTree {
  return {
    type: 'frame',
    name: 'My Thing',
    role: 'my-thing',
    width: 'fill_container',
    height: 'fit_content',
    layout: 'horizontal',
    // …
    children: [
      // …
    ],
  };
}

Rules:

  • Browser-safe: no node:fs, no I/O, no import.meta.env guards, no async. Builders are synchronous value producers.
  • No id stamping: ids are stamped by assignIdsRecursively AFTER construction (callers own that step).
  • No parent wiring: parent_id / pageId / filePath are meta params stripped before the builder is called.
  • Return shape is intentionally Record<string, unknown> (loose) — the downstream insert pipeline validates.

Conventions crystallized from 42 existing builders

  • Layout sizing: frame containers use width: 'fill_container' + height: 'fit_content' as the default. Atoms that carry concrete dimensions (avatars, rings) set numeric sizes.
  • Icons are icon_font nodes: type: 'icon_font' + iconFontFamily: 'lucide' + iconFontName: '<slug>'. Never use path for icons in builder output.
  • Text nodes: never set explicit height. Let text grow; use fontSize/lineHeight/letterSpacing to control typography.
  • Roles: every top-level node sets role: '<kebab-name>'. Sub-nodes may set scoped roles (list-row-text, card, stat-cell). The role string drives downstream post-processing (role-resolver, contrast pass, layout inference).
  • CJK dispatch: only buildHeading dispatches fontFamily per script (Noto Sans SC/JP/KR). Body text always fontFamily: 'Inter'. Other builders don't dispatch CJK — children inherit the renderer default.
  • Text-button / form-input: width: 'fill_container', height 48, cornerRadius 8, padding [12, 16] or [12, 24] — matches the Pencil-demo contract.
  • Icon-only buttons: 44×44 (Apple HIG / Material min-hit-target), flex-centered icon_font. Never layout: 'none' with manual x/y.
  • Ring / circle with content: use frame with cornerRadius: width/2. Never two stacked ellipses — that anti-pattern trips rewriteLlmAntiPatterns.
  • Divider: rectangle with height: 1 and fill_container. Never a one-sided stroke on a frame (renderer only supports uniform or [T,R,B,L] stroke thicknesses).

Adding a new builder

  1. Create packages/pen-core/src/element-builders/<name>.ts exporting build<Name>(params) + <Name>Params
  2. Re-export from packages/pen-core/src/element-builders/index.ts
  3. Re-export from packages/pen-core/src/index.ts (main barrel) — only matters if apps/web or pen-mcp import it via @zseven-w/pen-core directly
  4. Wire pen-mcp handler: packages/pen-mcp/src/tools/add-<name>-v0.ts imports build<Name> and delegates
  5. Register the pen-mcp handler in packages/pen-mcp/src/routes/element-tool-defs.ts (add import + switch branch + tool schema in -base.ts / -ext.ts)
  6. Wire apps/web shim: add to ELEMENT_SHIMS in apps/web/src/services/ai/element-tool-shims/index.ts
  7. Wire server builder: add to SERVER_BUILDERS in apps/web/server/api/mcp/exec-tool.post.ts
  8. Add to elements.md: packages/pen-ai-skills/skills/phases/generation/elements.md — add the tool to the PREFER list + examples section
  9. Tests (all of these should pass automatically if the builder follows conventions):
    • Layout smoke in packages/pen-core/src/__tests__/element-builders-layout.test.ts
    • Idempotency in element-builders-post-process-idempotent.test.ts
    • Role coverage in apps/web/src/services/ai/__tests__/role-resolver-builder-coverage.test.ts
    • Parity in shim-server-parity.test.ts + element-tool-registry-parity.test.ts
    • If the builder emits an icon_font, check builder-icon-coverage.test.ts covers it

Parity tests fail-fast on any skipped step — the test names tell you which registry or handler is missing.

Drift guards in the test suite

Tests to run when touching this module (bun run test at repo root runs all):

Test What it catches
element-builders-layout.test.ts computeLayoutPositions throws on your new builder
element-builders-composition.test.ts Builder doesn't compose with others in a screen frame
element-builders-post-process-idempotent.test.ts A post-pass mutates output twice
element-builders-edge-cases.test.ts Extreme inputs (empty, huge, boundary) crash
element-builders-cjk-dispatch.test.ts CJK font dispatch regresses
element-builders-normalize-preservation.test.ts normalizePenDocument drops a semantic field
element-builders-performance.test.ts New builder adds >5ms avg or >50ms cold
role-resolver-builder-coverage.test.ts (apps/web) Role string doesn't match role-definitions set or typo
detectors-builder-clean.test.ts (apps/web) Builder trips a pre-validation detector
anti-patterns-builder-clean.test.ts (apps/web) Builder produces an anti-pattern (stacked ellipses, open path + fill)
shim-server-parity.test.ts (apps/web) Shim and direct-buildX output diverge
builder-icon-coverage.test.ts (apps/web) Builder emits an icon name that doesn't resolve
element-tool-registry-parity.test.ts (pen-mcp) Registry + handler file + dispatcher switch drift
  • packages/pen-core/CLAUDE.md — broader pen-core module map
  • packages/pen-ai-skills/skills/phases/generation/elements.md — prompt-level spec of each tool, loaded by the AI
  • apps/web/src/services/ai/element-tool-shims/index.ts — client shim registry
  • apps/web/src/services/ai/element-tools-dispatcher.ts — routes parsed <op_tool> → shim → insert
  • apps/web/server/api/mcp/exec-tool.post.ts — Nitro HTTP fallback (same builder catalog, for cases the client shim doesn't support)
  • Spec: openpencil-docs/superpowers/specs/2026-04-19-element-tools-v0.md