From 6ef172f0b4b20b9d45d71862ca7c5a67768a9304 Mon Sep 17 00:00:00 2001 From: Fini Date: Tue, 21 Apr 2026 00:01:28 +0800 Subject: [PATCH] refactor(mcp): split element-tool-defs into base + ext + aggregator MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- .../src/routes/element-tool-defs-base.ts | 683 +++++++++++++++++ .../pen-mcp/src/routes/element-tool-defs.ts | 697 +----------------- 2 files changed, 701 insertions(+), 679 deletions(-) create mode 100644 packages/pen-mcp/src/routes/element-tool-defs-base.ts diff --git a/packages/pen-mcp/src/routes/element-tool-defs-base.ts b/packages/pen-mcp/src/routes/element-tool-defs-base.ts new file mode 100644 index 000000000..6aa9ab9e2 --- /dev/null +++ b/packages/pen-mcp/src/routes/element-tool-defs-base.ts @@ -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'], + }, + }, +]; diff --git a/packages/pen-mcp/src/routes/element-tool-defs.ts b/packages/pen-mcp/src/routes/element-tool-defs.ts index b5143761b..b5379a10b 100644 --- a/packages/pen-mcp/src/routes/element-tool-defs.ts +++ b/packages/pen-mcp/src/routes/element-tool-defs.ts @@ -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, ];