Files
pos-system/microservices/.agent/skills/pencil-design/references/PITFALLS.md
Ho Ngoc Hai 76d75c753b Migrate
2026-05-23 18:37:02 +07:00

529 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Pencil Design Pitfalls / Lỗi Thường Gặp
Comprehensive list of common mistakes when working with Pencil `.pen` files and their solutions.
## Table of Contents
1. [Hardcoding Colors](#1-hardcoding-colors)
2. [Ignoring Referencing System](#2-ignoring-referencing-system)
3. [Modifying Source Files Directly](#3-modifying-source-files-directly)
4. [Direct Value Usage](#4-direct-value-usage)
5. [Missing Width for Centering](#5-missing-width-for-centering)
6. [Font Weight Variables Type Error](#6-font-weight-variables-type-error)
7. [Empty Variables Block](#7-empty-variables-block)
8. [Token References in Numeric Fields](#8-token-references-in-numeric-fields)
9. [Using Emoji Text for Icons](#9-using-emoji-text-for-icons)
10. [Build Script Not Scanning Subdirectories](#10-build-script-not-scanning-subdirectories)
11. [Components at Root Level](#11-components-at-root-level)
12. [Wrap Property Not Working Reliably](#12-wrap-property-not-working-reliably)
13. [Dialog/Frame Height Causing Content Overlap](#13-dialogframe-height-causing-content-overlap)
---
## 1. Hardcoding Colors
**Problem**: Using hex codes directly in components (e.g. `#000000`), making theming impossible.
**Solution**: Always use design token variables.
```css
/* ❌ BAD: Hardcoded colors */
background: #0A0A0B;
/* ✅ GOOD: Use CSS variables */
background: var(--bg-page);
```
---
## 2. Ignoring Referencing System
**Problem**: Treating `ref` elements as normal frames, losing link to main component.
**Solution**: Resolve `ref` by looking up the component ID.
```javascript
// ❌ BAD: Skip ref elements
if (element.type === 'ref') return;
// ✅ GOOD: Resolve ref properly
if (element.type === 'ref') {
const component = findById(element.ref);
return mergeWithDescendants(component, element.descendants);
}
```
---
## 3. Modifying Source Files Directly
**Problem**: Opening and editing partial files in `src/` without building, causing "Missing Component" errors in Pencil.
**Solution**: Always run build script before opening in Pencil.
```bash
# ❌ BAD: Open source files directly
open src/pages/desktop-home.pen
# ✅ GOOD: Build first
python3 scripts/build.py -m
open tPOS.pen
```
---
## 4. Direct Value Usage
**Problem**: Using numeric values for spacing/fontsize, breaking design system consistency.
**Solution**: Use semantic variables (for colors only - see #8).
```json
// ❌ BAD: Direct color values
{
"fill": "#FF5C00"
}
// ✅ GOOD: Use color variables
{
"fill": "$orange-primary"
}
```
---
## 5. Missing Width for Centering
**Problem**: Setting `alignItems: "center"` without explicit width. The frame only takes content width, making horizontal centering ineffective.
**Solution**: Add `width: "fill_container"` to parent frame.
```json
// ❌ BAD: Centering won't work
{
"type": "frame",
"layout": "vertical",
"alignItems": "center",
"children": [...]
}
// ✅ GOOD: Explicit width enables centering
{
"type": "frame",
"width": "fill_container",
"layout": "vertical",
"alignItems": "center",
"children": [...]
}
```
---
## 6. Font Weight Variables Type Error
**Problem**: Defining font-weight variables with `type: "number"` causes Pencil to fail with:
```
Variable 'font-medium' has type 'number' (expected 'string')
```
**Solution**: Font weights MUST use `type: "string"` with quoted values.
```json
// ❌ BAD: Number type
"font-medium": { "type": "number", "value": 500 }
// ✅ GOOD: String type with quoted value
"font-medium": { "type": "string", "value": "500" }
```
**All font weight variables to fix:**
```json
"font-normal": { "type": "string", "value": "400" },
"font-medium": { "type": "string", "value": "500" },
"font-semibold": { "type": "string", "value": "600" },
"font-bold": { "type": "string", "value": "700" }
```
---
## 7. Empty Variables Block
**Problem**: Using token references like `$orange-primary` but leaving `variables: {}` empty. Tokens won't resolve and colors appear wrong.
**Solution**: Always define all used tokens in the file's `variables` block.
```json
// ❌ BAD: Empty variables but using $tokens
{
"children": [{ "fill": "$orange-primary" }],
"variables": {}
}
// ✅ GOOD: Define all used tokens
{
"children": [{ "fill": "$orange-primary" }],
"variables": {
"orange-primary": { "type": "color", "value": "#FF5C00" }
}
}
```
---
## 8. Token References in Numeric Fields
**Problem**: Using token syntax (`$text-sm`) for properties that MUST be numbers: `fontSize`, `fontWeight`, `cornerRadius`, `width`, `height`.
**Solution**: Use actual numbers for numeric properties. Only colors support token refs.
```json
// ❌ BAD: Token in fontSize (won't work)
{
"fontSize": "$text-sm",
"fontWeight": "$font-medium",
"cornerRadius": "$radius-md"
}
// ✅ GOOD: Direct numeric values
{
"fontSize": 14,
"fontWeight": "500",
"cornerRadius": 8
}
// ✅ ALSO OK: Tokens for colors only
{ "fill": "$text-primary" }
```
**Rule of thumb:**
- Color properties (`fill`, `stroke`) → Use `$tokens`
- Numeric properties → Use numbers directly
---
## 9. Using Emoji Text for Icons
**Problem**: Emoji characters in `type: "text"` don't render in Pencil.
**Solution**: Use `type: "icon_font"` with Lucide icons.
```json
// ❌ BAD: Emoji text (won't render)
{ "type": "text", "content": "🍽️", "fontSize": 16 }
// ✅ GOOD: Lucide icon_font
{
"type": "icon_font",
"id": "RestaurantIcon",
"iconFontName": "utensils",
"iconFontFamily": "lucide",
"width": 16,
"height": 16,
"fill": "#FFFFFF"
}
```
**Common Lucide icon mappings:**
| Emoji | Lucide Name | Use Case |
|-------|-------------|----------|
| 🍽️ | `utensils` | Restaurant |
| 🍸 | `wine` | Bar |
| 🎤 | `mic` | Karaoke |
| 💆 | `sparkles` | Spa |
| ✓ | `check` | Success |
| ✗ | `x` | Close/Error |
| + | `plus` | Add |
| - | `minus` | Remove |
| 🔍 | `search` | Search |
| ⚙️ | `settings` | Settings |
| 👤 | `user` | Profile |
| 🏠 | `home` | Home |
| 💳 | `credit-card` | Payment |
| 🛒 | `shopping-cart` | Cart |
---
## 10. Build Script Not Scanning Subdirectories
**Problem**: Using `glob('*.pen')` only scans top-level files, missing subdirectories like `organisms/vertical-specific/`.
**Solution**: Use `rglob('*.pen')` to scan recursively.
```python
# ❌ BAD: Only top-level files
for pen_file in dir_path.glob('*.pen'):
# ✅ GOOD: Include subdirectories
for pen_file in dir_path.rglob('*.pen'):
```
**In `scripts/build.py`:**
```python
# Line ~367
for pen_file in sorted(dir_path.rglob('*.pen')):
```
---
## 11. Components at Root Level
**Problem**: Placing multiple reusable components at root `children[]` causes them to stack/overlap incorrectly in Pencil.
**Solution**: Wrap in a Showcase frame with layout.
```json
// ❌ BAD: Components at root level (will overlap)
{
"children": [
{ "name": "Component/A", "reusable": true },
{ "name": "Component/B", "reusable": true }
]
}
// ✅ GOOD: Wrapped in Showcase frame
{
"children": [
{
"type": "frame",
"name": "Component Showcase",
"width": 1200,
"fill": "$bg-page",
"layout": "vertical",
"gap": 40,
"padding": 40,
"children": [
{ "name": "Component/A", "reusable": true },
{ "name": "Component/B", "reusable": true }
]
}
]
}
```
---
## 12. Wrap Property Not Working Reliably
**Problem**: Setting `wrap: true` on a frame expects children to wrap to next row when exceeding container width. However, Pencil's wrap behavior is unreliable and often causes overflow instead of wrapping.
**Symptom**: Elements overflow beyond container boundary instead of wrapping.
**Solution**: Use manual rows with explicit horizontal layout instead of relying on wrap.
```json
// ❌ BAD: wrap không hoạt động như mong đợi
{
"type": "frame",
"id": "TableGrid",
"width": "fill_container",
"layout": "horizontal",
"wrap": true,
"gap": 8,
"children": [
{"type": "frame", "id": "Table1", "width": 72, "height": 72},
{"type": "frame", "id": "Table2", "width": 72, "height": 72},
{"type": "frame", "id": "Table3", "width": 72, "height": 72},
{"type": "frame", "id": "Table4", "width": 72, "height": 72},
{"type": "frame", "id": "Table5", "width": 72, "height": 72},
{"type": "frame", "id": "Table6", "width": 72, "height": 72}
]
}
// ✅ GOOD: Manual rows - chắc chắn layout đúng
{
"type": "frame",
"id": "TableGrid",
"width": "fill_container",
"layout": "vertical",
"gap": 8,
"clip": true,
"children": [
{
"type": "frame",
"id": "Row1",
"width": "fill_container",
"gap": 8,
"children": [
{"type": "frame", "id": "Table1", "width": 72, "height": 72},
{"type": "frame", "id": "Table2", "width": 72, "height": 72},
{"type": "frame", "id": "Table3", "width": 72, "height": 72}
]
},
{
"type": "frame",
"id": "Row2",
"width": "fill_container",
"gap": 8,
"children": [
{"type": "frame", "id": "Table4", "width": 72, "height": 72},
{"type": "frame", "id": "Table5", "width": 72, "height": 72},
{"type": "frame", "id": "Table6", "width": 72, "height": 72}
]
}
]
}
```
**Tip**: Khi cần grid layout trong container có chiều rộng cố định:
- Dùng `layout: vertical` cho container chính
- Tạo các row con với `layout: horizontal` (mặc định)
- Thêm `clip: true` để tránh overflow
---
## Quick Checklist / Checklist Nhanh
Before committing `.pen` files:
- [ ] All `font-*` variables use `type: "string"`
- [ ] `variables` block contains all used tokens
- [ ] No `$tokens` in numeric fields (fontSize, cornerRadius, etc.)
- [ ] Icons use `icon_font` not emoji text
- [ ] Build script uses `rglob()` for subdirectories
- [ ] Components wrapped in layout frames, not at root level
- [ ] **Avoid `wrap: true` - use manual rows instead**
- [ ] **Check dialog height vs content height (avoid footer overlap)**
- [ ] Run `jq empty file.pen` to validate JSON syntax
- [ ] Run `python3 scripts/build.py --library` before testing
---
## 13. Dialog/Frame Height Causing Content Overlap
**Problem**: Footer buttons or bottom sections overlap with content above because the dialog/frame `height` is too small to contain all children. This is especially common in dialogs with many form fields.
**Symptoms**:
- Footer buttons covering input fields or cards
- Bottom section text cut off or hidden
- Content appears "squeezed" at the bottom
**Root causes**:
1. **Fixed height too small**: Dialog has `height: 480` but content requires 600px
2. **Footer outside parent frame**: Footer is a sibling at root level instead of child of dialog
3. **Content expands but container doesn't**: Using `height: "fill_container"` on content but parent has fixed small height
**Solution 1: Calculate correct height**
```json
// ❌ BAD: Height không đủ cho content
{
"type": "frame",
"id": "CustomerEditDialog",
"height": 480, // Quá nhỏ!
"layout": "vertical",
"children": [
{ "id": "Header", "height": 80 }, // 80px
{ "id": "Content", "height": "fill" }, // 6 fields × 70px = 420px
{ "id": "Footer", "height": 84 } // 84px
// Total: 80 + 420 + 84 = 584px (vượt 480px!)
]
}
// ✅ GOOD: Height đủ chứa tất cả content
{
"type": "frame",
"id": "CustomerEditDialog",
"height": 660, // Đủ không gian
"layout": "vertical",
"children": [...]
}
```
**Height calculation formula:**
```
Total Height = Header + Content + Footer + Padding
Where:
- Header: ~80-100px (icon + title)
- Content: (field count × field height) + ((field count - 1) × gap)
- Footer: ~84-100px (buttons + padding)
- Padding: header/content/footer padding combined
Example (6 fields):
- Header: 80px
- Content: 6 × 70 + 5 × 18 = 420 + 90 = 510px
- Footer: 84px
- Padding: ~40px
- Total: 80 + 510 + 84 + 40 = 714px → Use 720-760px
```
**Solution 2: Ensure footer is INSIDE dialog frame**
```json
// ❌ BAD: Footer ở cùng cấp root với Dialog (sẽ float ngoài)
{
"children": [
{
"type": "frame",
"id": "CustomerAddDialog",
"height": 600,
"children": [
{ "id": "Header" },
{ "id": "Content" }
] // Footer thiếu!
},
{
"type": "frame",
"id": "AddFooter", // ← NẰM NGOÀI dialog!
"y": 600, // ← Absolute positioning = BAD
"children": [...]
}
]
}
// ✅ GOOD: Footer là child của Dialog
{
"children": [
{
"type": "frame",
"id": "CustomerAddDialog",
"height": 680,
"layout": "vertical",
"children": [
{ "id": "Header" },
{ "id": "Content", "height": "fill_container" },
{ "id": "AddFooter" } // ← BÊN TRONG dialog!
]
}
]
}
```
**Quick checks khi tạo dialog:**
| Check | How to verify |
|-------|---------------|
| Footer inside dialog? | Footer phải là child cuối cùng của dialog frame |
| No absolute y position? | Footer không có `y: xxx` |
| Height đủ? | Height ≥ Header + Content + Footer + Gaps |
| Using vertical layout? | Dialog có `layout: "vertical"` |
**Common dialog sizes reference:**
| Dialog type | Recommended height |
|-------------|-------------------|
| Simple confirm (title + 2 buttons) | 200-250px |
| Form 3 fields | 400-450px |
| Form 5 fields | 550-620px |
| Form 6+ fields | 680-760px |
| Complex with cards/lists | 700-800px |
---
## Quick Checklist / Checklist Nhanh
Before committing `.pen` files:
- [ ] All `font-*` variables use `type: "string"`
- [ ] `variables` block contains all used tokens
- [ ] No `$tokens` in numeric fields (fontSize, cornerRadius, etc.)
- [ ] Icons use `icon_font` not emoji text
- [ ] Build script uses `rglob()` for subdirectories
- [ ] Components wrapped in layout frames, not at root level
- [ ] **Avoid `wrap: true` - use manual rows instead**
- [ ] **Dialog height accommodates all children (header + content + footer)**
- [ ] **Footer is INSIDE dialog frame, not at root level**
- [ ] Run `jq empty file.pen` to validate JSON syntax
- [ ] Run `python3 scripts/build.py --library` before testing