Initial commit

This commit is contained in:
Zhongwei Li
2025-11-30 09:02:33 +08:00
commit 0c40192593
82 changed files with 18699 additions and 0 deletions

View File

@@ -0,0 +1,212 @@
# Screenshot Import Data Merging
This document provides helper functions for merging vision agent outputs into a unified data structure.
## Overview
After 3 vision agents complete (layout, components, visual properties), their outputs must be merged into a single enriched data structure suitable for component generation.
## Helper Functions
### 1. findSectionForComponent()
Determines which layout section contains a component:
```typescript
function findSectionForComponent(compLocation, layoutStructure) {
// Match component location to layout sections
for (const section of layoutStructure.sections) {
if (compLocation.section === section.id) {
return section;
}
}
// Fallback to main section if no match
return layoutStructure.sections.find(s => s.type === 'main');
}
```
### 2. categorizeComponents()
Splits components into atomic, composite, and screen categories:
```typescript
function categorizeComponents(components, screenType) {
const atomicComponents = components
.filter(c => c.category === "atomic")
.map(c => c.id);
const compositeComponents = components
.filter(c => c.category === "composite")
.map(c => c.id);
return {
atomicComponents,
compositeComponents,
screenComponents: [`${screenType}-screen`]
};
}
```
### 3. generateScreenName()
Converts screen type to human-readable name:
```typescript
function generateScreenName(screenType) {
const names = {
"dashboard": "Dashboard",
"login": "Login Screen",
"form": "Form Screen",
"list": "List View",
"detail": "Detail View",
"settings": "Settings",
"profile": "Profile Page"
};
return names[screenType] || `${screenType.charAt(0).toUpperCase()}${screenType.slice(1)} Screen`;
}
```
### 4. generateScreenDescription()
Creates screen description from type and layout:
```typescript
function generateScreenDescription(screenType, layoutStructure) {
const sectionNames = layoutStructure.sections.map(s => s.type).join(", ");
return `${generateScreenName(screenType)} with ${sectionNames} sections`;
}
```
### 5. enrichComponentWithVisualProps()
Merges component data with visual properties (with defaults):
```typescript
function enrichComponentWithVisualProps(comp, visualResult) {
const visualProps = visualResult.visualProperties[comp.id] || {
dimensions: {
width: comp.location.position.width,
height: comp.location.position.height,
unit: "characters"
},
borderStyle: "light",
fillPattern: "transparent",
textAlignment: "left",
spacing: { padding: "normal", margin: "normal" }
};
return {
width: visualProps.dimensions?.width || comp.location.position.width,
height: visualProps.dimensions?.height || comp.location.position.height,
borderStyle: visualProps.borderStyle || "light",
fillPattern: visualProps.fillPattern || "transparent",
textAlignment: visualProps.textAlignment || "left",
textContent: comp.textContent,
placeholder: comp.placeholder || ""
};
}
```
## Complete Merging Workflow
```typescript
function mergeAgentResults(layoutResult, componentResult, visualResult) {
const mergedData = {
screen: {
type: layoutResult.screenType,
name: generateScreenName(layoutResult.screenType),
description: generateScreenDescription(layoutResult.screenType, layoutResult.layoutStructure),
layout: layoutResult.layoutStructure.type
},
components: componentResult.components.map(comp => {
// Find which section contains this component
const section = findSectionForComponent(comp.location, layoutResult.layoutStructure);
// Enrich with visual properties
const visualProperties = enrichComponentWithVisualProps(comp, visualResult);
return {
id: comp.id,
type: comp.type,
category: comp.category,
section: section?.id || "main",
visualProperties,
states: comp.states || ["default"],
accessibility: comp.accessibility,
location: comp.location // preserve original location data
};
}),
composition: categorizeComponents(componentResult.components, layoutResult.screenType),
layoutHierarchy: layoutResult.hierarchy
};
return mergedData;
}
```
## Usage Example
```typescript
// After all 3 vision agents complete
const layoutResult = await layoutAgent();
const componentResult = await componentAgent();
const visualResult = await visualAgent();
// Merge their outputs
const mergedData = mergeAgentResults(layoutResult, componentResult, visualResult);
// mergedData is now ready for component generation
console.log(`Merged ${mergedData.components.length} components for ${mergedData.screen.type} screen`);
```
## Output Structure
The merged data structure:
```typescript
{
screen: {
type: "dashboard",
name: "Dashboard",
description: "Dashboard with header, main sections",
layout: "fixed-header-sidebar"
},
components: [
{
id: "email-input",
type: "input",
category: "atomic",
section: "main",
visualProperties: {
width: 40,
height: 3,
borderStyle: "light",
fillPattern: "transparent",
textAlignment: "left",
textContent: "Email",
placeholder: "Enter your email"
},
states: ["default", "focus", "error"],
accessibility: {
role: "textbox",
label: "Email address"
},
location: { /* original location data */ }
}
],
composition: {
atomicComponents: ["email-input", "password-input"],
compositeComponents: ["login-form"],
screenComponents: ["dashboard-screen"]
},
layoutHierarchy: {
root: "screen",
children: {
header: ["logo", "navigation"],
main: ["login-form"]
}
}
}
```

View File

@@ -0,0 +1,481 @@
# Screenshot Import ASCII Generation
This document contains all ASCII art generation functions for screenshot-to-uxscii conversion. These functions transform visual component properties into uxscii-compliant ASCII representations.
## Core ASCII Functions
### selectBorderChars()
Selects box-drawing characters based on state and border style:
```typescript
function selectBorderChars(state: string, baseStyle: string): string {
// Border character map: state → style → character sequence
// Format: "topLeft|top|topRight|side|bottomLeft|bottom|bottomRight"
const styleMap: Record<string, Record<string, string>> = {
'default': {
'light': '┌|─|┐|│|└|┘',
'rounded': '╭|─|╮|│|╰|╯',
'double': '╔|═|╗|║|╚|╝',
'heavy': '┏|━|┓|┃|┗|┛',
'none': ' | | | | | '
},
'hover': {
'light': '┏|━|┓|┃|┗|┛', // Upgrade to heavy
'rounded': '┏|━|┓|┃|┗|┛', // Upgrade to heavy
'double': '╔|═|╗|║|╚|╝', // Keep double
'heavy': '┏|━|┓|┃|┗|┛', // Keep heavy
'none': ' | | | | | '
},
'focus': {
'light': '┏|━|┓|┃|┗|┛', // Upgrade to heavy
'rounded': '┏|━|┓|┃|┗|┛', // Upgrade to heavy
'double': '╔|═|╗|║|╚|╝', // Keep double
'heavy': '┏|━|┓|┃|┗|┛', // Keep heavy
'none': ' | | | | | '
},
'active': {
'light': '┏|━|┓|┃|┗|┛',
'rounded': '┏|━|┓|┃|┗|┛',
'double': '╔|═|╗|║|╚|╝',
'heavy': '┏|━|┓|┃|┗|┛',
'none': ' | | | | | '
},
'disabled': {
'light': '┌| ─ |┐|│|└| ─ |┘', // Dashed pattern
'rounded': '╭| ─ |╮|│|╰| ─ |╯', // Dashed pattern
'double': '╔| ═ |╗|║|╚| ═ |╝', // Dashed pattern
'heavy': '┏| ━ |┓|┃|┗| ━ |┛', // Dashed pattern
'none': ' | | | | | '
},
'error': {
'light': '┏|━|┓|┃|┗|┛',
'rounded': '┏|━|┓|┃|┗|┛',
'double': '╔|═|╗|║|╚|╝',
'heavy': '┏|━|┓|┃|┗|┛',
'none': ' | | | | | '
},
'success': {
'light': '┏|━|┓|┃|┗|┛',
'rounded': '┏|━|┓|┃|┗|┛',
'double': '╔|═|╗|║|╚|╝',
'heavy': '┏|━|┓|┃|┗|┛',
'none': ' | | | | | '
}
};
const stateStyles = styleMap[state] || styleMap['default'];
return stateStyles[baseStyle] || styleMap['default']['light'];
}
```
**State Transformations:**
- **hover/focus/active**: Upgrade light/rounded to heavy
- **disabled**: Add spaces for dashed appearance
- **error/success**: Use heavy borders for attention
### selectFillPattern()
Determines interior fill character based on component type and state:
```typescript
function selectFillPattern(state: string, componentType: string): string {
const typePatterns: Record<string, string> = {
'button': '▓', // Solid button fill
'input': ' ', // Empty space for text
'checkbox': ' ', // Filled by special generator
'radio': ' ', // Filled by special generator
'select': ' ', // Text area
'card': ' ', // Content area
'modal': ' ', // Content area
'panel': ' ', // Content area
'alert': ' ', // Message area
'badge': '▓', // Solid badge fill
'progress': ' ', // Filled by special generator
'spinner': ' ', // Filled by special generator
'toast': ' ', // Message area
'table': ' ', // Data cells
'list': ' ' // List items
};
const baseFill = typePatterns[componentType] || ' ';
// State-specific modifications
if (state === 'hover' && componentType === 'button') {
return '█'; // Darker fill on hover
}
if (state === 'disabled') {
return ' '; // Empty on disabled
}
if (state === 'active' && componentType === 'button') {
return '█'; // Full solid on active
}
if (state === 'focus' && componentType === 'input') {
return '│'; // Cursor indicator
}
return baseFill;
}
```
**Pattern Types:**
- Solid `▓`: Buttons, badges
- Full `█`: Hover/active states
- Cursor `│`: Input focus
- Empty ` `: Inputs, containers
### buildASCIIBox()
Constructs ASCII box with text centering:
```typescript
function buildASCIIBox(
width: number,
height: number,
text: string,
borderChars: string,
fillPattern: string
): string {
const [tl, t, tr, s, bl, b, br] = borderChars.split('|');
const lines: string[] = [];
const innerWidth = width - 2;
const innerHeight = height - 2;
// Top border
lines.push(tl + t.repeat(innerWidth) + tr);
// Calculate text position (vertical center)
const textLine = Math.floor(innerHeight / 2);
// Middle lines
for (let i = 0; i < innerHeight; i++) {
if (i === textLine && text) {
// Center text horizontally
const textLength = text.length;
const paddingLeft = Math.floor((innerWidth - textLength) / 2);
const paddingRight = innerWidth - textLength - paddingLeft;
const line = s +
' '.repeat(paddingLeft) +
text +
' '.repeat(paddingRight) +
s;
lines.push(line);
} else {
lines.push(s + fillPattern.repeat(innerWidth) + s);
}
}
// Bottom border
lines.push(bl + b.repeat(innerWidth) + br);
return lines.join('\n');
}
```
### generateASCII()
Main ASCII generation function with optional minimal mode:
```typescript
function generateASCII(
componentId: string,
state: string,
visualProperties: any,
componentType: string,
minimalMode: boolean = false // NEW: Enable single-state generation
): string {
// Special component handlers
if (componentType === 'checkbox') {
return generateCheckbox(state, visualProperties.textContent);
}
if (componentType === 'radio') {
return generateRadio(state, visualProperties.textContent);
}
if (componentType === 'progress') {
return generateProgressBar(50, visualProperties.width);
}
if (componentType === 'spinner') {
return generateSpinner(0);
}
// Standard box components
const borderChars = selectBorderChars(state, visualProperties.borderStyle);
const fillPattern = selectFillPattern(state, componentType);
let text = visualProperties.textContent || '';
// Add state indicators
if (state === 'focus' && componentType === 'button') {
text += ' ✨';
}
if (state === 'error' && componentType === 'input') {
text = '⚠️ ' + text;
}
if (state === 'success' && componentType === 'input') {
text = '✅ ' + text;
}
return buildASCIIBox(
visualProperties.width,
visualProperties.height,
text,
borderChars,
fillPattern
);
}
```
**Minimal Mode Usage**:
When `minimalMode` is `true`, this function is typically only called for the 'default' state during initial component creation. This enables fast MVP component generation.
```typescript
// Minimal mode - only generate default state
const defaultOnlyASCII = generateASCII(
'submit-button',
'default',
visualProperties,
'button',
true // minimalMode = true
);
// Full mode - generate any state
const hoverASCII = generateASCII(
'submit-button',
'hover',
visualProperties,
'button',
false // minimalMode = false (default)
);
```
The `minimalMode` parameter doesn't change the function behavior directly, but signals intent for documentation. When creating components in minimal mode, only call this function once for 'default' state instead of looping through all states.
## Special Component Generators
### generateCheckbox()
```typescript
function generateCheckbox(state: string, label: string): string {
let box = '[ ]'; // Unchecked
if (state === 'checked') {
box = '[✓]';
} else if (state === 'indeterminate') {
box = '[▬]';
} else if (state === 'disabled') {
box = '[─]';
}
return `${box} ${label}`;
}
```
**States:**
- Unchecked: `[ ]`
- Checked: `[✓]`
- Indeterminate: `[▬]`
- Disabled: `[─]`
### generateRadio()
```typescript
function generateRadio(state: string, label: string): string {
let circle = '○'; // Unselected
if (state === 'selected') {
circle = '◉';
} else if (state === 'disabled') {
circle = '◌';
}
return `${circle} ${label}`;
}
```
**States:**
- Unselected: `○`
- Selected: `◉`
- Disabled: `◌`
### generateProgressBar()
```typescript
function generateProgressBar(percent: number, width: number): string {
const filled = Math.floor((width * percent) / 100);
const remaining = width - filled;
const bar = '█'.repeat(filled) + '░'.repeat(remaining);
return `${bar} ${percent}%`;
}
```
**Example:** `████░░░░░░ 40%`
### generateSpinner()
```typescript
function generateSpinner(frame: number): string {
const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
return frames[frame % frames.length];
}
```
**Animation:** 10 Braille pattern characters
## Utility Functions
### maskPassword()
```typescript
function maskPassword(value: string): string {
return '•'.repeat(value.length);
}
```
### renderInputPlaceholder()
```typescript
function renderInputPlaceholder(
placeholder: string,
value: string,
width: number
): string {
if (value) {
return value.padEnd(width - 2, ' ');
} else {
return placeholder.padEnd(width - 2, ' ');
}
}
```
### addGlowEffect()
For hover states:
```typescript
function addGlowEffect(ascii: string): string {
const lines = ascii.split('\n');
const glowLines = lines.map(line => `${line}`);
const glowTop = '░'.repeat(glowLines[0].length);
const glowBottom = '░'.repeat(glowLines[0].length);
return [glowTop, ...glowLines, glowBottom].join('\n');
}
```
**Example:**
```
Without glow: With glow:
╭─────────╮ ░░░░░░░░░░░░░
│ Click │ ░╭─────────╮░
╰─────────╯ ░│ Click │░
░╰─────────╯░
░░░░░░░░░░░░░
```
### addValidationIndicator()
```typescript
function addValidationIndicator(
ascii: string,
state: string,
message?: string
): string {
let indicator = '';
if (state === 'error') {
indicator = '⚠️';
if (message) {
indicator += '\n❌ ' + message;
}
} else if (state === 'success') {
indicator = '✅';
}
return ascii + indicator;
}
```
## Complete Generation Example
Putting it all together for a button:
```typescript
// Input from vision analysis
const componentData = {
id: 'submit-button',
type: 'button',
visualProperties: {
width: 20,
height: 3,
borderStyle: 'rounded',
textContent: 'Submit'
}
};
// Generate default state
const defaultASCII = generateASCII(
'submit-button',
'default',
componentData.visualProperties,
'button'
);
// Result:
// ╭──────────────────╮
// │▓▓▓▓▓▓Submit▓▓▓▓▓▓│
// ╰──────────────────╯
// Generate hover state
const hoverASCII = generateASCII(
'submit-button',
'hover',
componentData.visualProperties,
'button'
);
// Result:
// ┏━━━━━━━━━━━━━━━━━━┓
// ┃████████Submit████┃
// ┗━━━━━━━━━━━━━━━━━━┛
// Generate disabled state
const disabledASCII = generateASCII(
'submit-button',
'disabled',
componentData.visualProperties,
'button'
);
// Result:
// ╭ ─ ─ ─ ─ ─ ─ ─ ─ ╮
// │ Submit │
// ╰ ─ ─ ─ ─ ─ ─ ─ ─ ╯
```
## ASCII Generation Guidelines
**Consistency Rules:**
1. Same dimensions across all states
2. Border progression: default→light, hover→heavy, disabled→dashed
3. Text always centered (horizontal and vertical)
4. State indicators used sparingly (✨, ⚠️, ✅)
5. Test in monospace font
**Performance Tips:**
- Pre-compile border character sets
- Cache generated ASCII for repeated components
- Reuse fill patterns
**Accessibility:**
- Keep text readable (adequate padding)
- Use Unicode carefully (terminal support varies)
- Provide text alternatives in metadata
## Reference
These functions implement ASCII patterns documented in:
- `fluxwing/data/docs/06-ascii-patterns.md` - Standard box-drawing patterns
- Examples in `fluxwing/data/examples/*.md` - Real component templates
Use these functions to generate consistent, high-quality ASCII representations for all uxscii components.

View File

@@ -0,0 +1,715 @@
# Screenshot Import Complete Examples
This document contains complete end-to-end examples of screenshot-to-uxscii conversion, showing input data, generated files, and expected outputs.
## Example 1: Simple Button Component
### Input: Vision Analysis Data
```json
{
"id": "submit-button",
"type": "button",
"category": "atomic",
"visualProperties": {
"width": 20,
"height": 3,
"borderStyle": "rounded",
"textContent": "Submit"
},
"states": ["default", "hover", "focus", "disabled"],
"accessibility": {
"role": "button",
"label": "Submit form"
}
}
```
### Output: submit-button.uxm
```json
{
"id": "submit-button",
"type": "button",
"version": "1.0.0",
"metadata": {
"name": "Submit Button",
"description": "An interactive button component for user actions. Submit form",
"author": "Fluxwing Screenshot Import",
"created": "2025-10-13T12:00:00Z",
"modified": "2025-10-13T12:00:00Z",
"tags": ["button", "imported", "screenshot-generated", "utility", "interactive"],
"category": "utility"
},
"props": {
"text": "Submit",
"variant": "secondary",
"size": "small"
},
"behavior": {
"states": [
{
"name": "default",
"properties": {
"border": "rounded",
"background": "filled",
"textColor": "default"
}
},
{
"name": "hover",
"properties": {
"border": "heavy",
"background": "filled",
"textColor": "default"
}
},
{
"name": "focus",
"properties": {
"border": "heavy",
"background": "filled",
"textColor": "default"
}
},
{
"name": "disabled",
"properties": {
"border": "dashed",
"background": "filled",
"textColor": "default",
"opacity": 0.5,
"cursor": "not-allowed"
}
}
],
"interactions": ["click", "keyboard"],
"accessibility": {
"role": "button",
"focusable": true,
"keyboardSupport": ["Enter", "Space"],
"ariaLabel": "Submit form"
}
},
"layout": {
"display": "inline-block",
"positioning": "static",
"spacing": {
"padding": { "x": 2, "y": 1 },
"margin": { "x": 0, "y": 0 }
},
"sizing": {
"minWidth": 20,
"height": 3
}
},
"ascii": {
"templateFile": "submit-button.md",
"width": 20,
"height": 3,
"variables": [
{
"name": "text",
"type": "string",
"required": true,
"default": "Submit",
"description": "button label text"
},
{
"name": "variant",
"type": "string",
"required": false,
"default": "primary",
"description": "Button style variant (primary, secondary, danger)"
},
{
"name": "size",
"type": "string",
"required": false,
"default": "medium",
"description": "Component size (small, medium, large)"
}
]
}
}
```
### Output: submit-button.md
```markdown
# Submit Button
An interactive button component for user actions. Submit form
## Default State
```
╭──────────────────╮
│▓▓▓▓▓▓Submit▓▓▓▓▓▓│
╰──────────────────╯
```
## Hover State
```
┏━━━━━━━━━━━━━━━━━━┓
┃████████Submit████┃
┗━━━━━━━━━━━━━━━━━━┛
```
## Focus State
```
┏━━━━━━━━━━━━━━━━━━┓
┃▓▓▓▓▓Submit ✨▓▓▓▓┃
┗━━━━━━━━━━━━━━━━━━┛
```
## Disabled State
```
╭ ─ ─ ─ ─ ─ ─ ─ ─ ╮
│ Submit │
╰ ─ ─ ─ ─ ─ ─ ─ ─ ╯
```
## Variables
- `text` (string, required): button label text
- Default: "Submit"
- Example: "Submit Form"
- `variant` (string, optional): Button style variant (primary, secondary, danger)
- Default: "primary"
- `size` (string, optional): Component size (small, medium, large)
- Default: "medium"
## Accessibility
- **Role**: button
- **Focusable**: Yes
- **Keyboard Support**: Enter, Space
- **ARIA Label**: Submit form
## Usage Examples
```
╭──────────────────╮
│▓▓▓Submit Form▓▓▓▓│
╰──────────────────╯
```
---
*Generated by Fluxwing Screenshot Import*
```
---
## Example 2: Email Input Component
### Input: Vision Analysis Data
```json
{
"id": "email-input",
"type": "input",
"category": "atomic",
"visualProperties": {
"width": 40,
"height": 3,
"borderStyle": "light",
"textContent": "Email",
"placeholder": "Enter your email"
},
"states": ["default", "focus", "error", "disabled"],
"accessibility": {
"role": "textbox",
"label": "Email address"
}
}
```
### Output: email-input.uxm (abbreviated)
```json
{
"id": "email-input",
"type": "input",
"version": "1.0.0",
"metadata": {
"name": "Email Input",
"description": "A text input field for user data entry. Email address",
"category": "input"
},
"props": {
"text": "Email",
"placeholder": "Enter your email",
"type": "email",
"size": "large",
"maxLength": 32
},
"ascii": {
"templateFile": "email-input.md",
"width": 40,
"height": 3,
"variables": [
{
"name": "text",
"type": "string",
"required": true,
"default": "Email",
"description": "input label text"
},
{
"name": "placeholder",
"type": "string",
"required": false,
"default": "Enter your email",
"description": "Placeholder text when empty"
},
{
"name": "value",
"type": "string",
"required": false,
"default": "",
"description": "Current value"
}
]
}
}
```
### Output: email-input.md
```markdown
# Email Input
A text input field for user data entry. Email address
## Default State
```
┌──────────────────────────────────────┐
│ Email │
│ Enter your email │
└──────────────────────────────────────┘
```
## Focus State
```
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ Email │┃
┃ john.doe@example.com ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
```
## Error State
```
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ⚠️ Email ┃
┃ invalid@email
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
❌ Please enter valid email address
```
## Disabled State
```
┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐
│ Email │
│ ────────────────────── │
└ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘
```
## Variables
- `text` (string, required): input label text
- Default: "Email"
- `placeholder` (string, optional): Placeholder text when empty
- Default: "Enter your email"
- `value` (string, optional): Current value
- Default: ""
## Accessibility
- **Role**: textbox
- **Focusable**: Yes
- **Keyboard Support**: Tab, Escape
- **ARIA Label**: Email address
---
*Generated by Fluxwing Screenshot Import*
```
---
## Example 3: Multi-Component Login Form
### Input: Vision Analysis Data
```json
{
"screen": {
"type": "login-form",
"name": "Login Screen",
"description": "User authentication screen with email/password inputs",
"layout": "vertical-center"
},
"components": [
{
"id": "email-input",
"type": "input",
"category": "atomic",
"visualProperties": { "width": 40, "height": 3, "borderStyle": "light", "textContent": "Email", "placeholder": "Enter your email" },
"states": ["default", "focus", "error"],
"accessibility": { "role": "textbox", "label": "Email address" }
},
{
"id": "password-input",
"type": "input",
"category": "atomic",
"visualProperties": { "width": 40, "height": 3, "borderStyle": "light", "textContent": "Password", "placeholder": "Enter your password" },
"states": ["default", "focus", "error"],
"accessibility": { "role": "textbox", "label": "Password" }
},
{
"id": "submit-button",
"type": "button",
"category": "atomic",
"visualProperties": { "width": 20, "height": 3, "borderStyle": "rounded", "textContent": "Sign In" },
"states": ["default", "hover", "disabled"],
"accessibility": { "role": "button", "label": "Sign in to account" }
},
{
"id": "login-form",
"type": "form",
"category": "composite",
"visualProperties": { "width": 50, "height": 20, "borderStyle": "rounded", "textContent": "Sign In" },
"states": ["default"],
"accessibility": { "role": "form", "label": "Login form" }
}
],
"composition": {
"atomicComponents": ["email-input", "password-input", "submit-button"],
"compositeComponents": ["login-form"],
"screenComponents": ["login-screen"]
}
}
```
### Files Generated
1. **Atomic Components:**
- `./fluxwing/components/email-input.uxm`
- `./fluxwing/components/email-input.md`
- `./fluxwing/components/password-input.uxm`
- `./fluxwing/components/password-input.md`
- `./fluxwing/components/submit-button.uxm`
- `./fluxwing/components/submit-button.md`
2. **Composite Component:**
- `./fluxwing/components/login-form.uxm`
- `./fluxwing/components/login-form.md`
3. **Screen:**
- `./fluxwing/screens/login-screen.uxm`
- `./fluxwing/screens/login-screen.md`
- `./fluxwing/screens/login-screen.rendered.md`
### Output: login-form.uxm (composite)
```json
{
"id": "login-form",
"type": "form",
"version": "1.0.0",
"metadata": {
"name": "Login Form",
"description": "A form container for collecting user input. Login form",
"category": "utility"
},
"props": {
"title": "Sign In",
"components": [
{ "id": "email-input", "slot": "field-1" },
{ "id": "password-input", "slot": "field-2" },
{ "id": "submit-button", "slot": "action" }
]
},
"ascii": {
"templateFile": "login-form.md",
"width": 50,
"height": 20,
"variables": [
{
"name": "title",
"type": "string",
"required": true,
"default": "Sign In",
"description": "Form title"
}
]
}
}
```
### Output: login-form.md (composite)
```markdown
# Login Form
A form container for collecting user input. Login form
## Default State
```
╭────────────────────────────────────────────────╮
│ Sign In │
├────────────────────────────────────────────────┤
│ │
│ {{component:email-input}} │
│ │
│ {{component:password-input}} │
│ │
│ {{component:submit-button}} │
│ │
╰────────────────────────────────────────────────╯
```
## Variables
- `title` (string, required): Form title
- Default: "Sign In"
## Accessibility
- **Role**: form
- **Focusable**: No
- **ARIA Label**: Login form
---
*Generated by Fluxwing Screenshot Import*
```
### Output: login-screen.rendered.md
```markdown
# Login Screen
User authentication screen with email/password inputs
## Rendered Example
```
╭────────────────────────────────────────────────╮
│ Sign In │
├────────────────────────────────────────────────┤
│ │
│ ┌──────────────────────────────────────┐ │
│ │ Email │ │
│ │ john.doe@example.com │ │
│ └──────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────┐ │
│ │ Password │ │
│ │ •••••••• │ │
│ └──────────────────────────────────────┘ │
│ │
│ ╭──────────────────╮ │
│ │▓▓▓▓Sign In▓▓▓▓▓▓▓│ │
│ ╰──────────────────╯ │
│ │
╰────────────────────────────────────────────────╯
```
**Example Data:**
- Email: john.doe@example.com
- Password: ••••••••
- Button: Sign In
**Actions:**
- Submit form → authenticate user
- Focus management → email → password → button
## Components Used
- `email-input` - Email Input (input)
- `password-input` - Password Input (input)
- `submit-button` - Submit Button (button)
- `login-form` - Login Form (form)
---
*Generated by Fluxwing Screenshot Import*
```
---
## Example 4: Checkbox Component
### Input: Vision Analysis Data
```json
{
"id": "remember-me-checkbox",
"type": "checkbox",
"category": "atomic",
"visualProperties": {
"width": 20,
"height": 1,
"borderStyle": "none",
"textContent": "Remember me"
},
"states": ["default", "checked", "disabled"],
"accessibility": {
"role": "checkbox",
"label": "Remember login credentials"
}
}
```
### Output: remember-me-checkbox.md
```markdown
# Remember Me Checkbox
A checkbox input for boolean selection. Remember login credentials
## Default State
```
[ ] Remember me
```
## Checked State
```
[✓] Remember me
```
## Disabled State
```
[─] Remember me
```
## Variables
- `text` (string, required): checkbox label text
- Default: "Remember me"
## Accessibility
- **Role**: checkbox
- **Focusable**: Yes
- **Keyboard Support**: Space
- **ARIA Label**: Remember login credentials
---
*Generated by Fluxwing Screenshot Import*
```
---
## Example 5: Badge Component
### Input: Vision Analysis Data
```json
{
"id": "new-badge",
"type": "badge",
"category": "atomic",
"visualProperties": {
"width": 8,
"height": 1,
"borderStyle": "none",
"textContent": "New"
},
"states": ["default"],
"accessibility": {
"role": "status",
"label": "New item indicator"
}
}
```
### Output: new-badge.md
```markdown
# New Badge
A small badge component for labels or counts. New item indicator
## Default State
```
▓ New ▓
```
## Variables
- `text` (string, required): badge label text
- Default: "New"
## Accessibility
- **Role**: status
- **Focusable**: No
- **ARIA Label**: New item indicator
---
*Generated by Fluxwing Screenshot Import*
```
---
## Usage Patterns
### Pattern 1: Generate Single Atomic Component
```typescript
const timestamp = new Date().toISOString();
const uxmData = generateAtomicUXM(visionComponentData, timestamp);
const mdContent = generateAtomicMD(visionComponentData, uxmData);
await saveAtomicComponent(componentId, uxmData, mdContent);
```
### Pattern 2: Generate All Components from Screen
```typescript
// 1. Generate atomics first
for (const atomicId of composition.atomicComponents) {
const componentData = components.find(c => c.id === atomicId);
await generateAtomicComponent(componentData);
}
// 2. Generate composites (can reference atomics)
for (const compositeId of composition.compositeComponents) {
const componentData = components.find(c => c.id === compositeId);
await generateCompositeComponent(componentData);
}
// 3. Generate screen (references everything)
await generateScreen(screenData, composition);
```
---
## Reference
These examples demonstrate the complete screenshot-to-uxscii workflow:
1. **Vision analysis** produces structured JSON
2. **Helper functions** (see screenshot-import-helpers.md) transform data
3. **ASCII generation** (see screenshot-import-ascii.md) creates visual representations
4. **File generation** produces valid .uxm + .md pairs
5. **Validation** ensures quality and schema compliance
All generated files conform to:
- `fluxwing/data/schema/uxm-component.schema.json` - JSON Schema
- Quality standards documented in the schema

View File

@@ -0,0 +1,610 @@
# Screenshot Import Helper Functions
This document contains all helper functions used during screenshot-to-uxscii conversion. These functions transform vision analysis data into valid uxscii component structures.
## Component Metadata Helpers
### mapTypeToCategory()
Maps component type to UXM category for proper organization:
```typescript
function mapTypeToCategory(componentType: string): string {
const categoryMap = {
// Input category - interactive data entry
'input': 'input', 'checkbox': 'input', 'radio': 'input',
'select': 'input', 'slider': 'input', 'toggle': 'input',
// Layout category - structural containers
'container': 'layout', 'card': 'layout', 'panel': 'layout',
'tabs': 'layout', 'fieldset': 'layout',
// Display category - content presentation
'text': 'display', 'heading': 'display', 'label': 'display',
'badge': 'display', 'icon': 'display', 'image': 'display',
'divider': 'display',
// Navigation category - movement and wayfinding
'navigation': 'navigation', 'breadcrumb': 'navigation',
'pagination': 'navigation', 'link': 'navigation',
// Feedback category - system responses
'alert': 'feedback', 'toast': 'feedback', 'progress': 'feedback',
'spinner': 'feedback',
// Utility category - action triggers
'button': 'utility', 'form': 'utility',
// Overlay category - modal displays
'modal': 'overlay',
// Data category - structured information
'list': 'data', 'table': 'data', 'tree': 'data', 'chart': 'data'
};
return categoryMap[componentType] || 'custom';
}
```
### generateComponentName()
Creates human-readable component name from kebab-case ID:
```typescript
function generateComponentName(componentId: string): string {
// Convert kebab-case to Title Case
return componentId
.split('-')
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
.join(' ');
}
// Examples:
// "email-input" → "Email Input"
// "submit-button" → "Submit Button"
// "user-profile-card" → "User Profile Card"
```
### generateComponentDescription()
Creates description based on type, properties, and accessibility:
```typescript
function generateComponentDescription(
componentType: string,
visualProperties: any,
accessibility: any
): string {
const typeDescriptions = {
'button': 'An interactive button component for user actions',
'input': 'A text input field for user data entry',
'checkbox': 'A checkbox input for boolean selection',
'radio': 'A radio button for single selection from a group',
'select': 'A dropdown select component for choosing from options',
'card': 'A container component for grouping related content',
'modal': 'An overlay component for focused interactions',
'alert': 'A feedback component for displaying messages',
'navigation': 'A navigation component for site/app navigation',
'form': 'A form container for collecting user input',
'badge': 'A small badge component for labels or counts',
'icon': 'An icon component for visual symbols',
'text': 'A text display component',
'heading': 'A heading component for section titles',
'divider': 'A visual divider for separating content'
};
let description = typeDescriptions[componentType] || `A ${componentType} component`;
// Add context from accessibility label if available
if (accessibility?.label && accessibility.label !== visualProperties.textContent) {
description += `. ${accessibility.label}`;
}
return description;
}
```
## Behavior Helpers
### inferBackground()
Determines background fill pattern based on component type:
```typescript
function inferBackground(componentType: string): string {
const backgroundMap = {
'button': 'filled', // Buttons have solid background
'input': 'transparent', // Inputs are hollow
'card': 'filled', // Cards have background
'modal': 'filled', // Modals have background
'alert': 'filled', // Alerts have background
'badge': 'filled', // Badges have background
'panel': 'filled', // Panels have background
'toast': 'filled' // Toasts have background
};
return backgroundMap[componentType] || 'transparent';
}
```
### generateInteractions()
Generates interaction array based on component type:
```typescript
function generateInteractions(componentType: string): string[] {
const interactionMap = {
'button': ['click', 'keyboard'],
'input': ['click', 'keyboard', 'type'],
'checkbox': ['click', 'keyboard'],
'radio': ['click', 'keyboard'],
'select': ['click', 'keyboard'],
'slider': ['click', 'drag', 'keyboard'],
'toggle': ['click', 'keyboard'],
'link': ['click', 'keyboard'],
'tabs': ['click', 'keyboard'],
'navigation': ['click', 'keyboard'],
'modal': ['click', 'keyboard'], // Close on ESC
'toast': [], // No interaction (auto-dismiss)
'progress': [], // No interaction (passive display)
'spinner': [] // No interaction (passive display)
};
return interactionMap[componentType] || [];
}
```
### isFocusable()
Determines if component should be focusable for keyboard navigation:
```typescript
function isFocusable(componentType: string): boolean {
const focusableTypes = [
'button', 'input', 'checkbox', 'radio', 'select',
'slider', 'toggle', 'link', 'tabs', 'navigation'
];
return focusableTypes.includes(componentType);
}
```
### generateKeyboardSupport()
Generates keyboard shortcuts based on component type:
```typescript
function generateKeyboardSupport(componentType: string): string[] {
const keyboardMap = {
'button': ['Enter', 'Space'],
'input': ['Tab', 'Escape'],
'checkbox': ['Space'],
'radio': ['Arrow keys'],
'select': ['Arrow keys', 'Enter', 'Escape'],
'slider': ['Arrow keys', 'Home', 'End'],
'toggle': ['Space'],
'link': ['Enter'],
'tabs': ['Arrow keys', 'Home', 'End'],
'navigation': ['Arrow keys', 'Enter'],
'modal': ['Escape']
};
return keyboardMap[componentType] || [];
}
```
### generateStatesFromList()
Creates state objects for each state in the states array:
```typescript
function generateStatesFromList(
states: string[],
baseProperties: any,
componentType: string
): any[] {
const stateObjects = [];
for (const stateName of states) {
if (stateName === 'default') continue; // Skip default, handled separately
const stateProperties: any = {};
// State-specific border styles
if (stateName === 'hover' || stateName === 'focus') {
stateProperties.border = 'heavy';
} else if (stateName === 'disabled') {
stateProperties.border = 'dashed';
stateProperties.opacity = 0.5;
stateProperties.cursor = 'not-allowed';
} else if (stateName === 'error') {
stateProperties.border = 'heavy';
stateProperties.borderColor = 'red';
} else if (stateName === 'success') {
stateProperties.border = 'heavy';
stateProperties.borderColor = 'green';
} else if (stateName === 'loading') {
stateProperties.opacity = 0.7;
stateProperties.cursor = 'wait';
} else if (stateName === 'active') {
stateProperties.border = 'heavy';
stateProperties.background = 'filled';
}
// Copy base properties and merge with state-specific ones
stateObjects.push({
name: stateName,
properties: { ...baseProperties, ...stateProperties }
});
}
return stateObjects;
}
```
### generateMinimalDefaultState()
Creates a single default state object for MVP component creation (fast mode):
```typescript
function generateMinimalDefaultState(
visualProperties: any,
componentType: string
): any {
return {
name: 'default',
properties: {
border: visualProperties.borderStyle || 'light',
background: inferBackground(componentType),
textColor: 'default'
}
};
}
```
**Usage**: This function creates ONLY the default state, enabling fast MVP component creation. Use `generateStatesFromList()` later when expanding components with `/fluxwing-expand-component`.
**Example**:
```typescript
// Minimal mode - single state
const minimalStates = [generateMinimalDefaultState(visualProps, 'button')];
// Full mode - multiple states
const fullStates = [
generateMinimalDefaultState(visualProps, 'button'),
...generateStatesFromList(['hover', 'active', 'disabled'], baseProps, 'button')
];
```
## Layout Helpers
### inferDisplay()
Determines CSS display property based on component type:
```typescript
function inferDisplay(componentType: string): string {
const displayMap = {
'button': 'inline-block',
'input': 'inline-block',
'checkbox': 'inline-block',
'radio': 'inline-block',
'badge': 'inline',
'link': 'inline',
'text': 'inline',
'heading': 'block',
'divider': 'block',
'card': 'block',
'panel': 'block',
'modal': 'block',
'container': 'block',
'form': 'block',
'list': 'block',
'table': 'block'
};
return displayMap[componentType] || 'block';
}
```
### generateSpacing()
Calculates padding based on component dimensions (~10% of size):
```typescript
function generateSpacing(width: number, height: number): any {
const paddingX = Math.max(1, Math.floor(width * 0.1));
const paddingY = Math.max(1, Math.floor(height * 0.1));
return {
padding: {
x: paddingX,
y: paddingY
},
margin: {
x: 0,
y: 0
}
};
}
```
## Props & Variables Helpers
### extractVariables()
Extracts variable definitions from visual properties:
```typescript
function extractVariables(
visualProperties: any,
componentType: string
): any[] {
const variables: any[] = [];
// Text content variable (common to most components)
if (visualProperties.textContent) {
variables.push({
name: 'text',
type: 'string',
required: true,
default: visualProperties.textContent,
description: `${componentType} label text`
});
}
// Placeholder variable (for inputs)
if (visualProperties.placeholder) {
variables.push({
name: 'placeholder',
type: 'string',
required: false,
default: visualProperties.placeholder,
description: 'Placeholder text when empty'
});
}
// Value variable (for inputs/displays)
if (['input', 'select', 'text'].includes(componentType)) {
variables.push({
name: 'value',
type: 'string',
required: false,
default: '',
description: 'Current value'
});
}
// Variant variable (for buttons)
if (componentType === 'button') {
variables.push({
name: 'variant',
type: 'string',
required: false,
default: 'primary',
description: 'Button style variant (primary, secondary, danger)'
});
}
// Size variable (for scalable components)
if (['button', 'input', 'badge'].includes(componentType)) {
variables.push({
name: 'size',
type: 'string',
required: false,
default: 'medium',
description: 'Component size (small, medium, large)'
});
}
return variables;
}
```
### extractPropsFromVisualProperties()
Extract component props based on type and visual properties:
```typescript
function extractPropsFromVisualProperties(
visualProperties: any,
componentType: string
): any {
const props: any = {};
// Text content (most components)
if (visualProperties.textContent) {
props.text = visualProperties.textContent;
}
// Placeholder (inputs)
if (visualProperties.placeholder) {
props.placeholder = visualProperties.placeholder;
}
// Type-specific props
if (componentType === 'button') {
props.variant = inferButtonVariant(visualProperties);
props.size = inferSize(visualProperties.width, visualProperties.height);
}
if (componentType === 'input') {
props.type = inferInputType(visualProperties);
props.size = inferSize(visualProperties.width, visualProperties.height);
props.maxLength = Math.floor(visualProperties.width * 0.8);
}
if (componentType === 'checkbox' || componentType === 'radio') {
props.label = visualProperties.textContent;
props.checked = false; // Default unchecked
}
if (componentType === 'badge') {
props.variant = inferBadgeVariant(visualProperties);
}
if (componentType === 'icon') {
props.name = visualProperties.textContent || 'icon';
props.size = inferSize(visualProperties.width, visualProperties.height);
}
return props;
}
```
## Inference Helpers
### inferButtonVariant()
```typescript
function inferButtonVariant(vp: any): string {
// Primary buttons typically have filled backgrounds
// Secondary buttons have borders
if (vp.borderStyle === 'heavy' || vp.borderStyle === 'double') {
return 'primary';
}
return 'secondary';
}
```
### inferInputType()
```typescript
function inferInputType(vp: any): string {
const text = vp.textContent?.toLowerCase() || '';
const placeholder = vp.placeholder?.toLowerCase() || '';
if (text.includes('password') || placeholder.includes('password')) {
return 'password';
}
if (text.includes('email') || placeholder.includes('email')) {
return 'email';
}
if (text.includes('number') || placeholder.includes('number')) {
return 'number';
}
if (text.includes('search')) {
return 'search';
}
return 'text';
}
```
### inferSize()
```typescript
function inferSize(width: number, height: number): string {
// Small: < 20 width or < 3 height
// Medium: 20-40 width, 3-5 height
// Large: > 40 width or > 5 height
if (width < 20 || height < 3) return 'small';
if (width > 40 || height > 5) return 'large';
return 'medium';
}
```
### inferBadgeVariant()
```typescript
function inferBadgeVariant(vp: any): string {
// Could be inferred from color analysis in future
// For now, default to neutral
return 'neutral';
}
```
### inferAdditionalTags()
```typescript
function inferAdditionalTags(type: string, vp: any): string[] {
const tags: string[] = [];
// Add category tags
const category = mapTypeToCategory(type);
if (category !== 'custom') {
tags.push(category);
}
// Add interaction tags
if (isFocusable(type)) {
tags.push('interactive');
}
return tags;
}
```
## Usage Pattern
These helper functions work together to transform vision analysis into complete .uxm files:
```typescript
// Example: Generate atomic component .uxm
function generateAtomicUXM(componentData: any, timestamp: string): any {
const { id, type, visualProperties, states, accessibility } = componentData;
return {
"id": id,
"type": type,
"version": "1.0.0",
"metadata": {
"name": generateComponentName(id),
"description": generateComponentDescription(type, visualProperties, accessibility),
"author": "Fluxwing Screenshot Import",
"created": timestamp,
"modified": timestamp,
"tags": [type, "imported", "screenshot-generated", ...inferAdditionalTags(type, visualProperties)],
"category": mapTypeToCategory(type)
},
"props": extractPropsFromVisualProperties(visualProperties, type),
"behavior": {
"states": [
{
"name": "default",
"properties": {
"border": visualProperties.borderStyle,
"background": inferBackground(type),
"textColor": "default"
}
},
...generateStatesFromList(
states.filter(s => s !== 'default'),
{ border: visualProperties.borderStyle, background: inferBackground(type), textColor: "default" },
type
)
],
"interactions": generateInteractions(type),
"accessibility": {
"role": accessibility.role,
"focusable": isFocusable(type),
"keyboardSupport": generateKeyboardSupport(type),
"ariaLabel": accessibility.label || visualProperties.textContent
}
},
"layout": {
"display": inferDisplay(type),
"positioning": "static",
"spacing": generateSpacing(visualProperties.width, visualProperties.height),
"sizing": {
"minWidth": visualProperties.width,
"height": visualProperties.height
}
},
"ascii": {
"templateFile": `${id}.md`,
"width": visualProperties.width,
"height": visualProperties.height,
"variables": extractVariables(visualProperties, type)
}
};
}
```
## Reference
These functions implement the uxscii component specification documented in:
- `fluxwing/data/docs/01-uxscii-specification.md` - Format specification
- `fluxwing/data/docs/03-component-creation.md` - Component structure
- `fluxwing/data/schema/uxm-component.schema.json` - JSON Schema validation
Use these helpers to ensure consistent, valid component generation from screenshot analysis data.

View File

@@ -0,0 +1,237 @@
# Screenshot Import Screen Generation
This document provides helper functions for generating screen files (.uxm, .md, .rendered.md) during screenshot import.
## Overview
Screen generation creates 3 files:
1. **`.uxm`** - Screen metadata and component references
2. **`.md`** - Template with `{{component:id}}` references
3. **`.rendered.md`** - Actual ASCII preview with real data
## Step 1: Generate Screen Metadata (.uxm)
```typescript
function generateScreenUxm(mergedData) {
const screenId = `${mergedData.screen.type}-screen`;
const screenName = mergedData.screen.name;
const screenDescription = mergedData.screen.description;
const allComponentIds = [
...mergedData.composition.atomicComponents,
...mergedData.composition.compositeComponents
];
return {
"id": screenId,
"type": "container",
"version": "1.0.0",
"metadata": {
"name": screenName,
"description": screenDescription,
"author": "Fluxwing Screenshot Import",
"created": new Date().toISOString(),
"modified": new Date().toISOString(),
"tags": ["screen", mergedData.screen.type, "imported"],
"category": "layout"
},
"props": {
"title": screenName,
"layout": mergedData.screen.layout,
"components": allComponentIds
},
"ascii": {
"templateFile": `${screenId}.md`,
"width": 80,
"height": 40
}
};
}
```
## Step 2: Generate Screen Template (.md)
```typescript
function generateScreenTemplate(screenId, screenName, description, components, mergedData) {
let markdown = `# ${screenName}\n\n${description}\n\n`;
markdown += `## Layout\n\n\`\`\`\n`;
// Reference all components
for (const compId of components) {
markdown += `{{component:${compId}}}\n\n`;
}
markdown += '\`\`\`\n\n';
markdown += `## Components Used\n\n`;
for (const compId of components) {
const comp = mergedData.components.find(c => c.id === compId);
const compName = comp ? generateComponentName(compId) : compId;
markdown += `- \`${compId}\` - ${compName} (${comp?.type || 'unknown'})\n`;
}
return markdown;
}
// Helper: Convert kebab-case ID to Title Case name
function generateComponentName(id) {
return id.split('-').map(word =>
word.charAt(0).toUpperCase() + word.slice(1)
).join(' ');
}
```
## Step 3: Generate Rendered Screen (.rendered.md)
**CRITICAL:** Embed actual ASCII from component files, NOT `{{variables}}`
```typescript
async function generateScreenRendered(screenId, screenName, description, mergedData) {
let markdown = `# ${screenName}\n\n`;
markdown += `## Rendered Example\n\n\`\`\`\n`;
// Build complete rendered layout
// Stack all components vertically (user can refine layout)
const allComponentIds = [
...mergedData.composition.atomicComponents,
...mergedData.composition.compositeComponents
];
for (const compId of allComponentIds) {
// Read component .md file
const compMdPath = `./fluxwing/components/${compId}.md`;
try {
const compMdContent = await read(compMdPath);
// Extract default state ASCII (between first ``` pair after "## Default State")
const asciiMatch = compMdContent.match(/## Default State\n\n```\n([\s\S]*?)\n```/);
if (asciiMatch) {
markdown += asciiMatch[1] + '\n\n';
} else {
markdown += `[Component ${compId} - ASCII not found]\n\n`;
}
} catch (error) {
markdown += `[Component ${compId} - File not found]\n\n`;
}
}
markdown += '\`\`\`\n\n';
// Add example data section
markdown += `**Example Data:**\n`;
const exampleData = generateExampleData(mergedData.screen.type);
for (const [key, value] of Object.entries(exampleData)) {
markdown += `- ${key}: ${value}\n`;
}
return markdown;
}
```
## Step 4: Generate Example Data by Screen Type
```typescript
function generateExampleData(screenType) {
const examples = {
"login": {
"Email": "john.doe@example.com",
"Password": "••••••••",
"Button": "Sign In"
},
"dashboard": {
"Revenue": "$24,567",
"Users": "1,234",
"Growth": "+12.5%"
},
"profile": {
"Name": "Jane Smith",
"Role": "Product Manager",
"Email": "jane.smith@company.com"
},
"settings": {
"Theme": "Dark Mode",
"Language": "English",
"Notifications": "Enabled"
},
"form": {
"Field 1": "Example value",
"Field 2": "Another value"
}
};
return examples[screenType] || examples["form"];
}
```
## Step 5: Write All Files Concurrently
```typescript
async function writeScreenFiles(screenId, screenUxm, screenMd, screenRendered) {
// Create screens directory
await bash('mkdir -p ./fluxwing/screens');
const screenDir = './fluxwing/screens';
const uxmPath = `${screenDir}/${screenId}.uxm`;
const mdPath = `${screenDir}/${screenId}.md`;
const renderedPath = `${screenDir}/${screenId}.rendered.md`;
// Write all 3 files concurrently
await Promise.all([
write(uxmPath, JSON.stringify(screenUxm, null, 2)),
write(mdPath, screenMd),
write(renderedPath, screenRendered)
]);
return { uxmPath, mdPath, renderedPath };
}
```
## Complete Workflow
```typescript
async function generateScreen(mergedData) {
const screenId = `${mergedData.screen.type}-screen`;
const allComponentIds = [
...mergedData.composition.atomicComponents,
...mergedData.composition.compositeComponents
];
// Generate all content
const screenUxm = generateScreenUxm(mergedData);
const screenMd = generateScreenTemplate(
screenId,
mergedData.screen.name,
mergedData.screen.description,
allComponentIds,
mergedData
);
const screenRendered = await generateScreenRendered(
screenId,
mergedData.screen.name,
mergedData.screen.description,
mergedData
);
// Write all files
const paths = await writeScreenFiles(screenId, screenUxm, screenMd, screenRendered);
console.log(`✓ Created: ${paths.uxmPath}`);
console.log(`✓ Created: ${paths.mdPath}`);
console.log(`✓ Created: ${paths.renderedPath}`);
return { screenId, ...paths };
}
```
## Usage
```typescript
try {
const screenResult = await generateScreen(mergedData);
console.log(`✅ Screen generated with ${allComponentIds.length} components`);
} catch (error) {
console.error(`❌ Failed to generate screen: ${error.message}`);
throw error;
}
```

View File

@@ -0,0 +1,221 @@
# Screenshot Import Validation Functions
This document provides validation function implementations for verifying generated uxscii components during screenshot import.
## Overview
Five validation checks run concurrently for each component:
1. **validateSchema()** - Check against JSON Schema
2. **validateFileIntegrity()** - Check template file exists and matches
3. **validateVariableConsistency()** - Check variables are defined and used
4. **validateComponentReferences()** - Check referenced components exist
5. **validateBestPractices()** - Check quality standards (warnings only)
## Validation Function Implementations
### 1. validateSchema()
Check against JSON Schema:
```typescript
async function validateSchema(uxmPath) {
const schemaPath = '{PLUGIN_ROOT}/data/schema/uxm-component.schema.json';
const schema = JSON.parse(await read(schemaPath));
const uxmData = JSON.parse(await read(uxmPath));
const errors = [];
// Required fields
if (!uxmData.id || !uxmData.type || !uxmData.version || !uxmData.metadata) {
errors.push("Missing required top-level fields");
}
// ID format
if (uxmData.id && !uxmData.id.match(/^[a-z0-9]+(?:-[a-z0-9]+)*$/)) {
errors.push(`Invalid ID format: ${uxmData.id}`);
}
// Version format
if (uxmData.version && !uxmData.version.match(/^\d+\.\d+\.\d+$/)) {
errors.push(`Invalid version format: ${uxmData.version}`);
}
if (errors.length > 0) {
throw new Error(`Schema validation errors: ${errors.join(', ')}`);
}
return { passed: true, message: "Schema compliance verified" };
}
```
### 2. validateFileIntegrity()
Check template file exists and matches:
```typescript
async function validateFileIntegrity(uxmPath, mdPath) {
const uxmData = JSON.parse(await read(uxmPath));
const expectedTemplateName = uxmData.ascii.templateFile;
// Check .md file exists
try {
await read(mdPath);
} catch (error) {
throw new Error(`Template file missing: ${mdPath}`);
}
// Check filename matches reference
const actualFileName = mdPath.split('/').pop();
if (expectedTemplateName !== actualFileName) {
throw new Error(`Template filename mismatch: expected ${expectedTemplateName}, got ${actualFileName}`);
}
return { passed: true, message: "File integrity verified" };
}
```
### 3. validateVariableConsistency()
Check variables are defined and used:
```typescript
async function validateVariableConsistency(uxmPath, mdPath) {
const uxmData = JSON.parse(await read(uxmPath));
const mdContent = await read(mdPath);
// Extract defined variables from .uxm
const definedVariables = uxmData.ascii?.variables ? uxmData.ascii.variables.map(v => v.name) : [];
// Extract used variables from .md (matches {{variableName}} but NOT {{component:id}})
const usedVariables = [...mdContent.matchAll(/\{\{(?!component:)(\w+)\}\}/g)].map(m => m[1]);
// Check all used variables are defined
const undefinedVars = usedVariables.filter(v => !definedVariables.includes(v));
if (undefinedVars.length > 0) {
throw new Error(`Undefined variables in template: ${undefinedVars.join(', ')}`);
}
// Warn about unused variables (non-blocking)
const unusedVars = definedVariables.filter(v => !usedVariables.includes(v));
if (unusedVars.length > 0) {
console.warn(`⚠️ Unused variables defined in ${uxmPath}: ${unusedVars.join(', ')}`);
}
return { passed: true, message: "Variable consistency verified", warnings: unusedVars.length };
}
```
### 4. validateComponentReferences()
Check referenced components exist:
```typescript
async function validateComponentReferences(uxmPath) {
const uxmData = JSON.parse(await read(uxmPath));
// Check if component has child references
if (!uxmData.props.components || uxmData.props.components.length === 0) {
return { passed: true, message: "No component references to validate" };
}
// Check each referenced component exists
for (const ref of uxmData.props.components) {
const refPath = `./fluxwing/components/${ref.id}.uxm`;
try {
await read(refPath);
} catch (error) {
throw new Error(`Referenced component not found: ${ref.id} (expected at ${refPath})`);
}
}
return { passed: true, message: `${uxmData.props.components.length} component references verified` };
}
```
### 5. validateBestPractices()
Check quality standards (warnings only):
```typescript
async function validateBestPractices(uxmPath) {
const uxmData = JSON.parse(await read(uxmPath));
const warnings = [];
// Check multiple states
if (uxmData.behavior?.states && uxmData.behavior.states.length < 3) {
warnings.push("Consider adding more states (recommended: 3+)");
}
// Check accessibility
if (!uxmData.behavior?.accessibility?.ariaLabel) {
warnings.push("Missing ARIA label for accessibility");
}
// Check description
if (!uxmData.metadata.description || uxmData.metadata.description.length < 10) {
warnings.push("Description is too brief");
}
// Check tags
if (!uxmData.metadata.tags || uxmData.metadata.tags.length < 2) {
warnings.push("Add more tags for discoverability");
}
if (warnings.length > 0) {
console.warn(`⚠️ Best practices warnings for ${uxmData.id}:\n - ${warnings.join('\n - ')}`);
}
return { passed: true, warnings: warnings.length, messages: warnings };
}
```
## Usage Pattern
Run all 5 validations concurrently for each component:
```typescript
const validationResults = await Promise.all(
allFiles.map(async (fileSet) => {
const { uxmPath, mdPath, id } = fileSet;
try {
const [
schemaResult,
integrityResult,
variableResult,
referenceResult,
practicesResult
] = await Promise.all([
validateSchema(uxmPath),
validateFileIntegrity(uxmPath, mdPath),
validateVariableConsistency(uxmPath, mdPath),
validateComponentReferences(uxmPath),
validateBestPractices(uxmPath)
]);
return {
componentId: id,
success: true,
checks: {
schema: schemaResult,
integrity: integrityResult,
variables: variableResult,
references: referenceResult,
practices: practicesResult
}
};
} catch (error) {
throw new Error(`Validation failed for ${id}: ${error.message}`);
}
})
);
```
## Error Handling
- **Schema, Integrity, Variables, References**: Fail-fast (throw errors)
- **Best Practices**: Non-blocking (warnings only)
- Always provide component ID in error messages
- Collect all warnings for summary report