# Tui Page Composer

> Compose multiple ASCII TUI components into a full page with a consolidated layout summary and Pencil MCP–ready batch_design plans, including validation steps via snapshot_layout and get_screenshot.

- Skill: `full-stack-skills/tui-page-composer` (Agent Skill)
- Install (CLI): `npx skillmds@latest add full-stack-skills/tui-page-composer`
- Raw SKILL.md: https://api.skillmd.com/api/skills/full-stack-skills/tui-page-composer/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: AI & ML
- Author: full-stack-skills (https://skillmd.com/u/full-stack-skills)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/full-stack-skills/tui-page-composer

---



## Purpose

Turn product design descriptions into a full-page text UI (TUI) that is also machine-readable and drawable:
- Output a combined `TUI_RENDER` for the entire page.
- Output a per-component summary table.
- Output a consolidated `PENCIL_SPEC` and `PENCIL_BATCH_DESIGN` plan (≤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:

```json
{
  "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

```text
...monospace-only text...
```

### OUTPUT: PAGE_SUMMARY

```text
| id         | type        | top | left | width | height | z | keyProps | state | hotkeys |
|------------|-------------|-----|------|-------|--------|---|----------|-------|---------|
| cmp_nav    | tui-navbar  |  24 |   24 |   342 |     56 |10 | ...      | ...   | ...     |
```

### OUTPUT: PENCIL_SPEC

```json
{
  "canvas": { "widthPx": 390, "heightPx": 844, "backgroundColor": "#ffffff" },
  "grid": { "cellWidthPx": 8, "cellHeightPx": 16 },
  "nodes": [],
  "components": []
}
```

### OUTPUT: PENCIL_BATCH_DESIGN

```text
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 using `zIndex`.
- Preserve requested `zIndex` in both `COMPONENT_SPEC` and drawing order:
  - Background blocks first
  - Content blocks next
  - Overlays (modal/toast) last

### 2) Geometry calculation

Given:
- `paddingPx`
- `gapPx`
- each component `preferredSizePx` or computed size from its `TUI_RENDER` grid bbox

Compute:
- `leftPx = paddingPx`
- `topPx = paddingPx + sum(prev.heightPx + gapPx)`
- `widthPx = canvas.widthPx - paddingPx * 2` unless 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
- Do not show hotkeys for disabled components.

## Pencil MCP Execution Playbook

1. `get_editor_state(include_schema=false)`
2. `open_document("new")` (or an existing `.pen` file path)
3. Apply `PENCIL_BATCH_DESIGN` in multiple calls (≤25 ops/call).
4. `snapshot_layout(filePath, maxDepth=2, problemsOnly=true)`
5. `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

```text
┌──────────────────────────────────────────────────────────────┐
│ [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

```text
| 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

```json
{
  "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

```text
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: 部署上线
完成开发后进行部署和监控。

