Purpose
Turn product design descriptions into a full-page text UI (TUI) that is also machine-readable and drawable:
- Output a combined
TUI_RENDERfor the entire page. - Output a per-component summary table.
- Output a consolidated
PENCIL_SPECandPENCIL_BATCH_DESIGNplan (≤25 ops per call).
This skill assumes you follow the shared contract and grid→pixel mapping from tui-front-ui.
Inputs (Recommended)
Provide a page request and a component list:
{
"page": {
"name": "Settings",
"widthCols": 70,
"canvas": { "widthPx": 390, "heightPx": 844 },
"grid": { "cellWidthPx": 8, "cellHeightPx": 16 },
"paddingPx": 24,
"gapPx": 16,
"backgroundColor": "#ffffff"
},
"components": [
{
"id": "cmp_nav",
"type": "tui-navbar",
"preferredSizePx": { "widthPx": 342, "heightPx": 56 },
"zIndex": 10
}
]
}
Output Contract (Mandatory)
OUTPUT: TUI_RENDER
...monospace-only text...
OUTPUT: PAGE_SUMMARY
| id | type | top | left | width | height | z | keyProps | state | hotkeys |
|------------|-------------|-----|------|-------|--------|---|----------|-------|---------|
| cmp_nav | tui-navbar | 24 | 24 | 342 | 56 |10 | ... | ... | ... |
OUTPUT: PENCIL_SPEC
{
"canvas": { "widthPx": 390, "heightPx": 844, "backgroundColor": "#ffffff" },
"grid": { "cellWidthPx": 8, "cellHeightPx": 16 },
"nodes": [],
"components": []
}
OUTPUT: PENCIL_BATCH_DESIGN
CALL 1:
root=G()
screen=I(root,{type:"frame",name:"Settings"})
U(screen,{width:390,height:844,x:0,y:0})
CALL 2:
...up to 25 ops...
Composition Rules (Strict)
1) Layout model
- Use a single page frame as root.
- Default layout: vertical stacking within page padding.
- Place components by ascending
topPx; break ties usingzIndex. - Preserve requested
zIndexin bothCOMPONENT_SPECand drawing order:- Background blocks first
- Content blocks next
- Overlays (modal/toast) last
2) Geometry calculation
Given:
paddingPxgapPx- each component
preferredSizePxor computed size from itsTUI_RENDERgrid bbox
Compute:
leftPx = paddingPxtopPx = paddingPx + sum(prev.heightPx + gapPx)widthPx = canvas.widthPx - paddingPx * 2unless a component requests a smaller width
3) ASCII rendering layout
- Use one outer page frame.
- Render each component as a framed block and separate blocks with a single blank line.
- Ensure the entire page does not exceed
page.widthCols.
4) Interactions
- Combine hotkeys and de-duplicate:
- Page-level default:
<tab> next,<esc> back - Component-level only if meaningful
- Page-level default:
- Do not show hotkeys for disabled components.
Pencil MCP Execution Playbook
get_editor_state(include_schema=false)open_document("new")(or an existing.penfile path)- Apply
PENCIL_BATCH_DESIGNin multiple calls (≤25 ops/call). snapshot_layout(filePath, maxDepth=2, problemsOnly=true)get_screenshot(filePath, nodeId)for the page frame node
If any layout problems are returned, create a follow-up batch_design patch block.
Example — Settings page with 4 components
OUTPUT: TUI_RENDER
┌──────────────────────────────────────────────────────────────┐
│ [Settings] │
├──────────────────────────────────────────────────────────────┤
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ [Navbar] Settings [Back] │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ [Form] │ │
│ │ Username: [ ada_lovelace________________________ ] │ │
│ │ Email: [ ada@example.com______________________ ] │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ [Rate] * * * * . (4/5) │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ [Button] Save changes │ │
│ └──────────────────────────────────────────────────────────┘ │
├──────────────────────────────────────────────────────────────┤
│ Keys: <tab> next <enter> action <esc> back │
└──────────────────────────────────────────────────────────────┘
OUTPUT: PAGE_SUMMARY
| id | type | top | left | width | height | z | keyProps | state | hotkeys |
|---------------|-------------|-----|------|-------|--------|----|------------------------|--------------------|-------------------------|
| cmp_nav | tui-navbar | 24 | 24 | 342 | 56 | 10 | title=Settings | focused=false | <esc> |
| cmp_form | tui-form | 96 | 24 | 342 | 128 | 1 | fields=2 | focused=true | <tab> |
| cmp_rate | tui-rate | 240 | 24 | 342 | 64 | 1 | value=4,count=5 | disabled=false | <left>/<right>,<enter> |
| cmp_save | tui-button | 320 | 24 | 342 | 56 | 2 | variant=primary | disabled=false | <enter> |
OUTPUT: PENCIL_SPEC
{
"canvas": { "widthPx": 390, "heightPx": 844, "backgroundColor": "#ffffff" },
"grid": { "cellWidthPx": 8, "cellHeightPx": 16 },
"nodes": [],
"components": [
{ "id": "cmp_nav", "name": "Navbar", "bbox": { "topPx": 24, "leftPx": 24, "widthPx": 342, "heightPx": 56 }, "zIndex": 10 },
{ "id": "cmp_form", "name": "Form", "bbox": { "topPx": 96, "leftPx": 24, "widthPx": 342, "heightPx": 128 }, "zIndex": 1 },
{ "id": "cmp_rate", "name": "Rate", "bbox": { "topPx": 240, "leftPx": 24, "widthPx": 342, "heightPx": 64 }, "zIndex": 1 },
{ "id": "cmp_save", "name": "Button", "bbox": { "topPx": 320, "leftPx": 24, "widthPx": 342, "heightPx": 56 }, "zIndex": 2 }
]
}
OUTPUT: PENCIL_BATCH_DESIGN
CALL 1:
root=G()
screen=I(root,{type:"frame",name:"Settings"})
U(screen,{width:390,height:844,x:0,y:0})
CALL 2:
navBg=I(screen,{type:"rect",name:"Navbar/Background"})
U(navBg,{x:24,y:24,width:342,height:56,fillColor:"#ffffff",strokeColor:"#e5e7eb",strokeThickness:1,cornerRadius:12})
navTitle=I(screen,{type:"text",name:"Navbar/Title",content:"Settings"})
U(navTitle,{x:40,y:40,width:310,height:20,textColor:"#111111",fontFamily:"Inter",fontSize:16,fontWeight:600})
formBg=I(screen,{type:"rect",name:"Form/Background"})
U(formBg,{x:24,y:96,width:342,height:128,fillColor:"#ffffff",strokeColor:"#e5e7eb",strokeThickness:1,cornerRadius:12})
uLabel=I(screen,{type:"text",name:"Form/UsernameLabel",content:"Username"})
U(uLabel,{x:40,y:112,width:90,height:20,textColor:"#111111",fontFamily:"Inter",fontSize:14,fontWeight:600})
uValue=I(screen,{type:"text",name:"Form/UsernameValue",content:"ada_lovelace"})
U(uValue,{x:140,y:112,width:210,height:20,textColor:"#111111",fontFamily:"Inter",fontSize:14,fontWeight:400})
eLabel=I(screen,{type:"text",name:"Form/EmailLabel",content:"Email"})
U(eLabel,{x:40,y:140,width:90,height:20,textColor:"#111111",fontFamily:"Inter",fontSize:14,fontWeight:600})
eValue=I(screen,{type:"text",name:"Form/EmailValue",content:"ada@example.com"})
U(eValue,{x:140,y:140,width:210,height:20,textColor:"#111111",fontFamily:"Inter",fontSize:14,fontWeight:400})
CALL 3:
rateBg=I(screen,{type:"rect",name:"Rate/Background"})
U(rateBg,{x:24,y:240,width:342,height:64,fillColor:"#ffffff",strokeColor:"#e5e7eb",strokeThickness:1,cornerRadius:12})
rateText=I(screen,{type:"text",name:"Rate/Text",content:"* * * * . (4/5)"})
U(rateText,{x:40,y:264,width:310,height:20,textColor:"#111111",fontFamily:"Inter",fontSize:14,fontWeight:400})
saveBg=I(screen,{type:"rect",name:"Button/Background"})
U(saveBg,{x:24,y:320,width:342,height:56,fillColor:"#111111",strokeColor:"#111111",strokeThickness:1,cornerRadius:12})
saveText=I(screen,{type:"text",name:"Button/Text",content:"Save changes"})
U(saveText,{x:40,y:338,width:310,height:20,textColor:"#ffffff",fontFamily:"Inter",fontSize:14,fontWeight:600})
国内适配
- 支持中文文档和中文注释
- 示例代码兼容国内开发环境
- 提供中文 FAQ 和常见问题解答
能力边界
✅ 适用场景
- 当你需要使用此技能对应的技术栈时
- 当项目需要遵循最佳实践时
- 当需要快速上手或深入理解核心概念时
⚠️ 需要注意
- 复杂业务逻辑需要结合具体场景调整
- 性能优化需要根据实际数据量评估
❌ 不适用场景
- 不相关的技术栈或框架
- 需要完全自定义的特殊场景
使用流程
Step 1: 环境准备
确保开发环境已安装必要的依赖和工具。
Step 2: 配置初始化
根据项目需求进行基础配置。
Step 3: 核心功能使用
按照示例代码实现核心功能。
Step 4: 测试验证
运行测试确保功能正常。
Step 5: 部署上线
完成开发后进行部署和监控。