mirror of
https://github.com/The-Low-Code-Foundation/OpenNoodl.git
synced 2026-01-12 07:12:54 +01:00
23 KiB
23 KiB
AI-004: AI Design Assistance
Overview
Provide AI-powered design feedback and improvements. Analyze components for design issues (accessibility, consistency, spacing) and suggest or auto-apply fixes. Transform rough layouts into polished designs.
Context
Many Noodl users are developers or designers who may not have deep expertise in both areas. Common issues include:
- Inconsistent spacing and alignment
- Accessibility problems (contrast, touch targets)
- Missing hover/focus states
- Unbalanced layouts
- Poor color combinations
AI Design Assistance provides:
- Automated design review
- One-click fixes for common issues
- Style consistency enforcement
- Accessibility compliance checking
- Layout optimization suggestions
Requirements
Functional Requirements
-
Design Analysis
- Scan component/page for issues
- Categorize by severity (error, warning, info)
- Group by type (spacing, color, typography, etc.)
- Provide explanations
-
Issue Categories
- Accessibility: Contrast, touch targets, labels
- Consistency: Spacing, colors, typography
- Layout: Alignment, balance, whitespace
- Interaction: Hover, focus, active states
- Responsiveness: Breakpoint issues
-
Fix Application
- One-click fix for individual issues
- "Fix all" for category
- Preview before applying
- Explain what was fixed
-
Design Improvement
- "Polish this" command
- Transform rough layouts
- Suggest design alternatives
- Apply consistent styling
-
Design System Enforcement
- Check against project styles
- Suggest using existing styles
- Identify one-off values
- Propose new styles
Non-Functional Requirements
- Analysis completes in < 5 seconds
- Fixes don't break functionality
- Respects existing design intent
- Works with any component structure
Technical Approach
1. Design Analysis Service
// packages/noodl-editor/src/editor/src/services/DesignAnalysisService.ts
interface DesignIssue {
id: string;
type: IssueType;
severity: 'error' | 'warning' | 'info';
category: IssueCategory;
node: NodeGraphNode;
property?: string;
message: string;
explanation: string;
fix?: DesignFix;
}
enum IssueCategory {
ACCESSIBILITY = 'accessibility',
CONSISTENCY = 'consistency',
LAYOUT = 'layout',
INTERACTION = 'interaction',
RESPONSIVENESS = 'responsiveness'
}
interface DesignFix {
description: string;
changes: PropertyChange[];
preview?: string;
}
class DesignAnalysisService {
private static instance: DesignAnalysisService;
private analyzers: DesignAnalyzer[] = [];
// Analysis
async analyzeComponent(component: ComponentModel): Promise<DesignIssue[]>;
async analyzePage(page: ComponentModel): Promise<DesignIssue[]>;
async analyzeProject(project: ProjectModel): Promise<DesignIssue[]>;
// Fixes
async applyFix(issue: DesignIssue): Promise<void>;
async applyAllFixes(issues: DesignIssue[]): Promise<void>;
async previewFix(issue: DesignIssue): Promise<FixPreview>;
// Polish
async polishComponent(component: ComponentModel): Promise<PolishResult>;
async suggestImprovements(component: ComponentModel): Promise<Improvement[]>;
// Design system
async checkDesignSystem(component: ComponentModel): Promise<DesignSystemIssue[]>;
async suggestStyles(component: ComponentModel): Promise<StyleSuggestion[]>;
}
2. Design Analyzers
// packages/noodl-editor/src/editor/src/services/ai/analyzers/
// Base analyzer
interface DesignAnalyzer {
name: string;
category: IssueCategory;
analyze(component: ComponentModel): DesignIssue[];
}
// Accessibility Analyzer
class AccessibilityAnalyzer implements DesignAnalyzer {
name = 'Accessibility';
category = IssueCategory.ACCESSIBILITY;
analyze(component: ComponentModel): DesignIssue[] {
const issues: DesignIssue[] = [];
component.forEachNode(node => {
// Check contrast
if (this.hasTextAndBackground(node)) {
const contrast = this.calculateContrast(
node.parameters.color,
node.parameters.backgroundColor
);
if (contrast < 4.5) {
issues.push({
type: 'low-contrast',
severity: contrast < 3 ? 'error' : 'warning',
category: IssueCategory.ACCESSIBILITY,
node,
message: `Low color contrast (${contrast.toFixed(1)}:1)`,
explanation: 'WCAG requires 4.5:1 for normal text',
fix: {
description: 'Adjust colors for better contrast',
changes: this.suggestContrastFix(node)
}
});
}
}
// Check touch targets
if (this.isInteractive(node)) {
const size = this.getSize(node);
if (size.width < 44 || size.height < 44) {
issues.push({
type: 'small-touch-target',
severity: 'warning',
category: IssueCategory.ACCESSIBILITY,
node,
message: 'Touch target too small',
explanation: 'Minimum 44x44px recommended for touch',
fix: {
description: 'Increase size to 44x44px minimum',
changes: [
{ property: 'width', value: Math.max(size.width, 44) },
{ property: 'height', value: Math.max(size.height, 44) }
]
}
});
}
}
// Check labels
if (this.isFormInput(node) && !this.hasLabel(node)) {
issues.push({
type: 'missing-label',
severity: 'error',
category: IssueCategory.ACCESSIBILITY,
node,
message: 'Form input missing label',
explanation: 'Screen readers need labels to identify inputs'
});
}
});
return issues;
}
}
// Consistency Analyzer
class ConsistencyAnalyzer implements DesignAnalyzer {
name = 'Consistency';
category = IssueCategory.CONSISTENCY;
analyze(component: ComponentModel): DesignIssue[] {
const issues: DesignIssue[] = [];
// Collect all values
const spacings = this.collectSpacings(component);
const colors = this.collectColors(component);
const fontSizes = this.collectFontSizes(component);
// Check for one-offs
const spacingOneOffs = this.findOneOffs(spacings, SPACING_SCALE);
const colorOneOffs = this.findOneOffs(colors, component.colorStyles);
const fontOneOffs = this.findOneOffs(fontSizes, FONT_SCALE);
// Report issues
for (const oneOff of spacingOneOffs) {
issues.push({
type: 'inconsistent-spacing',
severity: 'info',
category: IssueCategory.CONSISTENCY,
node: oneOff.node,
property: oneOff.property,
message: `Non-standard spacing: ${oneOff.value}`,
explanation: 'Consider using a standard spacing value',
fix: {
description: `Change to ${oneOff.suggestion}`,
changes: [{ property: oneOff.property, value: oneOff.suggestion }]
}
});
}
return issues;
}
}
// Layout Analyzer
class LayoutAnalyzer implements DesignAnalyzer {
name = 'Layout';
category = IssueCategory.LAYOUT;
analyze(component: ComponentModel): DesignIssue[] {
const issues: DesignIssue[] = [];
// Check alignment
const alignmentIssues = this.checkAlignment(component);
issues.push(...alignmentIssues);
// Check whitespace balance
const whitespaceIssues = this.checkWhitespace(component);
issues.push(...whitespaceIssues);
// Check visual hierarchy
const hierarchyIssues = this.checkHierarchy(component);
issues.push(...hierarchyIssues);
return issues;
}
}
// Interaction Analyzer
class InteractionAnalyzer implements DesignAnalyzer {
name = 'Interaction';
category = IssueCategory.INTERACTION;
analyze(component: ComponentModel): DesignIssue[] {
const issues: DesignIssue[] = [];
component.forEachNode(node => {
if (this.isInteractive(node)) {
// Check hover state
if (!this.hasHoverState(node)) {
issues.push({
type: 'missing-hover',
severity: 'warning',
category: IssueCategory.INTERACTION,
node,
message: 'Missing hover state',
explanation: 'Interactive elements should have hover feedback',
fix: {
description: 'Add subtle hover effect',
changes: this.generateHoverState(node)
}
});
}
// Check focus state
if (!this.hasFocusState(node)) {
issues.push({
type: 'missing-focus',
severity: 'error',
category: IssueCategory.INTERACTION,
node,
message: 'Missing focus state',
explanation: 'Keyboard users need visible focus indicators'
});
}
}
});
return issues;
}
}
3. AI Polish Engine
// packages/noodl-editor/src/editor/src/services/ai/PolishEngine.ts
interface PolishResult {
before: ComponentSnapshot;
after: ComponentSnapshot;
changes: Change[];
explanation: string;
}
class PolishEngine {
async polishComponent(component: ComponentModel): Promise<PolishResult> {
// 1. Analyze current state
const issues = await DesignAnalysisService.instance.analyzeComponent(component);
// 2. Apply automatic fixes
const autoFixable = issues.filter(i => i.fix && i.severity !== 'error');
for (const issue of autoFixable) {
await this.applyFix(issue);
}
// 3. Use AI for creative improvements
const aiImprovements = await this.getAiImprovements(component);
// 4. Apply AI suggestions
for (const improvement of aiImprovements) {
await this.applyImprovement(improvement);
}
return {
before: this.originalSnapshot,
after: this.currentSnapshot,
changes: this.recordedChanges,
explanation: this.generateExplanation()
};
}
private async getAiImprovements(component: ComponentModel): Promise<Improvement[]> {
const prompt = `Analyze this Noodl component and suggest design improvements:
Component structure:
${this.serializeComponent(component)}
Current styles:
${this.serializeStyles(component)}
Suggest improvements for:
1. Visual hierarchy
2. Whitespace and breathing room
3. Color harmony
4. Typography refinement
5. Micro-interactions
Output JSON array of improvements with property changes.`;
const response = await this.anthropicClient.complete(prompt);
return JSON.parse(response);
}
}
4. UI Components
Design Review Panel
┌─────────────────────────────────────────────────────────────────────┐
│ Design Review [×] │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 📊 Overview │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ 🔴 2 Errors 🟡 5 Warnings 🔵 3 Info │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ 🔴 ERRORS [Fix All (2)] │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ ♿ Low color contrast on "Submit" button [Fix] │ │
│ │ Contrast ratio 2.1:1, needs 4.5:1 │ │
│ ├─────────────────────────────────────────────────────────────────┤ │
│ │ ♿ Missing label on email input [Fix] │ │
│ │ Screen readers cannot identify this input │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ 🟡 WARNINGS [Fix All (5)] │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ 📐 Inconsistent spacing (12px vs 16px scale) [Fix] │ │
│ │ 👆 Touch target too small (32x32px) [Fix] │ │
│ │ ✨ Missing hover state on buttons [Fix] │ │
│ │ ... │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ [Analyze Again] [✨ Polish All] │
└─────────────────────────────────────────────────────────────────────┘
Polish Preview
┌─────────────────────────────────────────────────────────────────────┐
│ ✨ Polish Preview [×] │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ BEFORE AFTER │
│ ┌────────────────────────┐ ┌────────────────────────┐ │
│ │ ┌──────────────────┐ │ │ ┌──────────────────┐ │ │
│ │ │ Cramped card │ │ │ │ │ │ │
│ │ │ No shadow │ │ → │ │ Polished card │ │ │
│ │ │ Basic button │ │ │ │ with shadow │ │ │
│ │ └──────────────────┘ │ │ │ and spacing │ │ │
│ │ │ │ └──────────────────┘ │ │
│ └────────────────────────┘ └────────────────────────┘ │
│ │
│ Changes Applied: │
│ • Added 24px padding to card │
│ • Added subtle shadow (0 2px 8px rgba(0,0,0,0.1)) │
│ • Increased button padding (12px 24px) │
│ • Added hover state with 0.95 scale │
│ • Adjusted border radius to 12px │
│ │
│ [Revert] [Apply Polish] │
└─────────────────────────────────────────────────────────────────────┘
5. Design Rules Engine
// packages/noodl-editor/src/editor/src/services/ai/DesignRules.ts
interface DesignRule {
id: string;
name: string;
description: string;
category: IssueCategory;
severity: 'error' | 'warning' | 'info';
check: (node: NodeGraphNode, context: DesignContext) => RuleViolation | null;
fix?: (violation: RuleViolation) => PropertyChange[];
}
const DESIGN_RULES: DesignRule[] = [
// Accessibility
{
id: 'min-contrast',
name: 'Minimum Color Contrast',
description: 'Text must have sufficient contrast with background',
category: IssueCategory.ACCESSIBILITY,
severity: 'error',
check: (node, ctx) => {
if (!hasTextAndBackground(node)) return null;
const contrast = calculateContrast(node.parameters.color, node.parameters.backgroundColor);
if (contrast < 4.5) {
return { node, contrast, required: 4.5 };
}
return null;
},
fix: (violation) => suggestContrastFix(violation.node, violation.required)
},
{
id: 'min-touch-target',
name: 'Minimum Touch Target Size',
description: 'Interactive elements must be at least 44x44px',
category: IssueCategory.ACCESSIBILITY,
severity: 'warning',
check: (node) => {
if (!isInteractive(node)) return null;
const size = getSize(node);
if (size.width < 44 || size.height < 44) {
return { node, size, required: { width: 44, height: 44 } };
}
return null;
},
fix: (violation) => [
{ property: 'width', value: Math.max(violation.size.width, 44) },
{ property: 'height', value: Math.max(violation.size.height, 44) }
]
},
// Consistency
{
id: 'spacing-scale',
name: 'Use Spacing Scale',
description: 'Spacing should follow the design system scale',
category: IssueCategory.CONSISTENCY,
severity: 'info',
check: (node) => {
const spacing = getSpacingValues(node);
const nonStandard = spacing.filter(s => !SPACING_SCALE.includes(s));
if (nonStandard.length > 0) {
return { node, nonStandard, scale: SPACING_SCALE };
}
return null;
},
fix: (violation) => violation.nonStandard.map(s => ({
property: s.property,
value: findClosest(s.value, SPACING_SCALE)
}))
},
// Interaction
{
id: 'hover-state',
name: 'Interactive Hover State',
description: 'Interactive elements should have hover feedback',
category: IssueCategory.INTERACTION,
severity: 'warning',
check: (node) => {
if (isInteractive(node) && !hasHoverState(node)) {
return { node };
}
return null;
},
fix: (violation) => generateDefaultHoverState(violation.node)
},
// ... more rules
];
Files to Create
packages/noodl-editor/src/editor/src/services/DesignAnalysisService.tspackages/noodl-editor/src/editor/src/services/ai/analyzers/AccessibilityAnalyzer.tspackages/noodl-editor/src/editor/src/services/ai/analyzers/ConsistencyAnalyzer.tspackages/noodl-editor/src/editor/src/services/ai/analyzers/LayoutAnalyzer.tspackages/noodl-editor/src/editor/src/services/ai/analyzers/InteractionAnalyzer.tspackages/noodl-editor/src/editor/src/services/ai/PolishEngine.tspackages/noodl-editor/src/editor/src/services/ai/DesignRules.tspackages/noodl-core-ui/src/components/ai/DesignReviewPanel/DesignReviewPanel.tsxpackages/noodl-core-ui/src/components/ai/DesignIssueCard/DesignIssueCard.tsxpackages/noodl-core-ui/src/components/ai/PolishPreview/PolishPreview.tsx
Files to Modify
-
packages/noodl-editor/src/editor/src/pages/EditorPage/EditorPage.tsx- Add Design Review panel toggle
- Add menu option
-
packages/noodl-editor/src/editor/src/views/panels/propertiespanel/- Add issue indicators on properties
- Quick fix buttons
-
packages/noodl-editor/src/editor/src/views/nodegrapheditor.js- Highlight nodes with issues
- Add "Polish" context menu
Implementation Steps
Phase 1: Analysis Infrastructure
- Create DesignAnalysisService
- Define issue types and categories
- Create analyzer base class
- Implement fix application
Phase 2: Core Analyzers
- Implement AccessibilityAnalyzer
- Implement ConsistencyAnalyzer
- Implement LayoutAnalyzer
- Implement InteractionAnalyzer
Phase 3: Polish Engine
- Create PolishEngine
- Implement auto-fix application
- Add AI improvement suggestions
- Generate explanations
Phase 4: UI - Review Panel
- Create DesignReviewPanel
- Create DesignIssueCard
- Group issues by category
- Add fix buttons
Phase 5: UI - Polish Preview
- Create PolishPreview
- Show before/after
- List changes
- Apply/revert actions
Phase 6: Integration
- Add to editor menus
- Highlight issues on canvas
- Add keyboard shortcuts
Testing Checklist
- Accessibility issues detected
- Contrast calculation accurate
- Touch target check works
- Consistency issues found
- Fixes don't break layout
- Polish improves design
- Preview accurate
- Undo works
- Performance acceptable
- Works on all component types
Dependencies
- AI-001 (AI Project Scaffolding) - for AnthropicClient
- AI-003 (Natural Language Editing) - for change application
Blocked By
- AI-001
Blocks
- None (final task in AI series)
Estimated Effort
- Analysis service: 4-5 hours
- Accessibility analyzer: 4-5 hours
- Consistency analyzer: 3-4 hours
- Layout analyzer: 3-4 hours
- Interaction analyzer: 3-4 hours
- Polish engine: 5-6 hours
- UI review panel: 4-5 hours
- UI polish preview: 3-4 hours
- Integration: 3-4 hours
- Total: 32-41 hours
Success Criteria
- Issues detected accurately
- Fixes don't break functionality
- Polish improves design quality
- Accessibility issues caught
- One-click fixes work
- Preview shows accurate changes
Design Rules Categories
Accessibility (WCAG)
- Color contrast (4.5:1 text, 3:1 large)
- Touch targets (44x44px)
- Focus indicators
- Label associations
- Alt text for images
Consistency
- Spacing scale adherence
- Color from palette
- Typography scale
- Border radius consistency
- Shadow consistency
Layout
- Alignment on grid
- Balanced whitespace
- Visual hierarchy
- Content grouping
Interaction
- Hover states
- Focus states
- Active states
- Loading states
- Error states
Future Enhancements
- Design system integration
- Custom rule creation
- Team design standards
- A/B testing suggestions
- Animation review
- Performance impact analysis