refactor: remove obsolete scripts tooling
This commit is contained in:
2
.github/HOOKS.md
vendored
2
.github/HOOKS.md
vendored
@@ -49,7 +49,7 @@ If the remote CodeScene scores are better than the current thresholds, the hook
|
||||
|
||||
## Legacy Files
|
||||
|
||||
The legacy `pre-commit` and `post-commit` files under `.github/hooks/` are archival only. Do not copy them into `.git/hooks`; use Husky and `.husky/` instead. `install-hooks.sh` remains as a reinstall helper that runs Husky.
|
||||
The legacy `pre-commit` file under `.github/hooks/` is archival only. Do not copy it into `.git/hooks`; use Husky and `.husky/` instead. The old design `post-commit` auto-implementation hook was removed because it depended on obsolete one-off scripts. `install-hooks.sh` remains as a reinstall helper that runs Husky.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
|
||||
72
.github/hooks/post-commit
vendored
72
.github/hooks/post-commit
vendored
@@ -1,72 +0,0 @@
|
||||
#!/bin/bash
|
||||
# Post-commit hook: Auto-implement design changes via Claude Code
|
||||
# Copy to .git/hooks/post-commit and make executable
|
||||
|
||||
set -e
|
||||
|
||||
# Check if ui-design.pen was modified in this commit
|
||||
if ! git diff --name-only HEAD~1 HEAD | grep -q "ui-design.pen"; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "🎨 Design file changed - analyzing..."
|
||||
|
||||
# Run analyzer
|
||||
ANALYZER_OUTPUT=$(node scripts/design-diff-analyzer.js --json 2>&1)
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "✅ No implementation tasks needed (content-only changes)"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Extract tasks from JSON
|
||||
TASKS_JSON=$(echo "$ANALYZER_OUTPUT" | sed -n '/---JSON---/,$ p' | tail -n +2)
|
||||
|
||||
if [ -z "$TASKS_JSON" ]; then
|
||||
echo "✅ No significant design changes"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "$ANALYZER_OUTPUT" | sed '/---JSON---/,$ d'
|
||||
|
||||
# Generate Claude Code prompt
|
||||
PROMPT=$(cat <<EOF
|
||||
The design file ui-design.pen was just updated. Analyze the changes and implement them.
|
||||
|
||||
Design changes detected:
|
||||
$ANALYZER_OUTPUT
|
||||
|
||||
Tasks:
|
||||
1. Review the git diff of ui-design.pen (HEAD vs HEAD~1)
|
||||
2. Identify what changed (colors, typography, spacing, layout)
|
||||
3. Update the corresponding code:
|
||||
- Colors/Typography/Spacing → update src/theme.json
|
||||
- Layout changes → update React components
|
||||
- New elements → create new components
|
||||
4. Test visually in Chrome (pnpm dev, open localhost:5173)
|
||||
5. Run tests: pnpm test
|
||||
6. Commit changes with descriptive message referencing the design update
|
||||
|
||||
When completely finished, run:
|
||||
openclaw system event --text "✅ Design changes implemented and tested" --mode now
|
||||
EOF
|
||||
)
|
||||
|
||||
echo ""
|
||||
echo "🚀 Spawning Claude Code to implement changes..."
|
||||
echo ""
|
||||
|
||||
# Spawn Claude Code via OpenClaw
|
||||
openclaw sessions spawn \
|
||||
--task "$PROMPT" \
|
||||
--label "design-auto-implement" \
|
||||
--cleanup delete \
|
||||
--timeout-seconds 600 \
|
||||
--agent-id main
|
||||
|
||||
echo ""
|
||||
echo "✅ Claude Code spawned - you'll be notified when implementation is complete"
|
||||
echo ""
|
||||
|
||||
exit 0
|
||||
@@ -1,359 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Design Diff Analyzer
|
||||
* Analyzes changes to ui-design.pen and generates implementation tasks for Claude Code
|
||||
*/
|
||||
|
||||
import { execSync } from 'child_process';
|
||||
import fs from 'fs';
|
||||
|
||||
function getDiff() {
|
||||
try {
|
||||
return execSync('git diff HEAD~1 ui-design.pen', { encoding: 'utf8' });
|
||||
} catch (e) {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
function analyzeDiff(diff) {
|
||||
const changes = {
|
||||
colors: [],
|
||||
typography: [],
|
||||
spacing: [],
|
||||
layout: [],
|
||||
content: []
|
||||
};
|
||||
|
||||
const lines = diff.split('\n');
|
||||
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const line = lines[i];
|
||||
|
||||
// Color changes
|
||||
if (line.includes('"fill":') || line.includes('"backgroundColor":')) {
|
||||
const oldMatch = lines[i-1]?.match(/"(fill|backgroundColor)":\s*"([^"]+)"/);
|
||||
const newMatch = line.match(/"(fill|backgroundColor)":\s*"([^"]+)"/);
|
||||
|
||||
if (oldMatch && newMatch && oldMatch[2] !== newMatch[2]) {
|
||||
changes.colors.push({
|
||||
type: oldMatch[1],
|
||||
from: oldMatch[2],
|
||||
to: newMatch[2]
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Font size changes
|
||||
if (line.includes('"fontSize":')) {
|
||||
const oldMatch = lines[i-1]?.match(/"fontSize":\s*(\d+)/);
|
||||
const newMatch = line.match(/"fontSize":\s*(\d+)/);
|
||||
|
||||
if (oldMatch && newMatch && oldMatch[1] !== newMatch[1]) {
|
||||
changes.typography.push({
|
||||
type: 'fontSize',
|
||||
from: parseInt(oldMatch[1]),
|
||||
to: parseInt(newMatch[1])
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Spacing/padding changes
|
||||
if (line.includes('"padding":') || line.includes('"margin":') || line.includes('"gap":')) {
|
||||
const oldMatch = lines[i-1]?.match(/"(padding|margin|gap)":\s*(\d+)/);
|
||||
const newMatch = line.match(/"(padding|margin|gap)":\s*(\d+)/);
|
||||
|
||||
if (oldMatch && newMatch && oldMatch[2] !== newMatch[2]) {
|
||||
changes.spacing.push({
|
||||
type: oldMatch[1],
|
||||
from: parseInt(oldMatch[2]),
|
||||
to: parseInt(newMatch[2])
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Content changes (text only - no implementation needed)
|
||||
if (line.includes('"content":')) {
|
||||
const oldMatch = lines[i-1]?.match(/"content":\s*"([^"]+)"/);
|
||||
const newMatch = line.match(/"content":\s*"([^"]+)"/);
|
||||
|
||||
if (oldMatch && newMatch && oldMatch[1] !== newMatch[1]) {
|
||||
changes.content.push({
|
||||
from: oldMatch[1],
|
||||
to: newMatch[1]
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Layout changes - new components added
|
||||
if (line.startsWith('+') && line.includes('"name":')) {
|
||||
// Look back for "type": "frame" in recent lines
|
||||
const frameTypeMatch = lines.slice(Math.max(0, i-5), i+1).find(l =>
|
||||
l.startsWith('+') && l.includes('"type": "frame"')
|
||||
);
|
||||
|
||||
if (frameTypeMatch) {
|
||||
const nameMatch = line.match(/"name":\s*"([^"]+)"/);
|
||||
if (nameMatch) {
|
||||
changes.layout.push({
|
||||
action: 'added',
|
||||
component: nameMatch[1],
|
||||
type: 'frame'
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Layout changes - components removed
|
||||
if (line.startsWith('-') && line.includes('"name":')) {
|
||||
const frameTypeMatch = lines.slice(Math.max(0, i-5), i+1).find(l =>
|
||||
l.startsWith('-') && l.includes('"type": "frame"')
|
||||
);
|
||||
|
||||
if (frameTypeMatch) {
|
||||
const nameMatch = line.match(/"name":\s*"([^"]+)"/);
|
||||
if (nameMatch) {
|
||||
changes.layout.push({
|
||||
action: 'removed',
|
||||
component: nameMatch[1],
|
||||
type: 'frame'
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Height changes (structural)
|
||||
if (line.includes('"height":')) {
|
||||
const oldMatch = lines[i-1]?.match(/"height":\s*(\d+)/);
|
||||
const newMatch = line.match(/"height":\s*(\d+)/);
|
||||
|
||||
if (oldMatch && newMatch && oldMatch[1] !== newMatch[1]) {
|
||||
changes.layout.push({
|
||||
action: 'resized',
|
||||
property: 'height',
|
||||
from: parseInt(oldMatch[1]),
|
||||
to: parseInt(newMatch[1])
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return changes;
|
||||
}
|
||||
|
||||
function analyzeIntent(changes) {
|
||||
const intent = {
|
||||
patterns: [],
|
||||
systems: [],
|
||||
recommendations: []
|
||||
};
|
||||
|
||||
// Detect color-coding system for types
|
||||
const accentColorIntro = changes.colors.filter(c =>
|
||||
c.to.includes('accent-') && !c.from.includes('accent-')
|
||||
);
|
||||
|
||||
if (accentColorIntro.length >= 4) {
|
||||
const colors = [...new Set(accentColorIntro.map(c => c.to))];
|
||||
intent.systems.push({
|
||||
type: 'color-coding',
|
||||
description: 'Type-based color system',
|
||||
colors: colors,
|
||||
explanation: 'Each entity type (Project, Procedure, Person, Topic) has its own accent color. This color should be used consistently for: icons, active states, badges, and any UI element representing that type.',
|
||||
critical: '⚠️ IMPORTANT: Active states must use the color **dynamically** based on the note type, not hardcoded. Example: if a Project is selected, use accent-red; if a Procedure is selected, use accent-purple.'
|
||||
});
|
||||
}
|
||||
|
||||
// Detect contrast improvements
|
||||
const contrastChanges = changes.colors.filter(c =>
|
||||
c.from === '$--muted-foreground' && c.to === '$--foreground'
|
||||
);
|
||||
|
||||
if (contrastChanges.length > 3) {
|
||||
intent.patterns.push({
|
||||
type: 'contrast-improvement',
|
||||
description: 'Increased text contrast for better readability',
|
||||
count: contrastChanges.length
|
||||
});
|
||||
}
|
||||
|
||||
// Detect spacing improvements
|
||||
if (changes.spacing.length > 0) {
|
||||
const avgIncrease = changes.spacing.reduce((sum, s) => sum + (s.to - s.from), 0) / changes.spacing.length;
|
||||
if (avgIncrease > 0) {
|
||||
intent.patterns.push({
|
||||
type: 'spacing-increase',
|
||||
description: 'Increased spacing for less cluttered UI',
|
||||
avgIncrease: Math.round(avgIncrease)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return intent;
|
||||
}
|
||||
|
||||
function generateTasks(changes, intent) {
|
||||
const tasks = [];
|
||||
|
||||
// Color changes → update theme.json or CSS variables
|
||||
if (changes.colors.length > 0) {
|
||||
const colorList = changes.colors.map(c =>
|
||||
` - ${c.type}: ${c.from} → ${c.to}`
|
||||
).join('\n');
|
||||
|
||||
let details = `Color changes detected:\n${colorList}\n\nUpdate src/theme.json or CSS variables to match the design.`;
|
||||
|
||||
// Add intent explanation if color-coding system detected
|
||||
const colorSystem = intent.systems.find(s => s.type === 'color-coding');
|
||||
if (colorSystem) {
|
||||
details += `\n\n${colorSystem.explanation}\n\n${colorSystem.critical}`;
|
||||
}
|
||||
|
||||
tasks.push({
|
||||
priority: 'high',
|
||||
description: 'Update color palette',
|
||||
details: details
|
||||
});
|
||||
}
|
||||
|
||||
// Typography changes → update theme.json
|
||||
if (changes.typography.length > 0) {
|
||||
const typoList = changes.typography.map(t =>
|
||||
` - ${t.type}: ${t.from}px → ${t.to}px`
|
||||
).join('\n');
|
||||
|
||||
tasks.push({
|
||||
priority: 'medium',
|
||||
description: 'Update typography',
|
||||
details: `Typography changes detected:\n${typoList}\n\nUpdate src/theme.json typography settings.`
|
||||
});
|
||||
}
|
||||
|
||||
// Spacing changes → update theme.json
|
||||
if (changes.spacing.length > 0) {
|
||||
const spacingList = changes.spacing.map(s =>
|
||||
` - ${s.type}: ${s.from}px → ${s.to}px`
|
||||
).join('\n');
|
||||
|
||||
tasks.push({
|
||||
priority: 'medium',
|
||||
description: 'Update spacing',
|
||||
details: `Spacing changes detected:\n${spacingList}\n\nUpdate src/theme.json spacing values.`
|
||||
});
|
||||
}
|
||||
|
||||
// Layout changes → structural modifications
|
||||
if (changes.layout.length > 0) {
|
||||
const added = changes.layout.filter(l => l.action === 'added');
|
||||
const removed = changes.layout.filter(l => l.action === 'removed');
|
||||
const resized = changes.layout.filter(l => l.action === 'resized');
|
||||
|
||||
let layoutDetails = 'Layout changes detected:\n\n';
|
||||
|
||||
if (added.length > 0) {
|
||||
layoutDetails += 'Components added:\n';
|
||||
added.forEach(c => layoutDetails += ` + ${c.component} (${c.type})\n`);
|
||||
layoutDetails += '\n';
|
||||
}
|
||||
|
||||
if (removed.length > 0) {
|
||||
layoutDetails += 'Components removed:\n';
|
||||
removed.forEach(c => layoutDetails += ` - ${c.component} (${c.type})\n`);
|
||||
layoutDetails += '\n';
|
||||
}
|
||||
|
||||
if (resized.length > 0) {
|
||||
layoutDetails += 'Components resized:\n';
|
||||
resized.forEach(c => layoutDetails += ` • ${c.property}: ${c.from}px → ${c.to}px\n`);
|
||||
layoutDetails += '\n';
|
||||
}
|
||||
|
||||
layoutDetails += 'Review ui-design.pen diff and update components accordingly.';
|
||||
|
||||
tasks.push({
|
||||
priority: 'high',
|
||||
description: 'Update layout structure',
|
||||
details: layoutDetails
|
||||
});
|
||||
}
|
||||
|
||||
return tasks;
|
||||
}
|
||||
|
||||
function formatOutput(tasks, changes, intent) {
|
||||
if (tasks.length === 0 && changes.content.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let output = '🎨 Design changes detected:\n\n';
|
||||
|
||||
// Add design intent analysis
|
||||
if (intent.systems.length > 0 || intent.patterns.length > 0) {
|
||||
output += '🧠 Design Intent Analysis:\n\n';
|
||||
|
||||
intent.systems.forEach(system => {
|
||||
output += `📐 ${system.description}\n`;
|
||||
output += `${system.explanation}\n`;
|
||||
if (system.critical) {
|
||||
output += `\n${system.critical}\n`;
|
||||
}
|
||||
output += '\n';
|
||||
});
|
||||
|
||||
intent.patterns.forEach(pattern => {
|
||||
output += `• ${pattern.description}`;
|
||||
if (pattern.count) output += ` (${pattern.count} changes)`;
|
||||
if (pattern.avgIncrease) output += ` (+${pattern.avgIncrease}px avg)`;
|
||||
output += '\n';
|
||||
});
|
||||
output += '\n';
|
||||
}
|
||||
|
||||
if (tasks.length > 0) {
|
||||
output += '📋 Implementation tasks:\n\n';
|
||||
tasks.forEach((task, i) => {
|
||||
output += `${i + 1}. [${task.priority.toUpperCase()}] ${task.description}\n`;
|
||||
output += `${task.details}\n\n`;
|
||||
});
|
||||
}
|
||||
|
||||
if (changes.content.length > 0) {
|
||||
output += '📝 Content changes (mockup text only, no code changes needed):\n';
|
||||
changes.content.forEach(c => {
|
||||
output += ` - "${c.from}" → "${c.to}"\n`;
|
||||
});
|
||||
output += '\n';
|
||||
}
|
||||
|
||||
return { output, tasks, intent };
|
||||
}
|
||||
|
||||
function main() {
|
||||
const diff = getDiff();
|
||||
|
||||
if (!diff) {
|
||||
console.log('No design changes detected');
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
const changes = analyzeDiff(diff);
|
||||
const intent = analyzeIntent(changes);
|
||||
const tasks = generateTasks(changes, intent);
|
||||
const result = formatOutput(tasks, changes, intent);
|
||||
|
||||
if (!result) {
|
||||
console.log('No significant design changes detected');
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
console.log(result.output);
|
||||
|
||||
// Output JSON for automation
|
||||
if (process.argv.includes('--json')) {
|
||||
console.log('\n---JSON---');
|
||||
console.log(JSON.stringify({ tasks, changes, intent }, null, 2));
|
||||
}
|
||||
|
||||
process.exit(tasks.length > 0 ? 1 : 0); // Exit 1 if there are tasks to implement
|
||||
}
|
||||
|
||||
main();
|
||||
@@ -1,928 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Restructure ui-design.pen into a proper design system.
|
||||
|
||||
Sections (top → bottom):
|
||||
0. Cover — title, TOC
|
||||
1. Foundations — colors, typography, spacing, shadows, heights
|
||||
2. Components — atoms → molecules → organisms (reusable)
|
||||
3. Full Layouts — 6 app variants at 1440×900
|
||||
4. Feature Specs — existing frames reorganized by functional area
|
||||
"""
|
||||
import json
|
||||
import copy
|
||||
import sys
|
||||
import os
|
||||
|
||||
PEN_FILE = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "ui-design.pen")
|
||||
|
||||
def load():
|
||||
with open(PEN_FILE, "r") as f:
|
||||
return json.load(f)
|
||||
|
||||
def save(data):
|
||||
with open(PEN_FILE, "w") as f:
|
||||
json.dump(data, f, separators=(",", ":"))
|
||||
|
||||
# ─── Variable definitions ─────────────────────────────────────────
|
||||
NEW_VARIABLES = {
|
||||
"--spacing-xs": {"type": "number", "value": 4},
|
||||
"--spacing-sm": {"type": "number", "value": 8},
|
||||
"--spacing-md": {"type": "number", "value": 12},
|
||||
"--spacing-lg": {"type": "number", "value": 16},
|
||||
"--spacing-xl": {"type": "number", "value": 24},
|
||||
"--spacing-2xl": {"type": "number", "value": 32},
|
||||
"--spacing-3xl": {"type": "number", "value": 40},
|
||||
"--height-titlebar": {"type": "number", "value": 38},
|
||||
"--height-tabbar": {"type": "number", "value": 45},
|
||||
"--height-breadcrumb": {"type": "number", "value": 45},
|
||||
"--height-statusbar": {"type": "number", "value": 30},
|
||||
"--height-search-bar": {"type": "number", "value": 40},
|
||||
"--height-note-item": {"type": "number", "value": 64},
|
||||
"--height-inspector-header": {"type": "number", "value": 36},
|
||||
"--height-modal-header": {"type": "number", "value": 56},
|
||||
"--height-modal-footer": {"type": "number", "value": 56},
|
||||
"--shadow-sm": {"type": "string", "value": "0 1px 2px rgba(0,0,0,0.05)"},
|
||||
"--shadow-md": {"type": "string", "value": "0 4px 6px rgba(0,0,0,0.07)"},
|
||||
"--shadow-lg": {"type": "string", "value": "0 10px 15px rgba(0,0,0,0.1)"},
|
||||
"--font-mono": {"type": "string", "value": "IBM Plex Mono, monospace"},
|
||||
# Search panel colors (currently hardcoded)
|
||||
"--search-bg": {"type": "color", "value": [
|
||||
{"value": "#FFFFFF"}, {"theme": {"Mode": "Dark"}, "value": "#1E1E2E"}
|
||||
]},
|
||||
"--search-input-bg": {"type": "color", "value": [
|
||||
{"value": "#F0F0EF"}, {"theme": {"Mode": "Dark"}, "value": "#2A2A3C"}
|
||||
]},
|
||||
}
|
||||
|
||||
# ─── Hardcoded color mappings ─────────────────────────────────────
|
||||
# Maps hardcoded hex → variable name for replacement
|
||||
HARDCODED_COLORS = {
|
||||
"#1E1E2E": "$--search-bg",
|
||||
"#1e1e2e": "$--search-bg",
|
||||
"#2A2A3C": "$--search-input-bg",
|
||||
"#2a2a3c": "$--search-input-bg",
|
||||
"#6b7280": "$--muted-foreground",
|
||||
"#f9fafb": "$--muted",
|
||||
"#f3f4f6": "$--muted",
|
||||
"#ffffff": "$--background",
|
||||
"#000000": "$--black",
|
||||
"#6366f1": "$--primary",
|
||||
}
|
||||
|
||||
def replace_hardcoded_colors(node):
|
||||
"""Recursively replace hardcoded hex colors with variable references."""
|
||||
if isinstance(node, dict):
|
||||
for key, val in list(node.items()):
|
||||
if key == "fill" and isinstance(val, str) and val.startswith("#"):
|
||||
lower = val.lower()
|
||||
if lower in HARDCODED_COLORS:
|
||||
node[key] = HARDCODED_COLORS[lower]
|
||||
elif key == "fill" and isinstance(val, dict) and val.get("type") == "color":
|
||||
color = val.get("color", "")
|
||||
if isinstance(color, str) and color.lower() in HARDCODED_COLORS:
|
||||
val["color"] = HARDCODED_COLORS[color.lower()]
|
||||
elif isinstance(val, (dict, list)):
|
||||
replace_hardcoded_colors(val)
|
||||
elif isinstance(node, list):
|
||||
for item in node:
|
||||
replace_hardcoded_colors(item)
|
||||
|
||||
# ─── Section header helper ────────────────────────────────────────
|
||||
def make_section_header(sec_id, label, x, y):
|
||||
return {
|
||||
"type": "frame", "id": sec_id, "name": label,
|
||||
"x": x, "y": y, "width": 1440, "height": 60,
|
||||
"fill": "$--foreground", "padding": [0, 60],
|
||||
"alignItems": "center", "theme": {"Mode": "Light"},
|
||||
"children": [{
|
||||
"type": "text", "id": f"{sec_id}_lbl",
|
||||
"content": label.upper(),
|
||||
"fill": "$--background", "fontFamily": "Inter",
|
||||
"fontSize": 24, "fontWeight": "700", "letterSpacing": 2
|
||||
}]
|
||||
}
|
||||
|
||||
def make_subsection_label(sub_id, label, x, y):
|
||||
return {
|
||||
"type": "text", "id": sub_id,
|
||||
"content": label,
|
||||
"x": x, "y": y,
|
||||
"fill": "$--muted-foreground", "fontFamily": "Inter",
|
||||
"fontSize": 16, "fontWeight": "600", "letterSpacing": 1,
|
||||
"theme": {"Mode": "Light"}
|
||||
}
|
||||
|
||||
# ─── Cover ────────────────────────────────────────────────────────
|
||||
def make_cover(x, y):
|
||||
return {
|
||||
"type": "frame", "id": "ds_cover", "name": "0 — Cover",
|
||||
"x": x, "y": y, "width": 1440, "height": "fit_content(400)",
|
||||
"fill": "$--background", "layout": "vertical",
|
||||
"gap": 32, "padding": [80, 60],
|
||||
"theme": {"Mode": "Light"},
|
||||
"children": [
|
||||
{"type": "text", "id": "ds_title", "content": "Laputa Design System",
|
||||
"fill": "$--foreground", "fontFamily": "Inter", "fontSize": 48,
|
||||
"fontWeight": "700", "letterSpacing": -1.5},
|
||||
{"type": "text", "id": "ds_subtitle",
|
||||
"content": "Wiki-linked knowledge management for deep thinkers",
|
||||
"fill": "$--muted-foreground", "fontFamily": "Inter", "fontSize": 18, "lineHeight": 1.5},
|
||||
{"type": "rectangle", "id": "ds_div", "width": "fill_container", "height": 1, "fill": "$--border"},
|
||||
{"type": "frame", "id": "ds_toc", "name": "TOC", "layout": "vertical",
|
||||
"width": "fill_container", "gap": 16, "children": [
|
||||
{"type": "text", "id": "ds_toc_t", "content": "Table of Contents",
|
||||
"fill": "$--foreground", "fontFamily": "Inter", "fontSize": 20, "fontWeight": "600"},
|
||||
{"type": "frame", "id": "ds_toc_items", "layout": "vertical",
|
||||
"width": "fill_container", "gap": 8, "children": [
|
||||
{"type": "text", "id": "ds_t1", "fill": "$--primary", "fontFamily": "Inter",
|
||||
"fontSize": 14, "fontWeight": "500",
|
||||
"content": "1. Foundations — Colors, Typography, Spacing, Shadows, Heights"},
|
||||
{"type": "text", "id": "ds_t2", "fill": "$--primary", "fontFamily": "Inter",
|
||||
"fontSize": 14, "fontWeight": "500",
|
||||
"content": "2. Components — Atoms, Molecules, Organisms"},
|
||||
{"type": "text", "id": "ds_t3", "fill": "$--primary", "fontFamily": "Inter",
|
||||
"fontSize": 14, "fontWeight": "500",
|
||||
"content": "3. Full Layouts — 6 app variants at 1440×900"},
|
||||
{"type": "text", "id": "ds_t4", "fill": "$--primary", "fontFamily": "Inter",
|
||||
"fontSize": 14, "fontWeight": "500",
|
||||
"content": "4. Feature Specs — All screens grouped by functional area"},
|
||||
]}
|
||||
]}
|
||||
]
|
||||
}
|
||||
|
||||
# ─── Foundation frames ────────────────────────────────────────────
|
||||
def make_spacing_scale(x, y):
|
||||
rows = []
|
||||
for var, px in [("xs", 4), ("sm", 8), ("md", 12), ("lg", 16), ("xl", 24), ("2xl", 32), ("3xl", 40)]:
|
||||
rid = f"sp_{var}"
|
||||
rows.append({
|
||||
"type": "frame", "id": rid, "layout": "horizontal",
|
||||
"gap": 16, "alignItems": "center", "width": "fill_container",
|
||||
"children": [
|
||||
{"type": "rectangle", "id": f"{rid}_box", "width": px, "height": 24,
|
||||
"fill": "$--primary", "cornerRadius": 2},
|
||||
{"type": "text", "id": f"{rid}_lbl",
|
||||
"content": f"--spacing-{var}: {px}px",
|
||||
"fill": "$--foreground", "fontFamily": "IBM Plex Mono", "fontSize": 13}
|
||||
]
|
||||
})
|
||||
return {
|
||||
"type": "frame", "id": "ds_spacing", "name": "Spacing Scale",
|
||||
"x": x, "y": y, "width": 700,
|
||||
"fill": "$--background", "layout": "vertical",
|
||||
"gap": 24, "padding": 40, "theme": {"Mode": "Light"},
|
||||
"children": [
|
||||
{"type": "text", "id": "ds_sp_title", "content": "Spacing Scale",
|
||||
"fill": "$--foreground", "fontFamily": "Inter", "fontSize": 20, "fontWeight": "600"},
|
||||
{"type": "text", "id": "ds_sp_sub",
|
||||
"content": "Consistent spacing tokens used throughout the app.",
|
||||
"fill": "$--muted-foreground", "fontFamily": "Inter", "fontSize": 13, "lineHeight": 1.5},
|
||||
] + rows
|
||||
}
|
||||
|
||||
def make_heights_ref(x, y):
|
||||
items = [
|
||||
("titlebar", 38, "$--sidebar"), ("tabbar", 45, "$--sidebar"),
|
||||
("breadcrumb", 45, "$--background"), ("statusbar", 30, "$--sidebar"),
|
||||
("modal-header", 56, "$--background"), ("inspector-header", 36, "$--background"),
|
||||
]
|
||||
rows = []
|
||||
for name, px, fill in items:
|
||||
rid = f"ht_{name.replace('-','_')}"
|
||||
rows.append({
|
||||
"type": "frame", "id": rid, "layout": "horizontal",
|
||||
"gap": 16, "alignItems": "center", "width": "fill_container",
|
||||
"children": [
|
||||
{"type": "rectangle", "id": f"{rid}_bar", "width": 120, "height": px,
|
||||
"fill": fill, "cornerRadius": "$--radius-sm",
|
||||
"stroke": {"fill": "$--border", "thickness": 1}},
|
||||
{"type": "text", "id": f"{rid}_lbl",
|
||||
"content": f"--height-{name}: {px}px",
|
||||
"fill": "$--foreground", "fontFamily": "IBM Plex Mono", "fontSize": 13}
|
||||
]
|
||||
})
|
||||
return {
|
||||
"type": "frame", "id": "ds_heights", "name": "Height Variables",
|
||||
"x": x, "y": y, "width": 700,
|
||||
"fill": "$--background", "layout": "vertical",
|
||||
"gap": 24, "padding": 40, "theme": {"Mode": "Light"},
|
||||
"children": [
|
||||
{"type": "text", "id": "ds_ht_title", "content": "Height Variables",
|
||||
"fill": "$--foreground", "fontFamily": "Inter", "fontSize": 20, "fontWeight": "600"},
|
||||
{"type": "text", "id": "ds_ht_sub",
|
||||
"content": "Fixed heights for key layout regions.",
|
||||
"fill": "$--muted-foreground", "fontFamily": "Inter", "fontSize": 13, "lineHeight": 1.5},
|
||||
] + rows
|
||||
}
|
||||
|
||||
def make_shadows_ref(x, y):
|
||||
shadows = [
|
||||
("SM", "0 1px 2px rgba(0,0,0,0.05)", {"type": "shadow", "offset": {"x": 0, "y": 1}, "blur": 2, "color": "#0000000D"}),
|
||||
("MD", "0 4px 6px rgba(0,0,0,0.07)", {"type": "shadow", "offset": {"x": 0, "y": 4}, "blur": 6, "color": "#00000012"}),
|
||||
("LG", "0 10px 15px rgba(0,0,0,0.1)", {"type": "shadow", "offset": {"x": 0, "y": 10}, "blur": 15, "color": "#0000001A"}),
|
||||
]
|
||||
rows = []
|
||||
for name, desc, effect in shadows:
|
||||
rid = f"sh_{name.lower()}"
|
||||
rows.append({
|
||||
"type": "frame", "id": rid, "layout": "horizontal",
|
||||
"gap": 24, "alignItems": "center", "width": "fill_container",
|
||||
"children": [
|
||||
{"type": "frame", "id": f"{rid}_box", "width": 120, "height": 80,
|
||||
"fill": "$--card", "cornerRadius": "$--radius-lg", "effect": effect},
|
||||
{"type": "frame", "id": f"{rid}_info", "layout": "vertical", "gap": 4, "children": [
|
||||
{"type": "text", "id": f"{rid}_name", "content": f"Shadow {name}",
|
||||
"fill": "$--foreground", "fontFamily": "Inter", "fontSize": 14, "fontWeight": "600"},
|
||||
{"type": "text", "id": f"{rid}_desc", "content": desc,
|
||||
"fill": "$--muted-foreground", "fontFamily": "IBM Plex Mono", "fontSize": 11}
|
||||
]}
|
||||
]
|
||||
})
|
||||
return {
|
||||
"type": "frame", "id": "ds_shadows", "name": "Shadows",
|
||||
"x": x, "y": y, "width": 700,
|
||||
"fill": "$--background", "layout": "vertical",
|
||||
"gap": 32, "padding": 40, "theme": {"Mode": "Light"},
|
||||
"children": [
|
||||
{"type": "text", "id": "ds_sh_title", "content": "Shadows",
|
||||
"fill": "$--foreground", "fontFamily": "Inter", "fontSize": 20, "fontWeight": "600"},
|
||||
{"type": "text", "id": "ds_sh_sub",
|
||||
"content": "Elevation levels for cards, modals, and popovers.",
|
||||
"fill": "$--muted-foreground", "fontFamily": "Inter", "fontSize": 13, "lineHeight": 1.5},
|
||||
] + rows
|
||||
}
|
||||
|
||||
# ─── Reusable Components ─────────────────────────────────────────
|
||||
|
||||
def make_component_status_dot(x, y):
|
||||
return {
|
||||
"type": "frame", "id": "comp_StatusDot", "name": "component/StatusDot",
|
||||
"reusable": True, "x": x, "y": y,
|
||||
"width": "fit_content(6)", "height": "fit_content(6)",
|
||||
"theme": {"Mode": "Light"},
|
||||
"children": [
|
||||
{"type": "ellipse", "id": "StatusDot_dot", "width": 6, "height": 6,
|
||||
"fill": "$--accent-orange"}
|
||||
]
|
||||
}
|
||||
|
||||
def make_component_separator(x, y):
|
||||
return {
|
||||
"type": "frame", "id": "comp_Separator", "name": "component/Separator",
|
||||
"reusable": True, "x": x, "y": y,
|
||||
"width": "fill_container(200)", "height": 1,
|
||||
"fill": "$--border",
|
||||
"theme": {"Mode": "Light"},
|
||||
"children": []
|
||||
}
|
||||
|
||||
def make_component_badge(x, y):
|
||||
"""Colored pill badge — used for type badges, status badges, tags."""
|
||||
return {
|
||||
"type": "frame", "id": "comp_Badge", "name": "component/Badge",
|
||||
"reusable": True, "x": x, "y": y,
|
||||
"cornerRadius": 12, "fill": "$--accent-blue-light",
|
||||
"padding": [2, 8],
|
||||
"theme": {"Mode": "Light"},
|
||||
"children": [
|
||||
{"type": "text", "id": "Badge_label", "content": "Badge",
|
||||
"fill": "$--accent-blue", "fontFamily": "Inter", "fontSize": 12, "fontWeight": "500"}
|
||||
]
|
||||
}
|
||||
|
||||
def make_component_button_primary(x, y):
|
||||
return {
|
||||
"type": "frame", "id": "comp_ButtonPrimary", "name": "component/Button/Primary",
|
||||
"reusable": True, "x": x, "y": y,
|
||||
"cornerRadius": "$--radius-md", "fill": "$--primary",
|
||||
"padding": [8, 16], "gap": 6, "alignItems": "center",
|
||||
"justifyContent": "center", "height": 36,
|
||||
"theme": {"Mode": "Light"},
|
||||
"children": [
|
||||
{"type": "text", "id": "BtnPri_label", "content": "Button",
|
||||
"fill": "$--primary-foreground", "fontFamily": "Inter", "fontSize": 13, "fontWeight": "500"}
|
||||
]
|
||||
}
|
||||
|
||||
def make_component_button_secondary(x, y):
|
||||
return {
|
||||
"type": "frame", "id": "comp_ButtonSecondary", "name": "component/Button/Secondary",
|
||||
"reusable": True, "x": x, "y": y,
|
||||
"cornerRadius": "$--radius-md", "fill": "$--secondary",
|
||||
"padding": [8, 16], "gap": 6, "alignItems": "center",
|
||||
"justifyContent": "center", "height": 36,
|
||||
"theme": {"Mode": "Light"},
|
||||
"children": [
|
||||
{"type": "text", "id": "BtnSec_label", "content": "Button",
|
||||
"fill": "$--secondary-foreground", "fontFamily": "Inter", "fontSize": 13, "fontWeight": "500"}
|
||||
]
|
||||
}
|
||||
|
||||
def make_component_button_ghost(x, y):
|
||||
return {
|
||||
"type": "frame", "id": "comp_ButtonGhost", "name": "component/Button/Ghost",
|
||||
"reusable": True, "x": x, "y": y,
|
||||
"cornerRadius": "$--radius-md",
|
||||
"padding": [8, 16], "gap": 6, "alignItems": "center",
|
||||
"justifyContent": "center", "height": 36,
|
||||
"theme": {"Mode": "Light"},
|
||||
"children": [
|
||||
{"type": "text", "id": "BtnGho_label", "content": "Button",
|
||||
"fill": "$--foreground", "fontFamily": "Inter", "fontSize": 13, "fontWeight": "500"}
|
||||
]
|
||||
}
|
||||
|
||||
def make_component_input(x, y):
|
||||
return {
|
||||
"type": "frame", "id": "comp_Input", "name": "component/Input",
|
||||
"reusable": True, "x": x, "y": y,
|
||||
"width": 240, "height": 36,
|
||||
"cornerRadius": "$--radius-md", "fill": "$--background",
|
||||
"stroke": {"fill": "$--input", "thickness": 1, "align": "inside"},
|
||||
"padding": [0, 12], "alignItems": "center",
|
||||
"theme": {"Mode": "Light"},
|
||||
"children": [
|
||||
{"type": "text", "id": "Input_placeholder", "content": "Placeholder...",
|
||||
"fill": "$--muted-foreground", "fontFamily": "Inter", "fontSize": 13}
|
||||
]
|
||||
}
|
||||
|
||||
def make_component_icon_button(x, y):
|
||||
return {
|
||||
"type": "frame", "id": "comp_IconButton", "name": "component/IconButton",
|
||||
"reusable": True, "x": x, "y": y,
|
||||
"width": 28, "height": 28,
|
||||
"cornerRadius": "$--radius-sm",
|
||||
"alignItems": "center", "justifyContent": "center",
|
||||
"theme": {"Mode": "Light"},
|
||||
"children": [
|
||||
{"type": "icon_font", "id": "IconBtn_icon", "width": 16, "height": 16,
|
||||
"fill": "$--muted-foreground", "iconFontFamily": "lucide", "iconFontName": "plus"}
|
||||
]
|
||||
}
|
||||
|
||||
# ─── Molecule Components ──────────────────────────────────────────
|
||||
|
||||
def make_component_inspector_header(x, y):
|
||||
return {
|
||||
"type": "frame", "id": "comp_InspectorHeader", "name": "component/InspectorHeader",
|
||||
"reusable": True, "x": x, "y": y,
|
||||
"width": "fill_container(320)", "height": "$--height-inspector-header",
|
||||
"justifyContent": "space_between", "alignItems": "center",
|
||||
"padding": [0, 12],
|
||||
"theme": {"Mode": "Light"},
|
||||
"children": [
|
||||
{"type": "frame", "id": "IH_left", "name": "leftGroup",
|
||||
"gap": 6, "alignItems": "center", "children": [
|
||||
{"type": "icon_font", "id": "IH_icon", "width": 16, "height": 16,
|
||||
"fill": "$--muted-foreground", "iconFontFamily": "phosphor",
|
||||
"iconFontName": "sliders-horizontal"},
|
||||
{"type": "text", "id": "IH_title", "content": "Properties",
|
||||
"fill": "$--muted-foreground", "fontFamily": "Inter",
|
||||
"fontSize": 13, "fontWeight": "600"}
|
||||
]},
|
||||
{"type": "icon_font", "id": "IH_close", "width": 16, "height": 16,
|
||||
"fill": "$--muted-foreground", "iconFontFamily": "phosphor", "iconFontName": "x"}
|
||||
]
|
||||
}
|
||||
|
||||
def make_component_note_list_item(x, y):
|
||||
return {
|
||||
"type": "frame", "id": "comp_NoteListItem", "name": "component/NoteListItem",
|
||||
"reusable": True, "x": x, "y": y,
|
||||
"width": "fill_container(340)", "layout": "vertical",
|
||||
"gap": 4, "padding": [14, 16],
|
||||
"stroke": {"align": "inside", "fill": "$--border", "thickness": {"bottom": 1}},
|
||||
"theme": {"Mode": "Light"},
|
||||
"children": [
|
||||
{"type": "frame", "id": "NLI_titleRow", "name": "titleRow",
|
||||
"width": "fill_container", "justifyContent": "space_between", "alignItems": "center",
|
||||
"children": [
|
||||
{"type": "frame", "id": "NLI_titleGroup", "gap": 6, "alignItems": "center",
|
||||
"children": [
|
||||
{"type": "text", "id": "NLI_title", "content": "Note Title",
|
||||
"fill": "$--foreground", "fontFamily": "Inter", "fontSize": 13, "fontWeight": "500"}
|
||||
]},
|
||||
{"type": "text", "id": "NLI_time", "content": "2m ago",
|
||||
"fill": "$--muted-foreground", "fontFamily": "Inter", "fontSize": 11}
|
||||
]},
|
||||
{"type": "text", "id": "NLI_snippet",
|
||||
"content": "Preview text of the note content...",
|
||||
"fill": "$--muted-foreground", "fontFamily": "Inter",
|
||||
"fontSize": 12, "lineHeight": 1.5,
|
||||
"textGrowth": "fixed-width", "width": "fill_container"},
|
||||
]
|
||||
}
|
||||
|
||||
def make_component_tab_active(x, y):
|
||||
return {
|
||||
"type": "frame", "id": "comp_TabActive", "name": "component/Tab/Active",
|
||||
"reusable": True, "x": x, "y": y,
|
||||
"height": "fill_container(45)", "gap": 6,
|
||||
"fill": "$--background", "alignItems": "center",
|
||||
"padding": [0, 14],
|
||||
"stroke": {"align": "inside", "fill": "$--border", "thickness": {"right": 1}},
|
||||
"theme": {"Mode": "Light"},
|
||||
"children": [
|
||||
{"type": "text", "id": "TabA_label", "content": "Tab Title",
|
||||
"fill": "$--foreground", "fontFamily": "Inter", "fontSize": 12, "fontWeight": "500"},
|
||||
{"type": "icon_font", "id": "TabA_close", "width": 14, "height": 14,
|
||||
"fill": "$--muted-foreground", "iconFontFamily": "lucide", "iconFontName": "x"}
|
||||
]
|
||||
}
|
||||
|
||||
def make_component_tab_inactive(x, y):
|
||||
return {
|
||||
"type": "frame", "id": "comp_TabInactive", "name": "component/Tab/Inactive",
|
||||
"reusable": True, "x": x, "y": y,
|
||||
"height": "fill_container(45)", "gap": 6,
|
||||
"alignItems": "center", "padding": [0, 14],
|
||||
"stroke": {"align": "inside", "fill": "$--sidebar-border",
|
||||
"thickness": {"bottom": 1, "right": 1}},
|
||||
"theme": {"Mode": "Light"},
|
||||
"children": [
|
||||
{"type": "text", "id": "TabI_label", "content": "Tab Title",
|
||||
"fill": "$--muted-foreground", "fontFamily": "Inter", "fontSize": 12},
|
||||
{"type": "icon_font", "id": "TabI_close", "width": 14, "height": 14,
|
||||
"fill": "$--muted-foreground", "iconFontFamily": "lucide", "iconFontName": "x",
|
||||
"opacity": 0}
|
||||
]
|
||||
}
|
||||
|
||||
def make_component_property_row(x, y):
|
||||
return {
|
||||
"type": "frame", "id": "comp_PropertyRow", "name": "component/PropertyRow",
|
||||
"reusable": True, "x": x, "y": y,
|
||||
"width": "fill_container(300)", "alignItems": "center",
|
||||
"cornerRadius": 4, "padding": [4, 6],
|
||||
"theme": {"Mode": "Light"},
|
||||
"children": [
|
||||
{"type": "text", "id": "PR_label", "content": "LABEL",
|
||||
"fill": "$--muted-foreground", "fontFamily": "IBM Plex Mono",
|
||||
"fontSize": 10, "fontWeight": "500", "letterSpacing": 1.2},
|
||||
{"type": "text", "id": "PR_value", "content": "Value",
|
||||
"fill": "$--foreground", "fontFamily": "Inter", "fontSize": 13}
|
||||
]
|
||||
}
|
||||
|
||||
def make_component_relationship_pill(x, y):
|
||||
return {
|
||||
"type": "frame", "id": "comp_RelationshipPill", "name": "component/RelationshipPill",
|
||||
"reusable": True, "x": x, "y": y,
|
||||
"width": "fill_container(280)", "cornerRadius": 6,
|
||||
"fill": "$--accent-blue-light", "gap": 6,
|
||||
"justifyContent": "space_between", "alignItems": "center",
|
||||
"padding": [6, 10],
|
||||
"theme": {"Mode": "Light"},
|
||||
"children": [
|
||||
{"type": "text", "id": "RP_title", "content": "Related Note",
|
||||
"fill": "$--accent-blue", "fontFamily": "Inter", "fontSize": 12, "fontWeight": "500"},
|
||||
{"type": "icon_font", "id": "RP_icon", "width": 14, "height": 14,
|
||||
"fill": "$--accent-blue", "iconFontFamily": "phosphor", "iconFontName": "wrench",
|
||||
"weight": 700}
|
||||
]
|
||||
}
|
||||
|
||||
def make_component_sidebar_filter_item(x, y):
|
||||
return {
|
||||
"type": "frame", "id": "comp_SidebarFilterItem", "name": "component/SidebarFilterItem",
|
||||
"reusable": True, "x": x, "y": y,
|
||||
"width": "fill_container(250)", "cornerRadius": 6,
|
||||
"gap": 8, "alignItems": "center", "padding": [6, 16],
|
||||
"theme": {"Mode": "Light"},
|
||||
"children": [
|
||||
{"type": "icon_font", "id": "SFI_icon", "width": 16, "height": 16,
|
||||
"fill": "$--muted-foreground", "iconFontFamily": "lucide", "iconFontName": "folder"},
|
||||
{"type": "text", "id": "SFI_label", "content": "Filter Item",
|
||||
"fill": "$--foreground", "fontFamily": "Inter", "fontSize": 13},
|
||||
{"type": "text", "id": "SFI_count", "content": "42",
|
||||
"fill": "$--muted-foreground", "fontFamily": "Inter", "fontSize": 11}
|
||||
]
|
||||
}
|
||||
|
||||
def make_component_section_label(x, y):
|
||||
return {
|
||||
"type": "frame", "id": "comp_SectionLabel", "name": "component/SectionLabel",
|
||||
"reusable": True, "x": x, "y": y,
|
||||
"width": "fill_container(280)", "padding": [0, 12],
|
||||
"theme": {"Mode": "Light"},
|
||||
"children": [
|
||||
{"type": "text", "id": "SL_label", "content": "SECTION",
|
||||
"fill": "$--muted-foreground", "fontFamily": "Inter",
|
||||
"fontSize": 10, "fontWeight": "600", "letterSpacing": 1.2}
|
||||
]
|
||||
}
|
||||
|
||||
def make_component_section_count_header(x, y):
|
||||
return {
|
||||
"type": "frame", "id": "comp_SectionCountHeader", "name": "component/SectionCountHeader",
|
||||
"reusable": True, "x": x, "y": y,
|
||||
"width": "fill_container(280)", "justifyContent": "space_between",
|
||||
"alignItems": "center", "padding": [4, 0],
|
||||
"theme": {"Mode": "Light"},
|
||||
"children": [
|
||||
{"type": "text", "id": "SCH_label", "content": "SECTION",
|
||||
"fill": "$--muted-foreground", "fontFamily": "Inter",
|
||||
"fontSize": 10, "fontWeight": "600", "letterSpacing": 1.2},
|
||||
{"type": "text", "id": "SCH_count", "content": "3",
|
||||
"fill": "$--muted-foreground", "fontFamily": "Inter", "fontSize": 11}
|
||||
]
|
||||
}
|
||||
|
||||
def make_component_add_button(x, y):
|
||||
return {
|
||||
"type": "frame", "id": "comp_AddButton", "name": "component/AddButton",
|
||||
"reusable": True, "x": x, "y": y,
|
||||
"width": "fill_container(280)", "height": 32,
|
||||
"cornerRadius": 6, "justifyContent": "center", "alignItems": "center",
|
||||
"stroke": {"align": "inside", "fill": "$--border", "thickness": 1},
|
||||
"theme": {"Mode": "Light"},
|
||||
"children": [
|
||||
{"type": "text", "id": "AB_label", "content": "+ Add property",
|
||||
"fill": "$--muted-foreground", "fontFamily": "Inter", "fontSize": 12}
|
||||
]
|
||||
}
|
||||
|
||||
def make_component_rel_group_label(x, y):
|
||||
return {
|
||||
"type": "frame", "id": "comp_RelGroupLabel", "name": "component/RelGroupLabel",
|
||||
"reusable": True, "x": x, "y": y,
|
||||
"width": "fill_container(280)", "justifyContent": "space_between",
|
||||
"alignItems": "center",
|
||||
"theme": {"Mode": "Light"},
|
||||
"children": [
|
||||
{"type": "text", "id": "RGL_label", "content": "BELONGS TO",
|
||||
"fill": "$--muted-foreground", "fontFamily": "Inter",
|
||||
"fontSize": 10, "fontWeight": "600", "letterSpacing": 0.5},
|
||||
{"type": "text", "id": "RGL_count", "content": "3",
|
||||
"fill": "$--muted-foreground", "fontFamily": "Inter", "fontSize": 10}
|
||||
]
|
||||
}
|
||||
|
||||
def make_component_command_palette_item(x, y):
|
||||
return {
|
||||
"type": "frame", "id": "comp_CommandPaletteItem", "name": "component/CommandPaletteItem",
|
||||
"reusable": True, "x": x, "y": y,
|
||||
"width": "fill_container(460)", "height": 40,
|
||||
"alignItems": "center", "gap": 12, "padding": [0, 16],
|
||||
"cornerRadius": "$--radius-md",
|
||||
"theme": {"Mode": "Light"},
|
||||
"children": [
|
||||
{"type": "icon_font", "id": "CPI_icon", "width": 16, "height": 16,
|
||||
"fill": "$--muted-foreground", "iconFontFamily": "lucide", "iconFontName": "file-text"},
|
||||
{"type": "frame", "id": "CPI_content", "layout": "vertical",
|
||||
"width": "fill_container", "gap": 2, "children": [
|
||||
{"type": "text", "id": "CPI_title", "content": "Command Name",
|
||||
"fill": "$--foreground", "fontFamily": "Inter", "fontSize": 13, "fontWeight": "500"},
|
||||
]},
|
||||
{"type": "text", "id": "CPI_shortcut", "content": "⌘K",
|
||||
"fill": "$--muted-foreground", "fontFamily": "IBM Plex Mono", "fontSize": 11}
|
||||
]
|
||||
}
|
||||
|
||||
def make_component_editable_value(x, y):
|
||||
return {
|
||||
"type": "frame", "id": "comp_EditableValue", "name": "component/EditableValue",
|
||||
"reusable": True, "x": x, "y": y,
|
||||
"width": "fill_container(200)",
|
||||
"alignItems": "center", "gap": 4,
|
||||
"theme": {"Mode": "Light"},
|
||||
"children": [
|
||||
{"type": "text", "id": "EV_value", "content": "Editable Value",
|
||||
"fill": "$--foreground", "fontFamily": "Inter", "fontSize": 13},
|
||||
{"type": "icon_font", "id": "EV_edit", "width": 12, "height": 12,
|
||||
"fill": "$--muted-foreground", "iconFontFamily": "lucide", "iconFontName": "pencil",
|
||||
"opacity": 0}
|
||||
]
|
||||
}
|
||||
|
||||
# ─── Organism Components ──────────────────────────────────────────
|
||||
|
||||
def make_component_toast(x, y):
|
||||
return {
|
||||
"type": "frame", "id": "comp_Toast", "name": "component/Toast",
|
||||
"reusable": True, "x": x, "y": y,
|
||||
"width": 360, "cornerRadius": "$--radius-lg",
|
||||
"fill": "$--card", "padding": [12, 16], "gap": 12,
|
||||
"alignItems": "center",
|
||||
"stroke": {"fill": "$--border", "thickness": 1},
|
||||
"effect": {"type": "shadow", "offset": {"x": 0, "y": 4}, "blur": 12, "color": "#0000001A"},
|
||||
"theme": {"Mode": "Light"},
|
||||
"children": [
|
||||
{"type": "icon_font", "id": "Toast_icon", "width": 20, "height": 20,
|
||||
"fill": "$--accent-green", "iconFontFamily": "lucide", "iconFontName": "check-circle"},
|
||||
{"type": "frame", "id": "Toast_content", "layout": "vertical",
|
||||
"width": "fill_container", "gap": 2, "children": [
|
||||
{"type": "text", "id": "Toast_title", "content": "Success",
|
||||
"fill": "$--foreground", "fontFamily": "Inter", "fontSize": 13, "fontWeight": "600"},
|
||||
{"type": "text", "id": "Toast_msg", "content": "Your changes have been saved.",
|
||||
"fill": "$--muted-foreground", "fontFamily": "Inter", "fontSize": 12, "lineHeight": 1.4}
|
||||
]},
|
||||
{"type": "icon_font", "id": "Toast_close", "width": 14, "height": 14,
|
||||
"fill": "$--muted-foreground", "iconFontFamily": "lucide", "iconFontName": "x"}
|
||||
]
|
||||
}
|
||||
|
||||
def make_component_ai_chat_message(x, y):
|
||||
return {
|
||||
"type": "frame", "id": "comp_AIChatMessage", "name": "component/AIChatMessage",
|
||||
"reusable": True, "x": x, "y": y,
|
||||
"width": "fill_container(400)", "layout": "horizontal",
|
||||
"gap": 12, "padding": [12, 16],
|
||||
"theme": {"Mode": "Light"},
|
||||
"children": [
|
||||
{"type": "frame", "id": "AIM_avatar", "width": 28, "height": 28,
|
||||
"cornerRadius": 14, "fill": "$--accent-purple-light",
|
||||
"alignItems": "center", "justifyContent": "center",
|
||||
"children": [
|
||||
{"type": "icon_font", "id": "AIM_avatarIcon", "width": 16, "height": 16,
|
||||
"fill": "$--accent-purple", "iconFontFamily": "lucide", "iconFontName": "sparkles"}
|
||||
]},
|
||||
{"type": "frame", "id": "AIM_body", "layout": "vertical",
|
||||
"width": "fill_container", "gap": 4, "children": [
|
||||
{"type": "text", "id": "AIM_sender", "content": "AI Assistant",
|
||||
"fill": "$--foreground", "fontFamily": "Inter", "fontSize": 12, "fontWeight": "600"},
|
||||
{"type": "text", "id": "AIM_text",
|
||||
"content": "Here's a summary of your recent notes...",
|
||||
"fill": "$--foreground", "fontFamily": "Inter", "fontSize": 13,
|
||||
"lineHeight": 1.5, "textGrowth": "fixed-width", "width": "fill_container"}
|
||||
]}
|
||||
]
|
||||
}
|
||||
|
||||
# ─── Component showcase frame ─────────────────────────────────────
|
||||
def make_component_showcase(x, y, title, components):
|
||||
"""Creates a frame showing all component instances in a row for visual reference."""
|
||||
children = [
|
||||
{"type": "text", "id": f"showcase_{title.replace(' ','_')}_title",
|
||||
"content": title,
|
||||
"fill": "$--foreground", "fontFamily": "Inter", "fontSize": 18, "fontWeight": "600"},
|
||||
]
|
||||
for comp_id, label in components:
|
||||
children.append({
|
||||
"type": "frame", "id": f"show_{comp_id}", "layout": "vertical", "gap": 8,
|
||||
"padding": [12, 0], "children": [
|
||||
{"type": "text", "id": f"show_{comp_id}_lbl", "content": label,
|
||||
"fill": "$--muted-foreground", "fontFamily": "IBM Plex Mono", "fontSize": 10,
|
||||
"letterSpacing": 0.5},
|
||||
{"type": "ref", "id": f"show_{comp_id}_ref", "ref": comp_id}
|
||||
]
|
||||
})
|
||||
sid = f"showcase_{title.replace(' ', '_')}"
|
||||
return {
|
||||
"type": "frame", "id": sid, "name": f"Showcase — {title}",
|
||||
"x": x, "y": y, "width": 1440,
|
||||
"fill": "$--background", "layout": "vertical",
|
||||
"gap": 24, "padding": 40,
|
||||
"theme": {"Mode": "Light"},
|
||||
"children": children
|
||||
}
|
||||
|
||||
# ─── Feature spec grouping ────────────────────────────────────────
|
||||
FEATURE_GROUPS = {
|
||||
"Tabs & Navigation": [
|
||||
"dt0", "dtj", "dt11", "rnt0", "rne0", "rns0",
|
||||
"archBtnNorm", "archBtnArc", "srtDD", "srtBN"
|
||||
],
|
||||
"Inspector & Properties": [
|
||||
"pi01", "pi50", "rbF01", "rbF02", "rbF03",
|
||||
"reF01", "reF02", "reF03",
|
||||
"urlDefault", "urlHover", "urlEdit"
|
||||
],
|
||||
"Note List & Virtual List": [
|
||||
"vl001", "vl100", "vl200", "vl300",
|
||||
"mni01", "dp01"
|
||||
],
|
||||
"Sidebar & Sections": [
|
||||
"dsg01a", "dsg02w", "dsg04j", "csB01", "csA01", "trSB1"
|
||||
],
|
||||
"Modals & Settings": [
|
||||
"iogBH", "ghv01", "ghv02", "ghv03", "ghv04"
|
||||
],
|
||||
"Editor & Wikilinks": [
|
||||
"wlc01", "wlc20", "mb001", "mb011"
|
||||
],
|
||||
"Status & Indicators": [
|
||||
"mni10", "mni20", "dp10", "dp20", "dp30"
|
||||
],
|
||||
"Git & Changes": [
|
||||
"vc001", "vc100", "vc200", "ghCL1", "ghDO1"
|
||||
],
|
||||
"Search": [
|
||||
"3aG9b", "K1O2x", "nrIcZ"
|
||||
],
|
||||
"Trash": [
|
||||
"trNL1", "trRI1", "trW30"
|
||||
],
|
||||
}
|
||||
|
||||
# ─── Main transform ──────────────────────────────────────────────
|
||||
|
||||
def transform():
|
||||
data = load()
|
||||
|
||||
# 1. Add new variables (merge, don't replace)
|
||||
for k, v in NEW_VARIABLES.items():
|
||||
data["variables"][k] = v
|
||||
|
||||
# 2. Build an index of existing children by ID
|
||||
existing = {c["id"]: c for c in data["children"]}
|
||||
|
||||
# 3. Fix hardcoded colors in ALL existing frames
|
||||
for child in data["children"]:
|
||||
replace_hardcoded_colors(child)
|
||||
|
||||
# 4. Build new ordered children list
|
||||
new_children = []
|
||||
|
||||
# ─── Section 0: Cover (y=0) ───
|
||||
new_children.append(make_cover(0, 0))
|
||||
|
||||
# ─── Section 1: Foundations (y=500) ───
|
||||
new_children.append(make_section_header("sec1_hdr", "1 — Foundations", 0, 500))
|
||||
|
||||
# Move existing Color Palette
|
||||
if "mOf4J" in existing:
|
||||
cp = existing.pop("mOf4J")
|
||||
cp["x"] = 0; cp["y"] = 600
|
||||
new_children.append(cp)
|
||||
|
||||
# Move existing Typography & Spacing
|
||||
if "HZonq" in existing:
|
||||
ts = existing.pop("HZonq")
|
||||
ts["x"] = 0; ts["y"] = 1600
|
||||
new_children.append(ts)
|
||||
|
||||
# New foundation frames
|
||||
new_children.append(make_spacing_scale(0, 3000))
|
||||
new_children.append(make_heights_ref(800, 3000))
|
||||
new_children.append(make_shadows_ref(0, 3600))
|
||||
|
||||
# ─── Section 2: Components (y=4200) ───
|
||||
new_children.append(make_section_header("sec2_hdr", "2 — Components", 0, 4200))
|
||||
|
||||
# Subsection labels
|
||||
new_children.append(make_subsection_label("sub_atoms", "ATOMS", 0, 4290))
|
||||
|
||||
# Atom components (placed as reusable definitions)
|
||||
comp_y_atoms = 4320
|
||||
atom_components = [
|
||||
make_component_status_dot(0, comp_y_atoms),
|
||||
make_component_separator(100, comp_y_atoms),
|
||||
make_component_badge(400, comp_y_atoms),
|
||||
make_component_button_primary(0, comp_y_atoms + 60),
|
||||
make_component_button_secondary(200, comp_y_atoms + 60),
|
||||
make_component_button_ghost(400, comp_y_atoms + 60),
|
||||
make_component_input(0, comp_y_atoms + 120),
|
||||
make_component_icon_button(300, comp_y_atoms + 120),
|
||||
]
|
||||
new_children.extend(atom_components)
|
||||
|
||||
# Atom showcase
|
||||
new_children.append(make_component_showcase(0, comp_y_atoms + 200, "Atoms", [
|
||||
("comp_StatusDot", "StatusDot"),
|
||||
("comp_Separator", "Separator"),
|
||||
("comp_Badge", "Badge"),
|
||||
("comp_ButtonPrimary", "Button/Primary"),
|
||||
("comp_ButtonSecondary", "Button/Secondary"),
|
||||
("comp_ButtonGhost", "Button/Ghost"),
|
||||
("comp_Input", "Input"),
|
||||
("comp_IconButton", "IconButton"),
|
||||
]))
|
||||
|
||||
# Subsection: Molecules
|
||||
mol_y = 4900
|
||||
new_children.append(make_subsection_label("sub_molecules", "MOLECULES", 0, mol_y - 30))
|
||||
|
||||
molecule_components = [
|
||||
make_component_inspector_header(0, mol_y),
|
||||
make_component_note_list_item(0, mol_y + 60),
|
||||
make_component_tab_active(400, mol_y),
|
||||
make_component_tab_inactive(600, mol_y),
|
||||
make_component_property_row(0, mol_y + 160),
|
||||
make_component_relationship_pill(0, mol_y + 220),
|
||||
make_component_sidebar_filter_item(400, mol_y + 160),
|
||||
make_component_section_label(0, mol_y + 280),
|
||||
make_component_section_count_header(400, mol_y + 280),
|
||||
make_component_add_button(0, mol_y + 340),
|
||||
make_component_rel_group_label(400, mol_y + 340),
|
||||
make_component_command_palette_item(0, mol_y + 400),
|
||||
make_component_editable_value(0, mol_y + 460),
|
||||
]
|
||||
new_children.extend(molecule_components)
|
||||
|
||||
# Molecule showcase
|
||||
new_children.append(make_component_showcase(0, mol_y + 520, "Molecules", [
|
||||
("comp_InspectorHeader", "InspectorHeader"),
|
||||
("comp_NoteListItem", "NoteListItem"),
|
||||
("comp_TabActive", "Tab/Active"),
|
||||
("comp_TabInactive", "Tab/Inactive"),
|
||||
("comp_PropertyRow", "PropertyRow"),
|
||||
("comp_RelationshipPill", "RelationshipPill"),
|
||||
("comp_SidebarFilterItem", "SidebarFilterItem"),
|
||||
("comp_SectionLabel", "SectionLabel"),
|
||||
("comp_SectionCountHeader", "SectionCountHeader"),
|
||||
("comp_AddButton", "AddButton"),
|
||||
("comp_RelGroupLabel", "RelGroupLabel"),
|
||||
("comp_CommandPaletteItem", "CommandPaletteItem"),
|
||||
("comp_EditableValue", "EditableValue"),
|
||||
]))
|
||||
|
||||
# Subsection: Organisms
|
||||
org_y = 5900
|
||||
new_children.append(make_subsection_label("sub_organisms", "ORGANISMS", 0, org_y - 30))
|
||||
|
||||
new_children.append(make_component_toast(0, org_y))
|
||||
new_children.append(make_component_ai_chat_message(0, org_y + 80))
|
||||
|
||||
new_children.append(make_component_showcase(0, org_y + 200, "Organisms", [
|
||||
("comp_Toast", "Toast"),
|
||||
("comp_AIChatMessage", "AIChatMessage"),
|
||||
]))
|
||||
|
||||
# ─── Section 3: Full Layouts (y=6600) ───
|
||||
new_children.append(make_section_header("sec3_hdr", "3 — Full Layouts", 0, 6600))
|
||||
|
||||
# Layout frames — move existing ones
|
||||
layout_ids = ["qHhaj", "SC_all", "SC_nosb", "SC_edonly", "SC_ednl"]
|
||||
layout_y = 6700
|
||||
for lid in layout_ids:
|
||||
if lid in existing:
|
||||
frame = existing.pop(lid)
|
||||
frame["x"] = 0
|
||||
frame["y"] = layout_y
|
||||
new_children.append(frame)
|
||||
layout_y += 1000 # 900px height + 100px gap
|
||||
|
||||
# ─── Section 4: Feature Specs (y=12200) ───
|
||||
spec_y = 12200
|
||||
new_children.append(make_section_header("sec4_hdr", "4 — Feature Specs", 0, spec_y))
|
||||
spec_y += 100
|
||||
|
||||
placed_ids = set()
|
||||
for group_name, group_ids in FEATURE_GROUPS.items():
|
||||
# Group label
|
||||
new_children.append(make_subsection_label(
|
||||
f"fg_{group_name.replace(' ', '_').replace('&','and').lower()[:20]}",
|
||||
group_name.upper(), 0, spec_y
|
||||
))
|
||||
spec_y += 30
|
||||
|
||||
group_x = 0
|
||||
max_h = 0
|
||||
for fid in group_ids:
|
||||
if fid in existing:
|
||||
frame = existing.pop(fid)
|
||||
fw = frame.get("width", 300)
|
||||
if isinstance(fw, str):
|
||||
fw = 340 # fallback for dynamic widths
|
||||
|
||||
# Wrap to next row if too wide
|
||||
if group_x + fw > 3000:
|
||||
spec_y += max_h + 40
|
||||
group_x = 0
|
||||
max_h = 0
|
||||
|
||||
frame["x"] = group_x
|
||||
frame["y"] = spec_y
|
||||
new_children.append(frame)
|
||||
placed_ids.add(fid)
|
||||
|
||||
fh = frame.get("height", 200)
|
||||
if isinstance(fh, str):
|
||||
fh = 400 # fallback
|
||||
max_h = max(max_h, fh)
|
||||
group_x += fw + 100
|
||||
|
||||
spec_y += max_h + 80
|
||||
|
||||
# Any remaining unplaced frames go at the end
|
||||
for fid, frame in existing.items():
|
||||
if fid not in placed_ids:
|
||||
frame["x"] = 0
|
||||
frame["y"] = spec_y
|
||||
new_children.append(frame)
|
||||
fh = frame.get("height", 200)
|
||||
if isinstance(fh, str):
|
||||
fh = 400
|
||||
spec_y += fh + 100
|
||||
|
||||
# 5. Replace children
|
||||
data["children"] = new_children
|
||||
|
||||
# 6. Save
|
||||
save(data)
|
||||
|
||||
# Stats
|
||||
reusable_count = sum(1 for c in new_children if c.get("reusable"))
|
||||
print(f"✅ Restructured ui-design.pen")
|
||||
print(f" Total nodes: {len(new_children)}")
|
||||
print(f" Reusable components: {reusable_count}")
|
||||
print(f" Variables: {len(data['variables'])}")
|
||||
print(f" Hardcoded colors replaced in existing frames")
|
||||
print(f" Canvas organized into 5 sections")
|
||||
|
||||
if __name__ == "__main__":
|
||||
transform()
|
||||
Reference in New Issue
Block a user