refactor(mcp): split element-tool-defs into base + ext + aggregator
element-tool-defs.ts crossed the repo's 800-line ceiling (814 → 827 after comment expansion) when the 8-tool atom batch landed in 776cdbd. Move the 19 base tool schema definitions out to element-tool-defs-base.ts, leaving the main file as a thin aggregator that just imports BASE + EXT, builds the combined ELEMENT_TOOL_DEFINITIONS array, and wires the handleElementToolCall switch. File sizes after: element-tool-defs.ts 153 (aggregator + dispatcher) element-tool-defs-base.ts 683 (19 schemas) element-tool-defs-ext.ts 528 (20 schemas) Zero behavior change — the ELEMENT_TOOL_DEFINITIONS export contents are byte-identical (same 39 entries in the same order). All 288 pen-mcp tests + 1834-test full suite pass. Prompted by Codex stop-hook review flagging the 800-line violation.
This commit is contained in:
parent
51878c3894
commit
6ef172f0b4
683
packages/pen-mcp/src/routes/element-tool-defs-base.ts
Normal file
683
packages/pen-mcp/src/routes/element-tool-defs-base.ts
Normal file
|
|
@ -0,0 +1,683 @@
|
|||
// Base element-tool JSON schema definitions (19 tools shipped 2026-04-19).
|
||||
// Split out from element-tool-defs.ts 2026-04-20 to keep that file under
|
||||
// the 800-line ceiling as the dispatcher and ext-registry grew. Paired
|
||||
// with element-tool-defs-ext.ts (20 more tools); both are aggregated by
|
||||
// element-tool-defs.ts into the single ELEMENT_TOOL_DEFINITIONS export.
|
||||
|
||||
export const ELEMENT_TOOL_DEFINITIONS_BASE = [
|
||||
{
|
||||
name: 'add_card_row_v0',
|
||||
description:
|
||||
'Create a horizontal scroll row of CARDS (title + subtitle + optional icon). Each card is ' +
|
||||
'140×160, cornerRadius=20. Forces the overflow-safe wrapper+clipContent+fit_content structure ' +
|
||||
'taught in packages/pen-ai-skills/skills/phases/generation/overflow.md §HORIZONTAL SCROLL ROWS. ' +
|
||||
'Use when spec mentions "workout cards", "feature cards", "swipeable content cards", "pills", ' +
|
||||
'or any row where each item has a prominent title plus descriptive subtext. schemaVersion 1.0',
|
||||
inputSchema: {
|
||||
type: 'object' as const,
|
||||
properties: {
|
||||
schemaVersion: {
|
||||
type: 'string',
|
||||
enum: ['1.0'],
|
||||
description:
|
||||
'Schema version this tool was authored against (v0-MUST §4.2). Clients MAY omit. Breaking schema changes ship as a new tool with _v1 suffix; old tools are kept one stage before being removed from ListTools.',
|
||||
},
|
||||
filePath: { type: 'string', description: 'Path to .op file, or omit for live canvas' },
|
||||
items: {
|
||||
type: 'array',
|
||||
description: 'Card items. Each needs title; subtitle and icon are optional.',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
title: { type: 'string' },
|
||||
subtitle: { type: 'string' },
|
||||
icon: { type: 'string', description: 'lucide icon name' },
|
||||
},
|
||||
required: ['title'],
|
||||
},
|
||||
},
|
||||
card_width: { type: 'number', description: 'Fixed width per card (default 140)' },
|
||||
gap: { type: 'number', description: 'Inner-row gap in px (default 12)' },
|
||||
parent_id: {
|
||||
type: 'string',
|
||||
description:
|
||||
'Target parent node id (must exist in the document). Omit for root-level insertion.',
|
||||
},
|
||||
pageId: { type: 'string', description: 'Target page ID (defaults to first page)' },
|
||||
},
|
||||
required: ['items'],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'add_metric_row_v0',
|
||||
description:
|
||||
'Create a horizontal scroll row of METRIC TILES (small label + big value + optional icon). ' +
|
||||
'Each tile is 120×100, cornerRadius=16, value rendered at 28/700 heading. Forces the ' +
|
||||
'overflow-safe wrapper+clipContent+fit_content structure. Use when spec mentions "dashboard ' +
|
||||
'stats", "KPI cards", "metric tiles", or shows Steps/Kcal/Sleep/Revenue-style rows. schemaVersion 1.0',
|
||||
inputSchema: {
|
||||
type: 'object' as const,
|
||||
properties: {
|
||||
schemaVersion: {
|
||||
type: 'string',
|
||||
enum: ['1.0'],
|
||||
description:
|
||||
'Schema version this tool was authored against (v0-MUST §4.2). Clients MAY omit. Breaking schema changes ship as a new tool with _v1 suffix; old tools are kept one stage before being removed from ListTools.',
|
||||
},
|
||||
filePath: { type: 'string', description: 'Path to .op file, or omit for live canvas' },
|
||||
items: {
|
||||
type: 'array',
|
||||
description: 'Metric items. Each needs label + value; icon is optional.',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
label: { type: 'string', description: 'Small descriptive label (e.g. "Steps")' },
|
||||
value: { type: 'string', description: 'Big number / formatted value (e.g. "8,432")' },
|
||||
icon: { type: 'string', description: 'lucide icon name' },
|
||||
},
|
||||
required: ['label', 'value'],
|
||||
},
|
||||
},
|
||||
tile_width: { type: 'number', description: 'Fixed width per tile (default 120)' },
|
||||
gap: { type: 'number', description: 'Inner-row gap in px (default 12)' },
|
||||
parent_id: {
|
||||
type: 'string',
|
||||
description:
|
||||
'Target parent node id (must exist in the document). Omit for root-level insertion.',
|
||||
},
|
||||
pageId: { type: 'string', description: 'Target page ID (defaults to first page)' },
|
||||
},
|
||||
required: ['items'],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'add_nav_chip_row_v0',
|
||||
description:
|
||||
'Create a horizontal scroll row of NAV CHIPS (icon + small label, each with optional active ' +
|
||||
'state). Each chip is 72×fit_content, cornerRadius=12. Forces the overflow-safe ' +
|
||||
'wrapper+clipContent+fit_content structure. Use when spec mentions "category filter chips", ' +
|
||||
'"quick access shortcuts", "horizontal tab chips", "swipeable nav items". schemaVersion 1.0',
|
||||
inputSchema: {
|
||||
type: 'object' as const,
|
||||
properties: {
|
||||
schemaVersion: {
|
||||
type: 'string',
|
||||
enum: ['1.0'],
|
||||
description:
|
||||
'Schema version this tool was authored against (v0-MUST §4.2). Clients MAY omit. Breaking schema changes ship as a new tool with _v1 suffix; old tools are kept one stage before being removed from ListTools.',
|
||||
},
|
||||
filePath: { type: 'string', description: 'Path to .op file, or omit for live canvas' },
|
||||
items: {
|
||||
type: 'array',
|
||||
description:
|
||||
'Chip items. Each needs label; icon and active are optional. Label-only chips are supported (text-only filter tags).',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
label: { type: 'string' },
|
||||
icon: { type: 'string', description: 'lucide icon name (optional)' },
|
||||
active: { type: 'boolean' },
|
||||
},
|
||||
required: ['label'],
|
||||
},
|
||||
},
|
||||
chip_width: { type: 'number', description: 'Fixed width per chip (default 72)' },
|
||||
gap: { type: 'number', description: 'Inner-row gap in px (default 12)' },
|
||||
parent_id: {
|
||||
type: 'string',
|
||||
description:
|
||||
'Target parent node id (must exist in the document). Omit for root-level insertion.',
|
||||
},
|
||||
pageId: { type: 'string', description: 'Target page ID (defaults to first page)' },
|
||||
},
|
||||
required: ['items'],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'add_bottom_nav_v0',
|
||||
description:
|
||||
'Create a bottom tab bar (inline, not fixed-position). Forces the pattern taught in ' +
|
||||
'packages/pen-ai-skills/skills/phases/generation/layout.md §NO FIXED-POSITION LAYOUT: ' +
|
||||
'bottom-tab-bar is an inline child of the page (no empty spacer sibling needed, no ' +
|
||||
'position:fixed since the engine does not support it). Always prefer this over batch_design ' +
|
||||
'when the spec mentions "bottom nav", "tab bar", "tabbar", "底部导航", "tab bar with icons". ' +
|
||||
'schemaVersion 1.0',
|
||||
inputSchema: {
|
||||
type: 'object' as const,
|
||||
properties: {
|
||||
schemaVersion: {
|
||||
type: 'string',
|
||||
enum: ['1.0'],
|
||||
description:
|
||||
'Schema version this tool was authored against (v0-MUST §4.2). Clients MAY omit. Breaking schema changes ship as a new tool with _v1 suffix; old tools are kept one stage before being removed from ListTools.',
|
||||
},
|
||||
filePath: { type: 'string', description: 'Path to .op file, or omit for live canvas' },
|
||||
items: {
|
||||
type: 'array',
|
||||
description:
|
||||
'Tab items. Each needs title + icon (lucide name). active marks current tab.',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
title: { type: 'string' },
|
||||
icon: { type: 'string', description: 'lucide icon name (e.g. "home")' },
|
||||
active: { type: 'boolean' },
|
||||
},
|
||||
required: ['title', 'icon'],
|
||||
},
|
||||
},
|
||||
height: { type: 'number', description: 'Bar height in px (default 62)' },
|
||||
parent_id: {
|
||||
type: 'string',
|
||||
description:
|
||||
'Target parent node id (must exist in the document; validated before insertion). Omit for root-level insertion.',
|
||||
},
|
||||
pageId: { type: 'string', description: 'Target page ID (defaults to first page)' },
|
||||
},
|
||||
required: ['items'],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'add_activity_ring_v0',
|
||||
description:
|
||||
'Create an Apple-style activity ring (progress ring) with centered text. Forces the ' +
|
||||
'frame+cornerRadius+stroke+centered-text pattern taught in ' +
|
||||
'packages/pen-ai-skills/skills/phases/generation/layout.md §RING / CIRCLE WITH CENTER CONTENT. ' +
|
||||
'NEVER emits the documented anti-patterns (ellipse+sibling text, layout=none+absolute). ' +
|
||||
'Ships colorless with fixed typography (Style Guide orthogonal) — override ring color / text ' +
|
||||
'size / weight via a follow-up batch_design U-op. Use when the spec mentions "activity ring", ' +
|
||||
'"progress ring", "circular progress", "ring with number", "Apple health ring". schemaVersion 1.0',
|
||||
inputSchema: {
|
||||
type: 'object' as const,
|
||||
properties: {
|
||||
schemaVersion: {
|
||||
type: 'string',
|
||||
enum: ['1.0'],
|
||||
description:
|
||||
'Schema version this tool was authored against (v0-MUST §4.2). Clients MAY omit. Breaking schema changes ship as a new tool with _v1 suffix; old tools are kept one stage before being removed from ListTools.',
|
||||
},
|
||||
filePath: { type: 'string', description: 'Path to .op file, or omit for live canvas' },
|
||||
size: { type: 'number', description: 'Ring outer diameter in px (default 80)' },
|
||||
thickness: { type: 'number', description: 'Stroke thickness in px (default 8)' },
|
||||
center_text: { type: 'string', description: 'Text displayed in the ring center' },
|
||||
parent_id: {
|
||||
type: 'string',
|
||||
description:
|
||||
'Target parent node id (must exist in the document). Omit for root-level insertion.',
|
||||
},
|
||||
pageId: { type: 'string', description: 'Target page ID (defaults to first page)' },
|
||||
},
|
||||
required: ['center_text'],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'add_stat_grid_v0',
|
||||
description:
|
||||
'Create a NON-scrolling stat grid (2-5 items share the row via fill_container). ' +
|
||||
'Different from add_metric_row_v0: this emits an inline grid that auto-distributes ' +
|
||||
'available width, solving the documented activity-rings overflow bug in ' +
|
||||
'packages/pen-ai-skills/skills/phases/generation/layout.md (three fixed 100px items in ' +
|
||||
'a 279px inner card silently clip the third item). Each cell is width=fill_container; ' +
|
||||
'renderer does the division. Use when spec mentions "stats row", "3 metrics side by side", ' +
|
||||
'"summary bar", or an inline (non-scrollable) row of KPIs. schemaVersion 1.0',
|
||||
inputSchema: {
|
||||
type: 'object' as const,
|
||||
properties: {
|
||||
schemaVersion: {
|
||||
type: 'string',
|
||||
enum: ['1.0'],
|
||||
description: 'Schema version (v0-MUST §4.2). Clients MAY omit.',
|
||||
},
|
||||
filePath: { type: 'string', description: 'Path to .op file, or omit for live canvas' },
|
||||
items: {
|
||||
type: 'array',
|
||||
description:
|
||||
'Stat cells. Each needs value + label; icon is optional. 2-5 items work best.',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
value: { type: 'string', description: 'Big numeric value (e.g. "8,432")' },
|
||||
label: { type: 'string', description: 'Small descriptive label (e.g. "Steps")' },
|
||||
icon: { type: 'string', description: 'lucide icon name (optional)' },
|
||||
},
|
||||
required: ['value', 'label'],
|
||||
},
|
||||
},
|
||||
gap: { type: 'number', description: 'Gap between cells in px (default 16)' },
|
||||
parent_id: {
|
||||
type: 'string',
|
||||
description: 'Target parent node id (must exist). Omit for root-level insertion.',
|
||||
},
|
||||
pageId: { type: 'string', description: 'Target page ID (defaults to first page)' },
|
||||
},
|
||||
required: ['items'],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'add_section_header_v0',
|
||||
description:
|
||||
'Section header with big title on left + optional trailing action (e.g. "See all", ' +
|
||||
'"View more"). Forces horizontal space_between alignItems=center layout so the action ' +
|
||||
'always sits flush-right. Use when spec shows "Section Title" with a "See all" or "→" link, ' +
|
||||
'or any heading + secondary action pair. schemaVersion 1.0',
|
||||
inputSchema: {
|
||||
type: 'object' as const,
|
||||
properties: {
|
||||
schemaVersion: {
|
||||
type: 'string',
|
||||
enum: ['1.0'],
|
||||
description: 'Schema version (v0-MUST §4.2). Clients MAY omit.',
|
||||
},
|
||||
filePath: { type: 'string', description: 'Path to .op file, or omit for live canvas' },
|
||||
title: { type: 'string', description: 'Section title (big heading)' },
|
||||
action: {
|
||||
type: 'object',
|
||||
description: 'Optional trailing action (e.g. { label: "See all", icon: "arrow-right" })',
|
||||
properties: {
|
||||
label: { type: 'string' },
|
||||
icon: { type: 'string', description: 'lucide icon name (optional)' },
|
||||
},
|
||||
required: ['label'],
|
||||
},
|
||||
parent_id: {
|
||||
type: 'string',
|
||||
description: 'Target parent node id (must exist). Omit for root-level insertion.',
|
||||
},
|
||||
pageId: { type: 'string', description: 'Target page ID (defaults to first page)' },
|
||||
},
|
||||
required: ['title'],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'add_top_nav_bar_v0',
|
||||
description:
|
||||
'Mobile top navigation bar: optional leading icon (back/menu) + centered title + ' +
|
||||
'optional trailing icon (search/more). Dual of add_bottom_nav_v0. Title always centered; ' +
|
||||
'empty slots become 44×44 spacers so the title visually stays centered. Use when spec ' +
|
||||
'mentions "top bar", "app bar", "header with back button", "页面标题栏". schemaVersion 1.0',
|
||||
inputSchema: {
|
||||
type: 'object' as const,
|
||||
properties: {
|
||||
schemaVersion: {
|
||||
type: 'string',
|
||||
enum: ['1.0'],
|
||||
description: 'Schema version (v0-MUST §4.2). Clients MAY omit.',
|
||||
},
|
||||
filePath: { type: 'string', description: 'Path to .op file, or omit for live canvas' },
|
||||
title: { type: 'string', description: 'Centered title text' },
|
||||
leading_icon: {
|
||||
type: 'string',
|
||||
description: 'lucide icon name for the left slot (e.g. "chevron-left", "menu")',
|
||||
},
|
||||
trailing_icon: {
|
||||
type: 'string',
|
||||
description: 'lucide icon name for the right slot (e.g. "search", "more-vertical")',
|
||||
},
|
||||
height: { type: 'number', description: 'Bar height in px (default 56)' },
|
||||
parent_id: {
|
||||
type: 'string',
|
||||
description: 'Target parent node id (must exist). Omit for root-level insertion.',
|
||||
},
|
||||
pageId: { type: 'string', description: 'Target page ID (defaults to first page)' },
|
||||
},
|
||||
required: ['title'],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'add_icon_button_v0',
|
||||
description:
|
||||
'Icon-only button. Forces 44×44 minimum hit target (Apple HIG + Material) with ' +
|
||||
'flex-centered icon — NEVER emits the layout=none + absolute-positioned icon anti-pattern ' +
|
||||
'documented in pen-ai-skills memory (layout=none + nested children renders unreliably). ' +
|
||||
'Use when the spec shows "icon-only" buttons, search/close/menu buttons, or iconic actions ' +
|
||||
'in toolbars. schemaVersion 1.0',
|
||||
inputSchema: {
|
||||
type: 'object' as const,
|
||||
properties: {
|
||||
schemaVersion: {
|
||||
type: 'string',
|
||||
enum: ['1.0'],
|
||||
description: 'Schema version (v0-MUST §4.2). Clients MAY omit.',
|
||||
},
|
||||
filePath: { type: 'string', description: 'Path to .op file, or omit for live canvas' },
|
||||
icon: { type: 'string', description: 'lucide icon name' },
|
||||
size: { type: 'number', description: 'Button size (hit-target) in px (default 44)' },
|
||||
icon_size: { type: 'number', description: 'Icon glyph size in px (default 24)' },
|
||||
parent_id: {
|
||||
type: 'string',
|
||||
description: 'Target parent node id (must exist). Omit for root-level insertion.',
|
||||
},
|
||||
pageId: { type: 'string', description: 'Target page ID (defaults to first page)' },
|
||||
},
|
||||
required: ['icon'],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'add_divider_v0',
|
||||
description:
|
||||
'Hairline divider (rectangle, not stroke). Forces the pattern documented in pen-ai-skills memory: ' +
|
||||
'rectangle with height=1 + width=fill_container (horizontal) or width=1 + height=fill_container ' +
|
||||
'(vertical). Use between list rows, between form sections, between a card content and its footer. ' +
|
||||
'Ships colorless — override fill via a follow-up batch_design U-op. schemaVersion 1.0',
|
||||
inputSchema: {
|
||||
type: 'object' as const,
|
||||
properties: {
|
||||
schemaVersion: {
|
||||
type: 'string',
|
||||
enum: ['1.0'],
|
||||
description: 'Schema version (v0-MUST §4.2). Clients MAY omit.',
|
||||
},
|
||||
filePath: { type: 'string', description: 'Path to .op file, or omit for live canvas' },
|
||||
orientation: {
|
||||
type: 'string',
|
||||
enum: ['horizontal', 'vertical'],
|
||||
description: 'Default horizontal',
|
||||
},
|
||||
thickness: { type: 'number', description: 'Divider thickness in px (default 1)' },
|
||||
parent_id: {
|
||||
type: 'string',
|
||||
description: 'Target parent node id (must exist). Omit for root-level insertion.',
|
||||
},
|
||||
pageId: { type: 'string', description: 'Target page ID (defaults to first page)' },
|
||||
},
|
||||
required: [],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'add_badge_v0',
|
||||
description:
|
||||
'Short inline badge / pill / tag ("NEW", "BETA", "42", "Sale"). Forces the standard pill ' +
|
||||
'layout (cornerRadius=999, padding=[4,10], font 11/600). Documented constraint (overflow.md): ' +
|
||||
'CJK ≤8 chars, Latin ≤16 chars — longer labels are NOT a badge, use batch_design. schemaVersion 1.0',
|
||||
inputSchema: {
|
||||
type: 'object' as const,
|
||||
properties: {
|
||||
schemaVersion: {
|
||||
type: 'string',
|
||||
enum: ['1.0'],
|
||||
description: 'Schema version (v0-MUST §4.2). Clients MAY omit.',
|
||||
},
|
||||
filePath: { type: 'string', description: 'Path to .op file, or omit for live canvas' },
|
||||
label: {
|
||||
type: 'string',
|
||||
description: 'Short label (Latin ≤16 chars / CJK ≤8 chars)',
|
||||
},
|
||||
parent_id: {
|
||||
type: 'string',
|
||||
description: 'Target parent node id (must exist). Omit for root-level insertion.',
|
||||
},
|
||||
pageId: { type: 'string', description: 'Target page ID (defaults to first page)' },
|
||||
},
|
||||
required: ['label'],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'add_avatar_v0',
|
||||
description:
|
||||
'Circular avatar with optional centered initial. Forces the same frame+cornerRadius=size/2+flex-' +
|
||||
'centering pattern as add_activity_ring_v0 — NEVER the ellipse+sibling text anti-pattern ' +
|
||||
'documented in layout.md. Default size 40 (inline). For larger profile avatars pass size: 56 ' +
|
||||
'or 96. Without `initial`, emits an empty circle ready for an image child via batch_design. ' +
|
||||
'schemaVersion 1.0',
|
||||
inputSchema: {
|
||||
type: 'object' as const,
|
||||
properties: {
|
||||
schemaVersion: {
|
||||
type: 'string',
|
||||
enum: ['1.0'],
|
||||
description: 'Schema version (v0-MUST §4.2). Clients MAY omit.',
|
||||
},
|
||||
filePath: { type: 'string', description: 'Path to .op file, or omit for live canvas' },
|
||||
initial: {
|
||||
type: 'string',
|
||||
description: 'Single character / short initial (e.g. "A", "JD"). Omit for empty circle.',
|
||||
},
|
||||
size: { type: 'number', description: 'Diameter in px (default 40)' },
|
||||
parent_id: {
|
||||
type: 'string',
|
||||
description: 'Target parent node id (must exist). Omit for root-level insertion.',
|
||||
},
|
||||
pageId: { type: 'string', description: 'Target page ID (defaults to first page)' },
|
||||
},
|
||||
required: [],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'add_text_button_v0',
|
||||
description:
|
||||
'Padding-based text button matching the Pencil demo pattern (documented in memory): ' +
|
||||
'frame(padding=[12,20], horizontal, alignItems=center, justifyContent=center, cornerRadius=8) ' +
|
||||
'+ optional leading icon + label text. Height auto-derives from padding + text; NO explicit ' +
|
||||
'fixed height. Use for primary / secondary buttons with a short text label. schemaVersion 1.0',
|
||||
inputSchema: {
|
||||
type: 'object' as const,
|
||||
properties: {
|
||||
schemaVersion: {
|
||||
type: 'string',
|
||||
enum: ['1.0'],
|
||||
description: 'Schema version (v0-MUST §4.2). Clients MAY omit.',
|
||||
},
|
||||
filePath: { type: 'string', description: 'Path to .op file, or omit for live canvas' },
|
||||
label: { type: 'string', description: 'Button text (1-3 words recommended)' },
|
||||
leading_icon: {
|
||||
type: 'string',
|
||||
description: 'Optional leading lucide icon name (e.g. "plus", "arrow-right")',
|
||||
},
|
||||
parent_id: {
|
||||
type: 'string',
|
||||
description: 'Target parent node id (must exist). Omit for root-level insertion.',
|
||||
},
|
||||
pageId: { type: 'string', description: 'Target page ID (defaults to first page)' },
|
||||
},
|
||||
required: ['label'],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'add_heading_v0',
|
||||
description:
|
||||
'Typographic heading with Pencil-demo-derived presets + AUTO CJK script handling. ' +
|
||||
'Latin (default) per-level: display=48/700/1.0/letterSpacing=-0.5, h1=32/700/1.1, ' +
|
||||
'h2=24/600/1.2 (DEFAULT), h3=20/600/1.25. CJK content switches to: display=48/700/1.3 + ' +
|
||||
'NO negative letterSpacing, h1=32/700/1.3, h2=24/600/1.35, h3=20/600/1.4 + script-specific ' +
|
||||
'fontFamily (Chinese → Noto Sans SC / Japanese → Noto Sans JP / Korean → Noto Sans KR — ' +
|
||||
'text-rules.md: NEVER use SC for JP or KR). Encodes the preset so non-Claude models cannot ' +
|
||||
'forget lineHeight (default 1.5 stacks multi-word headings tight) or pick the wrong font for ' +
|
||||
'JP/KR content. Emits a single text node with role=heading. schemaVersion 1.0',
|
||||
inputSchema: {
|
||||
type: 'object' as const,
|
||||
properties: {
|
||||
schemaVersion: {
|
||||
type: 'string',
|
||||
enum: ['1.0'],
|
||||
description: 'Schema version (v0-MUST §4.2). Clients MAY omit.',
|
||||
},
|
||||
filePath: { type: 'string', description: 'Path to .op file, or omit for live canvas' },
|
||||
content: { type: 'string', description: 'Heading text' },
|
||||
level: {
|
||||
type: 'string',
|
||||
enum: ['display', 'h1', 'h2', 'h3'],
|
||||
description: 'Typography level. Default: h2.',
|
||||
},
|
||||
parent_id: {
|
||||
type: 'string',
|
||||
description: 'Target parent node id (must exist). Omit for root-level insertion.',
|
||||
},
|
||||
pageId: { type: 'string', description: 'Target page ID (defaults to first page)' },
|
||||
},
|
||||
required: ['content'],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'add_body_text_v0',
|
||||
description:
|
||||
'Body / description text. fontFamily ALWAYS Inter (per text-rules.md rule "body=Inter" — ' +
|
||||
'Inter uses system CJK fallback, so script-specific Noto faces apply ONLY to headings, ' +
|
||||
'not body). Script is auto-detected just to set the right lineHeight + letterSpacing: ' +
|
||||
'CJK (Chinese / Japanese / Korean) body gets lineHeight=1.6 + letterSpacing=0 (NEVER ' +
|
||||
'negative — causes CJK character overlap); Latin body gets lineHeight=1.5 + no letterSpacing ' +
|
||||
'override. Always sets width=fill_container + textGrowth=fixed-width so long text wraps ' +
|
||||
'(overflow.md: fixed-width required for >15-char text to wrap). Intended for VERTICAL-layout ' +
|
||||
'parents only. schemaVersion 1.0',
|
||||
inputSchema: {
|
||||
type: 'object' as const,
|
||||
properties: {
|
||||
schemaVersion: {
|
||||
type: 'string',
|
||||
enum: ['1.0'],
|
||||
description: 'Schema version (v0-MUST §4.2). Clients MAY omit.',
|
||||
},
|
||||
filePath: { type: 'string', description: 'Path to .op file, or omit for live canvas' },
|
||||
content: { type: 'string', description: 'Body text content (single paragraph)' },
|
||||
parent_id: {
|
||||
type: 'string',
|
||||
description:
|
||||
'Target parent node id (must exist; MUST be a vertical-layout frame). Omit for root insertion.',
|
||||
},
|
||||
pageId: { type: 'string', description: 'Target page ID (defaults to first page)' },
|
||||
},
|
||||
required: ['content'],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'add_icon_label_v0',
|
||||
description:
|
||||
'Atomic icon + label pair (horizontal, alignItems=center, gap=8). Common building block ' +
|
||||
'for menu items, breadcrumbs, status indicators, any "icon with text" inline composition. ' +
|
||||
'Narrow schema: icons always lead; size/weight fixed (icon 16×16, text 14/500). ' +
|
||||
'schemaVersion 1.0',
|
||||
inputSchema: {
|
||||
type: 'object' as const,
|
||||
properties: {
|
||||
schemaVersion: {
|
||||
type: 'string',
|
||||
enum: ['1.0'],
|
||||
description: 'Schema version (v0-MUST §4.2). Clients MAY omit.',
|
||||
},
|
||||
filePath: { type: 'string', description: 'Path to .op file, or omit for live canvas' },
|
||||
icon: { type: 'string', description: 'lucide icon name' },
|
||||
label: { type: 'string', description: 'Text label' },
|
||||
gap: { type: 'number', description: 'Gap between icon and label (default 8)' },
|
||||
parent_id: {
|
||||
type: 'string',
|
||||
description: 'Target parent node id (must exist). Omit for root-level insertion.',
|
||||
},
|
||||
pageId: { type: 'string', description: 'Target page ID (defaults to first page)' },
|
||||
},
|
||||
required: ['icon', 'label'],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'add_search_bar_v0',
|
||||
description:
|
||||
'Rounded search bar (height=44, cornerRadius=22, width=fill_container) matching the ' +
|
||||
'search-bar role spec in ROLE_GUIDE. Leading icon default "search" — override for ' +
|
||||
'custom affordance (e.g. "filter", "map-pin"). Placeholder text default "Search...". ' +
|
||||
'Use when spec shows a standalone search input, header search, or list filter bar. ' +
|
||||
'schemaVersion 1.0',
|
||||
inputSchema: {
|
||||
type: 'object' as const,
|
||||
properties: {
|
||||
schemaVersion: {
|
||||
type: 'string',
|
||||
enum: ['1.0'],
|
||||
description: 'Schema version (v0-MUST §4.2). Clients MAY omit.',
|
||||
},
|
||||
filePath: { type: 'string', description: 'Path to .op file, or omit for live canvas' },
|
||||
placeholder: {
|
||||
type: 'string',
|
||||
description: 'Placeholder text (default "Search...")',
|
||||
},
|
||||
leading_icon: {
|
||||
type: 'string',
|
||||
description: 'lucide icon name (default "search")',
|
||||
},
|
||||
parent_id: {
|
||||
type: 'string',
|
||||
description: 'Target parent node id (must exist). Omit for root-level insertion.',
|
||||
},
|
||||
pageId: { type: 'string', description: 'Target page ID (defaults to first page)' },
|
||||
},
|
||||
required: [],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'add_form_field_v0',
|
||||
description:
|
||||
'Label + input vertical pair. Enforces FORMS rule from DESIGN_GUIDELINES ("ALL inputs ' +
|
||||
'MUST use width=fill_container, vertical layout, gap=16-20") and ROLE_GUIDE input specs ' +
|
||||
'(height=48, padding=[12,16]). Affordance icons: leading_icon for email/search leads, ' +
|
||||
'trailing_icon for password-toggle. required=true appends " *" to the label. Intended ' +
|
||||
'for use inside a vertical form container with its siblings at gap=16-20. schemaVersion 1.0',
|
||||
inputSchema: {
|
||||
type: 'object' as const,
|
||||
properties: {
|
||||
schemaVersion: {
|
||||
type: 'string',
|
||||
enum: ['1.0'],
|
||||
description: 'Schema version (v0-MUST §4.2). Clients MAY omit.',
|
||||
},
|
||||
filePath: { type: 'string', description: 'Path to .op file, or omit for live canvas' },
|
||||
label: { type: 'string', description: 'Field label text' },
|
||||
placeholder: { type: 'string', description: 'Optional input placeholder' },
|
||||
leading_icon: {
|
||||
type: 'string',
|
||||
description: 'Optional leading icon (e.g. "mail" for email, "search" for search)',
|
||||
},
|
||||
trailing_icon: {
|
||||
type: 'string',
|
||||
description: 'Optional trailing icon (e.g. "eye" for password toggle)',
|
||||
},
|
||||
required: {
|
||||
type: 'boolean',
|
||||
description: 'When true, appends " *" to the label text',
|
||||
},
|
||||
parent_id: {
|
||||
type: 'string',
|
||||
description: 'Target parent node id (must exist). Omit for root-level insertion.',
|
||||
},
|
||||
pageId: { type: 'string', description: 'Target page ID (defaults to first page)' },
|
||||
},
|
||||
required: ['label'],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'add_list_row_v0',
|
||||
description:
|
||||
'iOS / Material-style list row: optional leading icon + center text stack (title + ' +
|
||||
'optional subtitle) + optional trailing icon (typically chevron-right). The middle text ' +
|
||||
'stack is wrapped in a VERTICAL container with width=fill_container — long titles wrap ' +
|
||||
'vertically and the row grows height-wise WITHOUT pushing the trailing icon out of frame ' +
|
||||
'(same pattern as add_section_header_v0; per overflow.md: text with fill_container + ' +
|
||||
'fixed-width only works inside vertical-layout parents). schemaVersion 1.0',
|
||||
inputSchema: {
|
||||
type: 'object' as const,
|
||||
properties: {
|
||||
schemaVersion: {
|
||||
type: 'string',
|
||||
enum: ['1.0'],
|
||||
description: 'Schema version (v0-MUST §4.2). Clients MAY omit.',
|
||||
},
|
||||
filePath: { type: 'string', description: 'Path to .op file, or omit for live canvas' },
|
||||
title: { type: 'string', description: 'Main row title (bold, 15/500)' },
|
||||
subtitle: {
|
||||
type: 'string',
|
||||
description: 'Optional secondary text (smaller, 13/400) shown below title',
|
||||
},
|
||||
leading_icon: {
|
||||
type: 'string',
|
||||
description: 'Optional 24×24 lucide icon before the text stack',
|
||||
},
|
||||
trailing_icon: {
|
||||
type: 'string',
|
||||
description: 'Optional 16×16 lucide icon after the text stack (e.g. "chevron-right")',
|
||||
},
|
||||
parent_id: {
|
||||
type: 'string',
|
||||
description: 'Target parent node id (must exist). Omit for root-level insertion.',
|
||||
},
|
||||
pageId: { type: 'string', description: 'Target page ID (defaults to first page)' },
|
||||
},
|
||||
required: ['title'],
|
||||
},
|
||||
},
|
||||
];
|
||||
|
|
@ -1,7 +1,19 @@
|
|||
// Element-tool JSON schema definitions + names + dispatcher.
|
||||
// Split out from design-routes.ts (2026-04-19) so that file stays under
|
||||
// the repo's 800-line limit as the element-tool family continues to grow.
|
||||
// Base (19) + ext (20) = 39 element tools → aggregated here.
|
||||
// Element-tool aggregator + NAMES set + runtime dispatcher.
|
||||
//
|
||||
// JSON schema definitions live in two sibling files to keep each file
|
||||
// under the repo's 800-line ceiling:
|
||||
//
|
||||
// element-tool-defs-base.ts — 19 initial tools (the 2026-04-19 batch)
|
||||
// element-tool-defs-ext.ts — 20 extension tools (later batches through
|
||||
// 2026-04-20's 8-tool atom batch)
|
||||
//
|
||||
// This file does NOT define tool schemas inline; it only:
|
||||
// - imports the two registries, concatenates into ELEMENT_TOOL_DEFINITIONS
|
||||
// - imports every element-tool handler and wires them into a switch
|
||||
// that `design-routes.ts` dispatches to
|
||||
// When growing the family, add the schema to ext-registry and extend
|
||||
// this file's handler imports + switch case — schema and dispatch stay
|
||||
// one edit each, file stays short.
|
||||
|
||||
import { handleAddCardRowV0 } from '../tools/add-card-row-v0';
|
||||
import { handleAddMetricRowV0 } from '../tools/add-metric-row-v0';
|
||||
|
|
@ -42,684 +54,11 @@ import { handleAddPriceV0 } from '../tools/add-price-v0';
|
|||
import { handleAddQuoteBlockV0 } from '../tools/add-quote-block-v0';
|
||||
import { handleAddCodeBlockV0 } from '../tools/add-code-block-v0';
|
||||
import { handleAddColorSwatchV0 } from '../tools/add-color-swatch-v0';
|
||||
import { ELEMENT_TOOL_DEFINITIONS_BASE } from './element-tool-defs-base';
|
||||
import { ELEMENT_TOOL_DEFINITIONS_EXT } from './element-tool-defs-ext';
|
||||
|
||||
export const ELEMENT_TOOL_DEFINITIONS = [
|
||||
{
|
||||
name: 'add_card_row_v0',
|
||||
description:
|
||||
'Create a horizontal scroll row of CARDS (title + subtitle + optional icon). Each card is ' +
|
||||
'140×160, cornerRadius=20. Forces the overflow-safe wrapper+clipContent+fit_content structure ' +
|
||||
'taught in packages/pen-ai-skills/skills/phases/generation/overflow.md §HORIZONTAL SCROLL ROWS. ' +
|
||||
'Use when spec mentions "workout cards", "feature cards", "swipeable content cards", "pills", ' +
|
||||
'or any row where each item has a prominent title plus descriptive subtext. schemaVersion 1.0',
|
||||
inputSchema: {
|
||||
type: 'object' as const,
|
||||
properties: {
|
||||
schemaVersion: {
|
||||
type: 'string',
|
||||
enum: ['1.0'],
|
||||
description:
|
||||
'Schema version this tool was authored against (v0-MUST §4.2). Clients MAY omit. Breaking schema changes ship as a new tool with _v1 suffix; old tools are kept one stage before being removed from ListTools.',
|
||||
},
|
||||
filePath: { type: 'string', description: 'Path to .op file, or omit for live canvas' },
|
||||
items: {
|
||||
type: 'array',
|
||||
description: 'Card items. Each needs title; subtitle and icon are optional.',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
title: { type: 'string' },
|
||||
subtitle: { type: 'string' },
|
||||
icon: { type: 'string', description: 'lucide icon name' },
|
||||
},
|
||||
required: ['title'],
|
||||
},
|
||||
},
|
||||
card_width: { type: 'number', description: 'Fixed width per card (default 140)' },
|
||||
gap: { type: 'number', description: 'Inner-row gap in px (default 12)' },
|
||||
parent_id: {
|
||||
type: 'string',
|
||||
description:
|
||||
'Target parent node id (must exist in the document). Omit for root-level insertion.',
|
||||
},
|
||||
pageId: { type: 'string', description: 'Target page ID (defaults to first page)' },
|
||||
},
|
||||
required: ['items'],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'add_metric_row_v0',
|
||||
description:
|
||||
'Create a horizontal scroll row of METRIC TILES (small label + big value + optional icon). ' +
|
||||
'Each tile is 120×100, cornerRadius=16, value rendered at 28/700 heading. Forces the ' +
|
||||
'overflow-safe wrapper+clipContent+fit_content structure. Use when spec mentions "dashboard ' +
|
||||
'stats", "KPI cards", "metric tiles", or shows Steps/Kcal/Sleep/Revenue-style rows. schemaVersion 1.0',
|
||||
inputSchema: {
|
||||
type: 'object' as const,
|
||||
properties: {
|
||||
schemaVersion: {
|
||||
type: 'string',
|
||||
enum: ['1.0'],
|
||||
description:
|
||||
'Schema version this tool was authored against (v0-MUST §4.2). Clients MAY omit. Breaking schema changes ship as a new tool with _v1 suffix; old tools are kept one stage before being removed from ListTools.',
|
||||
},
|
||||
filePath: { type: 'string', description: 'Path to .op file, or omit for live canvas' },
|
||||
items: {
|
||||
type: 'array',
|
||||
description: 'Metric items. Each needs label + value; icon is optional.',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
label: { type: 'string', description: 'Small descriptive label (e.g. "Steps")' },
|
||||
value: { type: 'string', description: 'Big number / formatted value (e.g. "8,432")' },
|
||||
icon: { type: 'string', description: 'lucide icon name' },
|
||||
},
|
||||
required: ['label', 'value'],
|
||||
},
|
||||
},
|
||||
tile_width: { type: 'number', description: 'Fixed width per tile (default 120)' },
|
||||
gap: { type: 'number', description: 'Inner-row gap in px (default 12)' },
|
||||
parent_id: {
|
||||
type: 'string',
|
||||
description:
|
||||
'Target parent node id (must exist in the document). Omit for root-level insertion.',
|
||||
},
|
||||
pageId: { type: 'string', description: 'Target page ID (defaults to first page)' },
|
||||
},
|
||||
required: ['items'],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'add_nav_chip_row_v0',
|
||||
description:
|
||||
'Create a horizontal scroll row of NAV CHIPS (icon + small label, each with optional active ' +
|
||||
'state). Each chip is 72×fit_content, cornerRadius=12. Forces the overflow-safe ' +
|
||||
'wrapper+clipContent+fit_content structure. Use when spec mentions "category filter chips", ' +
|
||||
'"quick access shortcuts", "horizontal tab chips", "swipeable nav items". schemaVersion 1.0',
|
||||
inputSchema: {
|
||||
type: 'object' as const,
|
||||
properties: {
|
||||
schemaVersion: {
|
||||
type: 'string',
|
||||
enum: ['1.0'],
|
||||
description:
|
||||
'Schema version this tool was authored against (v0-MUST §4.2). Clients MAY omit. Breaking schema changes ship as a new tool with _v1 suffix; old tools are kept one stage before being removed from ListTools.',
|
||||
},
|
||||
filePath: { type: 'string', description: 'Path to .op file, or omit for live canvas' },
|
||||
items: {
|
||||
type: 'array',
|
||||
description:
|
||||
'Chip items. Each needs label; icon and active are optional. Label-only chips are supported (text-only filter tags).',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
label: { type: 'string' },
|
||||
icon: { type: 'string', description: 'lucide icon name (optional)' },
|
||||
active: { type: 'boolean' },
|
||||
},
|
||||
required: ['label'],
|
||||
},
|
||||
},
|
||||
chip_width: { type: 'number', description: 'Fixed width per chip (default 72)' },
|
||||
gap: { type: 'number', description: 'Inner-row gap in px (default 12)' },
|
||||
parent_id: {
|
||||
type: 'string',
|
||||
description:
|
||||
'Target parent node id (must exist in the document). Omit for root-level insertion.',
|
||||
},
|
||||
pageId: { type: 'string', description: 'Target page ID (defaults to first page)' },
|
||||
},
|
||||
required: ['items'],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'add_bottom_nav_v0',
|
||||
description:
|
||||
'Create a bottom tab bar (inline, not fixed-position). Forces the pattern taught in ' +
|
||||
'packages/pen-ai-skills/skills/phases/generation/layout.md §NO FIXED-POSITION LAYOUT: ' +
|
||||
'bottom-tab-bar is an inline child of the page (no empty spacer sibling needed, no ' +
|
||||
'position:fixed since the engine does not support it). Always prefer this over batch_design ' +
|
||||
'when the spec mentions "bottom nav", "tab bar", "tabbar", "底部导航", "tab bar with icons". ' +
|
||||
'schemaVersion 1.0',
|
||||
inputSchema: {
|
||||
type: 'object' as const,
|
||||
properties: {
|
||||
schemaVersion: {
|
||||
type: 'string',
|
||||
enum: ['1.0'],
|
||||
description:
|
||||
'Schema version this tool was authored against (v0-MUST §4.2). Clients MAY omit. Breaking schema changes ship as a new tool with _v1 suffix; old tools are kept one stage before being removed from ListTools.',
|
||||
},
|
||||
filePath: { type: 'string', description: 'Path to .op file, or omit for live canvas' },
|
||||
items: {
|
||||
type: 'array',
|
||||
description:
|
||||
'Tab items. Each needs title + icon (lucide name). active marks current tab.',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
title: { type: 'string' },
|
||||
icon: { type: 'string', description: 'lucide icon name (e.g. "home")' },
|
||||
active: { type: 'boolean' },
|
||||
},
|
||||
required: ['title', 'icon'],
|
||||
},
|
||||
},
|
||||
height: { type: 'number', description: 'Bar height in px (default 62)' },
|
||||
parent_id: {
|
||||
type: 'string',
|
||||
description:
|
||||
'Target parent node id (must exist in the document; validated before insertion). Omit for root-level insertion.',
|
||||
},
|
||||
pageId: { type: 'string', description: 'Target page ID (defaults to first page)' },
|
||||
},
|
||||
required: ['items'],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'add_activity_ring_v0',
|
||||
description:
|
||||
'Create an Apple-style activity ring (progress ring) with centered text. Forces the ' +
|
||||
'frame+cornerRadius+stroke+centered-text pattern taught in ' +
|
||||
'packages/pen-ai-skills/skills/phases/generation/layout.md §RING / CIRCLE WITH CENTER CONTENT. ' +
|
||||
'NEVER emits the documented anti-patterns (ellipse+sibling text, layout=none+absolute). ' +
|
||||
'Ships colorless with fixed typography (Style Guide orthogonal) — override ring color / text ' +
|
||||
'size / weight via a follow-up batch_design U-op. Use when the spec mentions "activity ring", ' +
|
||||
'"progress ring", "circular progress", "ring with number", "Apple health ring". schemaVersion 1.0',
|
||||
inputSchema: {
|
||||
type: 'object' as const,
|
||||
properties: {
|
||||
schemaVersion: {
|
||||
type: 'string',
|
||||
enum: ['1.0'],
|
||||
description:
|
||||
'Schema version this tool was authored against (v0-MUST §4.2). Clients MAY omit. Breaking schema changes ship as a new tool with _v1 suffix; old tools are kept one stage before being removed from ListTools.',
|
||||
},
|
||||
filePath: { type: 'string', description: 'Path to .op file, or omit for live canvas' },
|
||||
size: { type: 'number', description: 'Ring outer diameter in px (default 80)' },
|
||||
thickness: { type: 'number', description: 'Stroke thickness in px (default 8)' },
|
||||
center_text: { type: 'string', description: 'Text displayed in the ring center' },
|
||||
parent_id: {
|
||||
type: 'string',
|
||||
description:
|
||||
'Target parent node id (must exist in the document). Omit for root-level insertion.',
|
||||
},
|
||||
pageId: { type: 'string', description: 'Target page ID (defaults to first page)' },
|
||||
},
|
||||
required: ['center_text'],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'add_stat_grid_v0',
|
||||
description:
|
||||
'Create a NON-scrolling stat grid (2-5 items share the row via fill_container). ' +
|
||||
'Different from add_metric_row_v0: this emits an inline grid that auto-distributes ' +
|
||||
'available width, solving the documented activity-rings overflow bug in ' +
|
||||
'packages/pen-ai-skills/skills/phases/generation/layout.md (three fixed 100px items in ' +
|
||||
'a 279px inner card silently clip the third item). Each cell is width=fill_container; ' +
|
||||
'renderer does the division. Use when spec mentions "stats row", "3 metrics side by side", ' +
|
||||
'"summary bar", or an inline (non-scrollable) row of KPIs. schemaVersion 1.0',
|
||||
inputSchema: {
|
||||
type: 'object' as const,
|
||||
properties: {
|
||||
schemaVersion: {
|
||||
type: 'string',
|
||||
enum: ['1.0'],
|
||||
description: 'Schema version (v0-MUST §4.2). Clients MAY omit.',
|
||||
},
|
||||
filePath: { type: 'string', description: 'Path to .op file, or omit for live canvas' },
|
||||
items: {
|
||||
type: 'array',
|
||||
description:
|
||||
'Stat cells. Each needs value + label; icon is optional. 2-5 items work best.',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
value: { type: 'string', description: 'Big numeric value (e.g. "8,432")' },
|
||||
label: { type: 'string', description: 'Small descriptive label (e.g. "Steps")' },
|
||||
icon: { type: 'string', description: 'lucide icon name (optional)' },
|
||||
},
|
||||
required: ['value', 'label'],
|
||||
},
|
||||
},
|
||||
gap: { type: 'number', description: 'Gap between cells in px (default 16)' },
|
||||
parent_id: {
|
||||
type: 'string',
|
||||
description: 'Target parent node id (must exist). Omit for root-level insertion.',
|
||||
},
|
||||
pageId: { type: 'string', description: 'Target page ID (defaults to first page)' },
|
||||
},
|
||||
required: ['items'],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'add_section_header_v0',
|
||||
description:
|
||||
'Section header with big title on left + optional trailing action (e.g. "See all", ' +
|
||||
'"View more"). Forces horizontal space_between alignItems=center layout so the action ' +
|
||||
'always sits flush-right. Use when spec shows "Section Title" with a "See all" or "→" link, ' +
|
||||
'or any heading + secondary action pair. schemaVersion 1.0',
|
||||
inputSchema: {
|
||||
type: 'object' as const,
|
||||
properties: {
|
||||
schemaVersion: {
|
||||
type: 'string',
|
||||
enum: ['1.0'],
|
||||
description: 'Schema version (v0-MUST §4.2). Clients MAY omit.',
|
||||
},
|
||||
filePath: { type: 'string', description: 'Path to .op file, or omit for live canvas' },
|
||||
title: { type: 'string', description: 'Section title (big heading)' },
|
||||
action: {
|
||||
type: 'object',
|
||||
description: 'Optional trailing action (e.g. { label: "See all", icon: "arrow-right" })',
|
||||
properties: {
|
||||
label: { type: 'string' },
|
||||
icon: { type: 'string', description: 'lucide icon name (optional)' },
|
||||
},
|
||||
required: ['label'],
|
||||
},
|
||||
parent_id: {
|
||||
type: 'string',
|
||||
description: 'Target parent node id (must exist). Omit for root-level insertion.',
|
||||
},
|
||||
pageId: { type: 'string', description: 'Target page ID (defaults to first page)' },
|
||||
},
|
||||
required: ['title'],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'add_top_nav_bar_v0',
|
||||
description:
|
||||
'Mobile top navigation bar: optional leading icon (back/menu) + centered title + ' +
|
||||
'optional trailing icon (search/more). Dual of add_bottom_nav_v0. Title always centered; ' +
|
||||
'empty slots become 44×44 spacers so the title visually stays centered. Use when spec ' +
|
||||
'mentions "top bar", "app bar", "header with back button", "页面标题栏". schemaVersion 1.0',
|
||||
inputSchema: {
|
||||
type: 'object' as const,
|
||||
properties: {
|
||||
schemaVersion: {
|
||||
type: 'string',
|
||||
enum: ['1.0'],
|
||||
description: 'Schema version (v0-MUST §4.2). Clients MAY omit.',
|
||||
},
|
||||
filePath: { type: 'string', description: 'Path to .op file, or omit for live canvas' },
|
||||
title: { type: 'string', description: 'Centered title text' },
|
||||
leading_icon: {
|
||||
type: 'string',
|
||||
description: 'lucide icon name for the left slot (e.g. "chevron-left", "menu")',
|
||||
},
|
||||
trailing_icon: {
|
||||
type: 'string',
|
||||
description: 'lucide icon name for the right slot (e.g. "search", "more-vertical")',
|
||||
},
|
||||
height: { type: 'number', description: 'Bar height in px (default 56)' },
|
||||
parent_id: {
|
||||
type: 'string',
|
||||
description: 'Target parent node id (must exist). Omit for root-level insertion.',
|
||||
},
|
||||
pageId: { type: 'string', description: 'Target page ID (defaults to first page)' },
|
||||
},
|
||||
required: ['title'],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'add_icon_button_v0',
|
||||
description:
|
||||
'Icon-only button. Forces 44×44 minimum hit target (Apple HIG + Material) with ' +
|
||||
'flex-centered icon — NEVER emits the layout=none + absolute-positioned icon anti-pattern ' +
|
||||
'documented in pen-ai-skills memory (layout=none + nested children renders unreliably). ' +
|
||||
'Use when the spec shows "icon-only" buttons, search/close/menu buttons, or iconic actions ' +
|
||||
'in toolbars. schemaVersion 1.0',
|
||||
inputSchema: {
|
||||
type: 'object' as const,
|
||||
properties: {
|
||||
schemaVersion: {
|
||||
type: 'string',
|
||||
enum: ['1.0'],
|
||||
description: 'Schema version (v0-MUST §4.2). Clients MAY omit.',
|
||||
},
|
||||
filePath: { type: 'string', description: 'Path to .op file, or omit for live canvas' },
|
||||
icon: { type: 'string', description: 'lucide icon name' },
|
||||
size: { type: 'number', description: 'Button size (hit-target) in px (default 44)' },
|
||||
icon_size: { type: 'number', description: 'Icon glyph size in px (default 24)' },
|
||||
parent_id: {
|
||||
type: 'string',
|
||||
description: 'Target parent node id (must exist). Omit for root-level insertion.',
|
||||
},
|
||||
pageId: { type: 'string', description: 'Target page ID (defaults to first page)' },
|
||||
},
|
||||
required: ['icon'],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'add_divider_v0',
|
||||
description:
|
||||
'Hairline divider (rectangle, not stroke). Forces the pattern documented in pen-ai-skills memory: ' +
|
||||
'rectangle with height=1 + width=fill_container (horizontal) or width=1 + height=fill_container ' +
|
||||
'(vertical). Use between list rows, between form sections, between a card content and its footer. ' +
|
||||
'Ships colorless — override fill via a follow-up batch_design U-op. schemaVersion 1.0',
|
||||
inputSchema: {
|
||||
type: 'object' as const,
|
||||
properties: {
|
||||
schemaVersion: {
|
||||
type: 'string',
|
||||
enum: ['1.0'],
|
||||
description: 'Schema version (v0-MUST §4.2). Clients MAY omit.',
|
||||
},
|
||||
filePath: { type: 'string', description: 'Path to .op file, or omit for live canvas' },
|
||||
orientation: {
|
||||
type: 'string',
|
||||
enum: ['horizontal', 'vertical'],
|
||||
description: 'Default horizontal',
|
||||
},
|
||||
thickness: { type: 'number', description: 'Divider thickness in px (default 1)' },
|
||||
parent_id: {
|
||||
type: 'string',
|
||||
description: 'Target parent node id (must exist). Omit for root-level insertion.',
|
||||
},
|
||||
pageId: { type: 'string', description: 'Target page ID (defaults to first page)' },
|
||||
},
|
||||
required: [],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'add_badge_v0',
|
||||
description:
|
||||
'Short inline badge / pill / tag ("NEW", "BETA", "42", "Sale"). Forces the standard pill ' +
|
||||
'layout (cornerRadius=999, padding=[4,10], font 11/600). Documented constraint (overflow.md): ' +
|
||||
'CJK ≤8 chars, Latin ≤16 chars — longer labels are NOT a badge, use batch_design. schemaVersion 1.0',
|
||||
inputSchema: {
|
||||
type: 'object' as const,
|
||||
properties: {
|
||||
schemaVersion: {
|
||||
type: 'string',
|
||||
enum: ['1.0'],
|
||||
description: 'Schema version (v0-MUST §4.2). Clients MAY omit.',
|
||||
},
|
||||
filePath: { type: 'string', description: 'Path to .op file, or omit for live canvas' },
|
||||
label: {
|
||||
type: 'string',
|
||||
description: 'Short label (Latin ≤16 chars / CJK ≤8 chars)',
|
||||
},
|
||||
parent_id: {
|
||||
type: 'string',
|
||||
description: 'Target parent node id (must exist). Omit for root-level insertion.',
|
||||
},
|
||||
pageId: { type: 'string', description: 'Target page ID (defaults to first page)' },
|
||||
},
|
||||
required: ['label'],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'add_avatar_v0',
|
||||
description:
|
||||
'Circular avatar with optional centered initial. Forces the same frame+cornerRadius=size/2+flex-' +
|
||||
'centering pattern as add_activity_ring_v0 — NEVER the ellipse+sibling text anti-pattern ' +
|
||||
'documented in layout.md. Default size 40 (inline). For larger profile avatars pass size: 56 ' +
|
||||
'or 96. Without `initial`, emits an empty circle ready for an image child via batch_design. ' +
|
||||
'schemaVersion 1.0',
|
||||
inputSchema: {
|
||||
type: 'object' as const,
|
||||
properties: {
|
||||
schemaVersion: {
|
||||
type: 'string',
|
||||
enum: ['1.0'],
|
||||
description: 'Schema version (v0-MUST §4.2). Clients MAY omit.',
|
||||
},
|
||||
filePath: { type: 'string', description: 'Path to .op file, or omit for live canvas' },
|
||||
initial: {
|
||||
type: 'string',
|
||||
description: 'Single character / short initial (e.g. "A", "JD"). Omit for empty circle.',
|
||||
},
|
||||
size: { type: 'number', description: 'Diameter in px (default 40)' },
|
||||
parent_id: {
|
||||
type: 'string',
|
||||
description: 'Target parent node id (must exist). Omit for root-level insertion.',
|
||||
},
|
||||
pageId: { type: 'string', description: 'Target page ID (defaults to first page)' },
|
||||
},
|
||||
required: [],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'add_text_button_v0',
|
||||
description:
|
||||
'Padding-based text button matching the Pencil demo pattern (documented in memory): ' +
|
||||
'frame(padding=[12,20], horizontal, alignItems=center, justifyContent=center, cornerRadius=8) ' +
|
||||
'+ optional leading icon + label text. Height auto-derives from padding + text; NO explicit ' +
|
||||
'fixed height. Use for primary / secondary buttons with a short text label. schemaVersion 1.0',
|
||||
inputSchema: {
|
||||
type: 'object' as const,
|
||||
properties: {
|
||||
schemaVersion: {
|
||||
type: 'string',
|
||||
enum: ['1.0'],
|
||||
description: 'Schema version (v0-MUST §4.2). Clients MAY omit.',
|
||||
},
|
||||
filePath: { type: 'string', description: 'Path to .op file, or omit for live canvas' },
|
||||
label: { type: 'string', description: 'Button text (1-3 words recommended)' },
|
||||
leading_icon: {
|
||||
type: 'string',
|
||||
description: 'Optional leading lucide icon name (e.g. "plus", "arrow-right")',
|
||||
},
|
||||
parent_id: {
|
||||
type: 'string',
|
||||
description: 'Target parent node id (must exist). Omit for root-level insertion.',
|
||||
},
|
||||
pageId: { type: 'string', description: 'Target page ID (defaults to first page)' },
|
||||
},
|
||||
required: ['label'],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'add_heading_v0',
|
||||
description:
|
||||
'Typographic heading with Pencil-demo-derived presets + AUTO CJK script handling. ' +
|
||||
'Latin (default) per-level: display=48/700/1.0/letterSpacing=-0.5, h1=32/700/1.1, ' +
|
||||
'h2=24/600/1.2 (DEFAULT), h3=20/600/1.25. CJK content switches to: display=48/700/1.3 + ' +
|
||||
'NO negative letterSpacing, h1=32/700/1.3, h2=24/600/1.35, h3=20/600/1.4 + script-specific ' +
|
||||
'fontFamily (Chinese → Noto Sans SC / Japanese → Noto Sans JP / Korean → Noto Sans KR — ' +
|
||||
'text-rules.md: NEVER use SC for JP or KR). Encodes the preset so non-Claude models cannot ' +
|
||||
'forget lineHeight (default 1.5 stacks multi-word headings tight) or pick the wrong font for ' +
|
||||
'JP/KR content. Emits a single text node with role=heading. schemaVersion 1.0',
|
||||
inputSchema: {
|
||||
type: 'object' as const,
|
||||
properties: {
|
||||
schemaVersion: {
|
||||
type: 'string',
|
||||
enum: ['1.0'],
|
||||
description: 'Schema version (v0-MUST §4.2). Clients MAY omit.',
|
||||
},
|
||||
filePath: { type: 'string', description: 'Path to .op file, or omit for live canvas' },
|
||||
content: { type: 'string', description: 'Heading text' },
|
||||
level: {
|
||||
type: 'string',
|
||||
enum: ['display', 'h1', 'h2', 'h3'],
|
||||
description: 'Typography level. Default: h2.',
|
||||
},
|
||||
parent_id: {
|
||||
type: 'string',
|
||||
description: 'Target parent node id (must exist). Omit for root-level insertion.',
|
||||
},
|
||||
pageId: { type: 'string', description: 'Target page ID (defaults to first page)' },
|
||||
},
|
||||
required: ['content'],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'add_body_text_v0',
|
||||
description:
|
||||
'Body / description text. fontFamily ALWAYS Inter (per text-rules.md rule "body=Inter" — ' +
|
||||
'Inter uses system CJK fallback, so script-specific Noto faces apply ONLY to headings, ' +
|
||||
'not body). Script is auto-detected just to set the right lineHeight + letterSpacing: ' +
|
||||
'CJK (Chinese / Japanese / Korean) body gets lineHeight=1.6 + letterSpacing=0 (NEVER ' +
|
||||
'negative — causes CJK character overlap); Latin body gets lineHeight=1.5 + no letterSpacing ' +
|
||||
'override. Always sets width=fill_container + textGrowth=fixed-width so long text wraps ' +
|
||||
'(overflow.md: fixed-width required for >15-char text to wrap). Intended for VERTICAL-layout ' +
|
||||
'parents only. schemaVersion 1.0',
|
||||
inputSchema: {
|
||||
type: 'object' as const,
|
||||
properties: {
|
||||
schemaVersion: {
|
||||
type: 'string',
|
||||
enum: ['1.0'],
|
||||
description: 'Schema version (v0-MUST §4.2). Clients MAY omit.',
|
||||
},
|
||||
filePath: { type: 'string', description: 'Path to .op file, or omit for live canvas' },
|
||||
content: { type: 'string', description: 'Body text content (single paragraph)' },
|
||||
parent_id: {
|
||||
type: 'string',
|
||||
description:
|
||||
'Target parent node id (must exist; MUST be a vertical-layout frame). Omit for root insertion.',
|
||||
},
|
||||
pageId: { type: 'string', description: 'Target page ID (defaults to first page)' },
|
||||
},
|
||||
required: ['content'],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'add_icon_label_v0',
|
||||
description:
|
||||
'Atomic icon + label pair (horizontal, alignItems=center, gap=8). Common building block ' +
|
||||
'for menu items, breadcrumbs, status indicators, any "icon with text" inline composition. ' +
|
||||
'Narrow schema: icons always lead; size/weight fixed (icon 16×16, text 14/500). ' +
|
||||
'schemaVersion 1.0',
|
||||
inputSchema: {
|
||||
type: 'object' as const,
|
||||
properties: {
|
||||
schemaVersion: {
|
||||
type: 'string',
|
||||
enum: ['1.0'],
|
||||
description: 'Schema version (v0-MUST §4.2). Clients MAY omit.',
|
||||
},
|
||||
filePath: { type: 'string', description: 'Path to .op file, or omit for live canvas' },
|
||||
icon: { type: 'string', description: 'lucide icon name' },
|
||||
label: { type: 'string', description: 'Text label' },
|
||||
gap: { type: 'number', description: 'Gap between icon and label (default 8)' },
|
||||
parent_id: {
|
||||
type: 'string',
|
||||
description: 'Target parent node id (must exist). Omit for root-level insertion.',
|
||||
},
|
||||
pageId: { type: 'string', description: 'Target page ID (defaults to first page)' },
|
||||
},
|
||||
required: ['icon', 'label'],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'add_search_bar_v0',
|
||||
description:
|
||||
'Rounded search bar (height=44, cornerRadius=22, width=fill_container) matching the ' +
|
||||
'search-bar role spec in ROLE_GUIDE. Leading icon default "search" — override for ' +
|
||||
'custom affordance (e.g. "filter", "map-pin"). Placeholder text default "Search...". ' +
|
||||
'Use when spec shows a standalone search input, header search, or list filter bar. ' +
|
||||
'schemaVersion 1.0',
|
||||
inputSchema: {
|
||||
type: 'object' as const,
|
||||
properties: {
|
||||
schemaVersion: {
|
||||
type: 'string',
|
||||
enum: ['1.0'],
|
||||
description: 'Schema version (v0-MUST §4.2). Clients MAY omit.',
|
||||
},
|
||||
filePath: { type: 'string', description: 'Path to .op file, or omit for live canvas' },
|
||||
placeholder: {
|
||||
type: 'string',
|
||||
description: 'Placeholder text (default "Search...")',
|
||||
},
|
||||
leading_icon: {
|
||||
type: 'string',
|
||||
description: 'lucide icon name (default "search")',
|
||||
},
|
||||
parent_id: {
|
||||
type: 'string',
|
||||
description: 'Target parent node id (must exist). Omit for root-level insertion.',
|
||||
},
|
||||
pageId: { type: 'string', description: 'Target page ID (defaults to first page)' },
|
||||
},
|
||||
required: [],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'add_form_field_v0',
|
||||
description:
|
||||
'Label + input vertical pair. Enforces FORMS rule from DESIGN_GUIDELINES ("ALL inputs ' +
|
||||
'MUST use width=fill_container, vertical layout, gap=16-20") and ROLE_GUIDE input specs ' +
|
||||
'(height=48, padding=[12,16]). Affordance icons: leading_icon for email/search leads, ' +
|
||||
'trailing_icon for password-toggle. required=true appends " *" to the label. Intended ' +
|
||||
'for use inside a vertical form container with its siblings at gap=16-20. schemaVersion 1.0',
|
||||
inputSchema: {
|
||||
type: 'object' as const,
|
||||
properties: {
|
||||
schemaVersion: {
|
||||
type: 'string',
|
||||
enum: ['1.0'],
|
||||
description: 'Schema version (v0-MUST §4.2). Clients MAY omit.',
|
||||
},
|
||||
filePath: { type: 'string', description: 'Path to .op file, or omit for live canvas' },
|
||||
label: { type: 'string', description: 'Field label text' },
|
||||
placeholder: { type: 'string', description: 'Optional input placeholder' },
|
||||
leading_icon: {
|
||||
type: 'string',
|
||||
description: 'Optional leading icon (e.g. "mail" for email, "search" for search)',
|
||||
},
|
||||
trailing_icon: {
|
||||
type: 'string',
|
||||
description: 'Optional trailing icon (e.g. "eye" for password toggle)',
|
||||
},
|
||||
required: {
|
||||
type: 'boolean',
|
||||
description: 'When true, appends " *" to the label text',
|
||||
},
|
||||
parent_id: {
|
||||
type: 'string',
|
||||
description: 'Target parent node id (must exist). Omit for root-level insertion.',
|
||||
},
|
||||
pageId: { type: 'string', description: 'Target page ID (defaults to first page)' },
|
||||
},
|
||||
required: ['label'],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'add_list_row_v0',
|
||||
description:
|
||||
'iOS / Material-style list row: optional leading icon + center text stack (title + ' +
|
||||
'optional subtitle) + optional trailing icon (typically chevron-right). The middle text ' +
|
||||
'stack is wrapped in a VERTICAL container with width=fill_container — long titles wrap ' +
|
||||
'vertically and the row grows height-wise WITHOUT pushing the trailing icon out of frame ' +
|
||||
'(same pattern as add_section_header_v0; per overflow.md: text with fill_container + ' +
|
||||
'fixed-width only works inside vertical-layout parents). schemaVersion 1.0',
|
||||
inputSchema: {
|
||||
type: 'object' as const,
|
||||
properties: {
|
||||
schemaVersion: {
|
||||
type: 'string',
|
||||
enum: ['1.0'],
|
||||
description: 'Schema version (v0-MUST §4.2). Clients MAY omit.',
|
||||
},
|
||||
filePath: { type: 'string', description: 'Path to .op file, or omit for live canvas' },
|
||||
title: { type: 'string', description: 'Main row title (bold, 15/500)' },
|
||||
subtitle: {
|
||||
type: 'string',
|
||||
description: 'Optional secondary text (smaller, 13/400) shown below title',
|
||||
},
|
||||
leading_icon: {
|
||||
type: 'string',
|
||||
description: 'Optional 24×24 lucide icon before the text stack',
|
||||
},
|
||||
trailing_icon: {
|
||||
type: 'string',
|
||||
description: 'Optional 16×16 lucide icon after the text stack (e.g. "chevron-right")',
|
||||
},
|
||||
parent_id: {
|
||||
type: 'string',
|
||||
description: 'Target parent node id (must exist). Omit for root-level insertion.',
|
||||
},
|
||||
pageId: { type: 'string', description: 'Target page ID (defaults to first page)' },
|
||||
},
|
||||
required: ['title'],
|
||||
},
|
||||
},
|
||||
...ELEMENT_TOOL_DEFINITIONS_BASE,
|
||||
...ELEMENT_TOOL_DEFINITIONS_EXT,
|
||||
];
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue