mirror of
https://github.com/The-Low-Code-Foundation/OpenNoodl.git
synced 2026-01-12 15:22:55 +01:00
Added styles overhaul task docs
This commit is contained in:
@@ -0,0 +1,639 @@
|
||||
# STYLE-003: Style Presets System
|
||||
|
||||
## Overview
|
||||
|
||||
Create a library of style presets (Modern, Minimal, Playful, Enterprise) that users can choose when creating a new project. Each preset defines token values that cascade through the entire style system.
|
||||
|
||||
**Phase:** 8 (Styles Overhaul)
|
||||
**Priority:** HIGH
|
||||
**Effort:** 8-12 hours
|
||||
**Risk:** Low
|
||||
**Dependencies:** STYLE-001 (Token System), STYLE-002 (Element Configs)
|
||||
|
||||
---
|
||||
|
||||
## Background
|
||||
|
||||
### The Goal
|
||||
|
||||
When a user creates a new project, they should be able to:
|
||||
|
||||
1. Choose a visual style preset from a gallery
|
||||
2. See live previews of what their UI will look like
|
||||
3. Start building immediately with consistent, professional styling
|
||||
4. Customize the preset later if desired
|
||||
|
||||
### Why Presets Matter
|
||||
|
||||
- **Beginners**: Don't know what colors/spacing to choose
|
||||
- **Speed**: Skip the "design token setup" phase
|
||||
- **Consistency**: Entire project has cohesive look from start
|
||||
- **Exploration**: Easy to try different aesthetics
|
||||
|
||||
---
|
||||
|
||||
## Preset Definitions
|
||||
|
||||
### Modern (Default)
|
||||
|
||||
A clean, professional look with subtle shadows and medium rounding. Works for most applications.
|
||||
|
||||
```typescript
|
||||
const ModernPreset: StylePreset = {
|
||||
name: 'Modern',
|
||||
description: 'Clean and professional with subtle depth',
|
||||
|
||||
tokens: {
|
||||
// Colors
|
||||
'--primary': '#3b82f6', // Blue
|
||||
'--primary-hover': '#2563eb',
|
||||
'--primary-foreground': '#ffffff',
|
||||
'--secondary': '#64748b',
|
||||
'--secondary-hover': '#475569',
|
||||
'--secondary-foreground': '#ffffff',
|
||||
'--destructive': '#ef4444',
|
||||
'--destructive-hover': '#dc2626',
|
||||
'--destructive-foreground': '#ffffff',
|
||||
'--muted': '#f1f5f9',
|
||||
'--muted-foreground': '#64748b',
|
||||
'--accent': '#f1f5f9',
|
||||
'--accent-foreground': '#0f172a',
|
||||
'--background': '#ffffff',
|
||||
'--foreground': '#0f172a',
|
||||
'--surface': '#f8fafc',
|
||||
'--border': '#e2e8f0',
|
||||
'--border-subtle': '#f1f5f9',
|
||||
'--ring': '#3b82f6',
|
||||
|
||||
// Typography
|
||||
'--font-sans': 'Inter, ui-sans-serif, system-ui, sans-serif',
|
||||
|
||||
// Borders
|
||||
'--radius-sm': '4px',
|
||||
'--radius-md': '8px',
|
||||
'--radius-lg': '12px',
|
||||
|
||||
// Shadows
|
||||
'--shadow-sm': '0 1px 2px 0 rgb(0 0 0 / 0.05)',
|
||||
'--shadow-md': '0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1)',
|
||||
},
|
||||
|
||||
preview: {
|
||||
primaryButton: '#3b82f6',
|
||||
cardBackground: '#ffffff',
|
||||
cardBorder: '#e2e8f0',
|
||||
textColor: '#0f172a',
|
||||
radius: '8px',
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
### Minimal
|
||||
|
||||
Ultra-clean with sharp corners, no shadows, and monochromatic palette.
|
||||
|
||||
```typescript
|
||||
const MinimalPreset: StylePreset = {
|
||||
name: 'Minimal',
|
||||
description: 'Ultra-clean with sharp edges and no shadows',
|
||||
|
||||
tokens: {
|
||||
// Colors - Monochromatic
|
||||
'--primary': '#18181b', // Near black
|
||||
'--primary-hover': '#27272a',
|
||||
'--primary-foreground': '#ffffff',
|
||||
'--secondary': '#71717a',
|
||||
'--secondary-hover': '#52525b',
|
||||
'--secondary-foreground': '#ffffff',
|
||||
'--destructive': '#dc2626',
|
||||
'--destructive-hover': '#b91c1c',
|
||||
'--destructive-foreground': '#ffffff',
|
||||
'--muted': '#f4f4f5',
|
||||
'--muted-foreground': '#71717a',
|
||||
'--accent': '#f4f4f5',
|
||||
'--accent-foreground': '#18181b',
|
||||
'--background': '#ffffff',
|
||||
'--foreground': '#18181b',
|
||||
'--surface': '#fafafa',
|
||||
'--border': '#e4e4e7',
|
||||
'--border-subtle': '#f4f4f5',
|
||||
'--ring': '#18181b',
|
||||
|
||||
// Typography
|
||||
'--font-sans': 'system-ui, -apple-system, sans-serif',
|
||||
|
||||
// Borders - Sharp
|
||||
'--radius-sm': '2px',
|
||||
'--radius-md': '4px',
|
||||
'--radius-lg': '6px',
|
||||
|
||||
// Shadows - None
|
||||
'--shadow-sm': 'none',
|
||||
'--shadow-md': 'none',
|
||||
'--shadow-lg': 'none',
|
||||
},
|
||||
|
||||
preview: {
|
||||
primaryButton: '#18181b',
|
||||
cardBackground: '#ffffff',
|
||||
cardBorder: '#e4e4e7',
|
||||
textColor: '#18181b',
|
||||
radius: '4px',
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
### Playful
|
||||
|
||||
Vibrant colors, generous rounding, and friendly typography.
|
||||
|
||||
```typescript
|
||||
const PlayfulPreset: StylePreset = {
|
||||
name: 'Playful',
|
||||
description: 'Vibrant colors and rounded, friendly shapes',
|
||||
|
||||
tokens: {
|
||||
// Colors - Vibrant
|
||||
'--primary': '#8b5cf6', // Purple
|
||||
'--primary-hover': '#7c3aed',
|
||||
'--primary-foreground': '#ffffff',
|
||||
'--secondary': '#ec4899', // Pink
|
||||
'--secondary-hover': '#db2777',
|
||||
'--secondary-foreground': '#ffffff',
|
||||
'--destructive': '#f43f5e', // Rose
|
||||
'--destructive-hover': '#e11d48',
|
||||
'--destructive-foreground': '#ffffff',
|
||||
'--muted': '#faf5ff',
|
||||
'--muted-foreground': '#6b7280',
|
||||
'--accent': '#fce7f3',
|
||||
'--accent-foreground': '#831843',
|
||||
'--background': '#ffffff',
|
||||
'--foreground': '#1e1b4b',
|
||||
'--surface': '#faf5ff',
|
||||
'--border': '#e9d5ff',
|
||||
'--border-subtle': '#faf5ff',
|
||||
'--ring': '#8b5cf6',
|
||||
|
||||
// Typography - Friendly
|
||||
'--font-sans': '"Nunito", "Quicksand", ui-sans-serif, sans-serif',
|
||||
|
||||
// Borders - Very Rounded
|
||||
'--radius-sm': '8px',
|
||||
'--radius-md': '16px',
|
||||
'--radius-lg': '24px',
|
||||
'--radius-full': '9999px',
|
||||
|
||||
// Shadows - Soft colored
|
||||
'--shadow-sm': '0 1px 3px rgb(139 92 246 / 0.1)',
|
||||
'--shadow-md': '0 4px 12px rgb(139 92 246 / 0.15)',
|
||||
},
|
||||
|
||||
preview: {
|
||||
primaryButton: '#8b5cf6',
|
||||
cardBackground: '#faf5ff',
|
||||
cardBorder: '#e9d5ff',
|
||||
textColor: '#1e1b4b',
|
||||
radius: '16px',
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
### Enterprise
|
||||
|
||||
Conservative, trustworthy palette with traditional styling.
|
||||
|
||||
```typescript
|
||||
const EnterprisePreset: StylePreset = {
|
||||
name: 'Enterprise',
|
||||
description: 'Professional and trustworthy for business applications',
|
||||
|
||||
tokens: {
|
||||
// Colors - Conservative
|
||||
'--primary': '#0f172a', // Dark navy
|
||||
'--primary-hover': '#1e293b',
|
||||
'--primary-foreground': '#ffffff',
|
||||
'--secondary': '#475569',
|
||||
'--secondary-hover': '#334155',
|
||||
'--secondary-foreground': '#ffffff',
|
||||
'--destructive': '#b91c1c',
|
||||
'--destructive-hover': '#991b1b',
|
||||
'--destructive-foreground': '#ffffff',
|
||||
'--muted': '#f1f5f9',
|
||||
'--muted-foreground': '#475569',
|
||||
'--accent': '#e2e8f0',
|
||||
'--accent-foreground': '#0f172a',
|
||||
'--background': '#ffffff',
|
||||
'--foreground': '#0f172a',
|
||||
'--surface': '#f8fafc',
|
||||
'--border': '#cbd5e1',
|
||||
'--border-subtle': '#e2e8f0',
|
||||
'--ring': '#0f172a',
|
||||
|
||||
// Typography - Traditional
|
||||
'--font-sans': '"Source Sans Pro", "Segoe UI", ui-sans-serif, sans-serif',
|
||||
|
||||
// Borders - Conservative
|
||||
'--radius-sm': '2px',
|
||||
'--radius-md': '4px',
|
||||
'--radius-lg': '6px',
|
||||
|
||||
// Shadows - Subtle
|
||||
'--shadow-sm': '0 1px 2px rgb(0 0 0 / 0.05)',
|
||||
'--shadow-md': '0 2px 4px rgb(0 0 0 / 0.06), 0 1px 2px rgb(0 0 0 / 0.04)',
|
||||
},
|
||||
|
||||
preview: {
|
||||
primaryButton: '#0f172a',
|
||||
cardBackground: '#ffffff',
|
||||
cardBorder: '#cbd5e1',
|
||||
textColor: '#0f172a',
|
||||
radius: '4px',
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
### Soft
|
||||
|
||||
Gentle, calming aesthetic with soft colors and generous whitespace.
|
||||
|
||||
```typescript
|
||||
const SoftPreset: StylePreset = {
|
||||
name: 'Soft',
|
||||
description: 'Gentle colors and soft shapes for a calming aesthetic',
|
||||
|
||||
tokens: {
|
||||
// Colors - Soft/Muted
|
||||
'--primary': '#6366f1', // Indigo
|
||||
'--primary-hover': '#4f46e5',
|
||||
'--primary-foreground': '#ffffff',
|
||||
'--secondary': '#a78bfa', // Light purple
|
||||
'--secondary-hover': '#8b5cf6',
|
||||
'--secondary-foreground': '#ffffff',
|
||||
'--destructive': '#fb7185', // Soft red
|
||||
'--destructive-hover': '#f43f5e',
|
||||
'--destructive-foreground': '#ffffff',
|
||||
'--muted': '#f5f3ff',
|
||||
'--muted-foreground': '#6b7280',
|
||||
'--accent': '#ede9fe',
|
||||
'--accent-foreground': '#4c1d95',
|
||||
'--background': '#fefefe',
|
||||
'--foreground': '#374151',
|
||||
'--surface': '#f9fafb',
|
||||
'--border': '#e5e7eb',
|
||||
'--border-subtle': '#f3f4f6',
|
||||
'--ring': '#6366f1',
|
||||
|
||||
// Typography
|
||||
'--font-sans': '"DM Sans", ui-sans-serif, system-ui, sans-serif',
|
||||
|
||||
// Borders - Soft rounded
|
||||
'--radius-sm': '6px',
|
||||
'--radius-md': '12px',
|
||||
'--radius-lg': '20px',
|
||||
|
||||
// Shadows - Very soft
|
||||
'--shadow-sm': '0 1px 3px rgb(0 0 0 / 0.02)',
|
||||
'--shadow-md': '0 4px 6px rgb(0 0 0 / 0.04)',
|
||||
},
|
||||
|
||||
preview: {
|
||||
primaryButton: '#6366f1',
|
||||
cardBackground: '#fefefe',
|
||||
cardBorder: '#e5e7eb',
|
||||
textColor: '#374151',
|
||||
radius: '12px',
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Implementation
|
||||
|
||||
### Phase 1: Preset Data Structure (2-3 hrs)
|
||||
|
||||
**Files to create:**
|
||||
|
||||
```
|
||||
packages/noodl-editor/src/editor/src/models/
|
||||
├── StylePresets/
|
||||
│ ├── StylePresetsModel.ts # Preset management
|
||||
│ ├── presets/
|
||||
│ │ ├── ModernPreset.ts
|
||||
│ │ ├── MinimalPreset.ts
|
||||
│ │ ├── PlayfulPreset.ts
|
||||
│ │ ├── EnterprisePreset.ts
|
||||
│ │ ├── SoftPreset.ts
|
||||
│ │ └── index.ts
|
||||
│ ├── StylePresetTypes.ts # TypeScript interfaces
|
||||
│ └── index.ts
|
||||
```
|
||||
|
||||
**TypeScript Interfaces:**
|
||||
|
||||
```typescript
|
||||
interface StylePreset {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
tokens: Record<string, string>;
|
||||
preview: PresetPreview;
|
||||
isBuiltIn: boolean;
|
||||
createdAt?: Date;
|
||||
}
|
||||
|
||||
interface PresetPreview {
|
||||
primaryButton: string;
|
||||
cardBackground: string;
|
||||
cardBorder: string;
|
||||
textColor: string;
|
||||
radius: string;
|
||||
}
|
||||
|
||||
interface StylePresetsModel {
|
||||
builtInPresets: StylePreset[];
|
||||
customPresets: StylePreset[];
|
||||
|
||||
getPreset(id: string): StylePreset | undefined;
|
||||
applyPreset(presetId: string): void;
|
||||
saveCustomPreset(preset: Omit<StylePreset, 'id' | 'isBuiltIn'>): StylePreset;
|
||||
deleteCustomPreset(id: string): void;
|
||||
exportPreset(id: string): string; // JSON
|
||||
importPreset(json: string): StylePreset;
|
||||
}
|
||||
```
|
||||
|
||||
### Phase 2: Preset Selector Component (3-4 hrs)
|
||||
|
||||
**Files to create:**
|
||||
|
||||
```
|
||||
packages/noodl-core-ui/src/components/
|
||||
├── StylePresets/
|
||||
│ ├── PresetSelector.tsx # Main selector component
|
||||
│ ├── PresetSelector.module.scss
|
||||
│ ├── PresetSelector.stories.tsx
|
||||
│ ├── PresetCard.tsx # Individual preset card
|
||||
│ ├── PresetPreview.tsx # Live preview mini-UI
|
||||
│ └── index.ts
|
||||
```
|
||||
|
||||
**PresetSelector UI:**
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ STYLE PRESET │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ Choose a visual style for your project: │
|
||||
│ │
|
||||
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
|
||||
│ │ Modern │ │ Minimal │ │ Playful │ │ Corp │ │ Soft │ │
|
||||
│ │ │ │ │ │ │ │ │ │ │ │
|
||||
│ │ ┌─────┐ │ │ ┌─────┐ │ │ ┌─────┐ │ │ ┌─────┐ │ │ ┌─────┐ │ │
|
||||
│ │ │ btn │ │ │ │ btn │ │ │ │ btn │ │ │ │ btn │ │ │ │ btn │ │ │
|
||||
│ │ └─────┘ │ │ └─────┘ │ │ └─────┘ │ │ └─────┘ │ │ └─────┘ │ │
|
||||
│ │ ─────── │ │ ─────── │ │ ─────── │ │ ─────── │ │ ─────── │ │
|
||||
│ │ ─────── │ │ ─────── │ │ ─────── │ │ ─────── │ │ ─────── │ │
|
||||
│ │ ███████ │ │ ███████ │ │ ███████ │ │ ███████ │ │ ███████ │ │
|
||||
│ └────●────┘ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │
|
||||
│ Selected │
|
||||
│ │
|
||||
│ "Clean and professional with subtle depth" │
|
||||
│ │
|
||||
│ [Customize After Creation] │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**PresetCard Component:**
|
||||
|
||||
```typescript
|
||||
interface PresetCardProps {
|
||||
preset: StylePreset;
|
||||
isSelected: boolean;
|
||||
onSelect: (presetId: string) => void;
|
||||
}
|
||||
|
||||
function PresetCard({ preset, isSelected, onSelect }: PresetCardProps) {
|
||||
return (
|
||||
<div
|
||||
className={cn(css.PresetCard, isSelected && css.Selected)}
|
||||
onClick={() => onSelect(preset.id)}
|
||||
>
|
||||
<PresetPreview preview={preset.preview} />
|
||||
<span className={css.PresetName}>{preset.name}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Phase 3: Project Creation Integration (2-3 hrs)
|
||||
|
||||
**Files to modify:**
|
||||
|
||||
```
|
||||
packages/noodl-editor/src/editor/src/views/
|
||||
├── NewProjectDialog/
|
||||
│ ├── NewProjectDialog.tsx # Add preset selector
|
||||
│ └── NewProjectDialog.module.scss
|
||||
|
||||
packages/noodl-editor/src/editor/src/models/
|
||||
├── ProjectModel.ts # Apply preset on creation
|
||||
```
|
||||
|
||||
**Integration Points:**
|
||||
|
||||
```typescript
|
||||
// In NewProjectDialog.tsx
|
||||
function NewProjectDialog() {
|
||||
const [selectedPreset, setSelectedPreset] = useState('modern');
|
||||
|
||||
const handleCreate = async () => {
|
||||
const project = await ProjectModel.create({
|
||||
name: projectName,
|
||||
// ... other options
|
||||
});
|
||||
|
||||
// Apply selected preset's tokens
|
||||
const preset = StylePresetsModel.getPreset(selectedPreset);
|
||||
if (preset) {
|
||||
StyleTokensModel.applyPreset(preset.tokens);
|
||||
}
|
||||
|
||||
// Open project
|
||||
EditorModel.openProject(project);
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog>
|
||||
{/* ... name input ... */}
|
||||
<PresetSelector
|
||||
selected={selectedPreset}
|
||||
onSelect={setSelectedPreset}
|
||||
/>
|
||||
{/* ... other options ... */}
|
||||
<Button onClick={handleCreate}>Create Project</Button>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Phase 4: Preset Management UI (2-3 hrs)
|
||||
|
||||
**Files to create:**
|
||||
|
||||
```
|
||||
packages/noodl-editor/src/editor/src/views/panels/
|
||||
├── PresetManagerPanel/
|
||||
│ ├── PresetManagerPanel.tsx # Full preset management
|
||||
│ ├── PresetManagerPanel.module.scss
|
||||
│ ├── CreatePresetDialog.tsx
|
||||
│ └── index.ts
|
||||
```
|
||||
|
||||
**Features:**
|
||||
|
||||
1. View all presets (built-in + custom)
|
||||
2. Create preset from current tokens
|
||||
3. Edit custom presets
|
||||
4. Delete custom presets
|
||||
5. Import/Export presets
|
||||
|
||||
---
|
||||
|
||||
## Custom Preset Creation
|
||||
|
||||
### From Project Settings
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ SAVE AS PRESET │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ Save your current token configuration as a reusable preset. │
|
||||
│ │
|
||||
│ Preset Name: [My Brand Style ] │
|
||||
│ Description: [Corporate brand colors] │
|
||||
│ │
|
||||
│ Preview: │
|
||||
│ ┌───────────────────────────────────────────────────────────┐ │
|
||||
│ │ ┌──────┐ │ │
|
||||
│ │ │ btn │ Heading Text │ │
|
||||
│ │ └──────┘ Body text preview with current styling │ │
|
||||
│ │ ┌──────────────────────────────────────────────────┐ │ │
|
||||
│ │ │ Card preview with surface color │ │ │
|
||||
│ │ └──────────────────────────────────────────────────┘ │ │
|
||||
│ └───────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ [Cancel] [Save Preset] │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Import/Export
|
||||
|
||||
```typescript
|
||||
// Export format (JSON)
|
||||
{
|
||||
"name": "My Brand Style",
|
||||
"description": "Corporate brand colors",
|
||||
"version": "1.0",
|
||||
"tokens": {
|
||||
"--primary": "#1a73e8",
|
||||
"--primary-hover": "#1557b0",
|
||||
// ... all token values
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
### Unit Tests
|
||||
|
||||
- Preset loading and parsing
|
||||
- Token application
|
||||
- Custom preset CRUD operations
|
||||
- Import/export JSON serialization
|
||||
|
||||
### Integration Tests
|
||||
|
||||
- Preset selection in new project dialog
|
||||
- Tokens apply correctly to elements
|
||||
- Custom presets persist across sessions
|
||||
|
||||
### Manual Testing Checklist
|
||||
|
||||
- [ ] Create project with each built-in preset
|
||||
- [ ] Verify elements styled correctly per preset
|
||||
- [ ] Create custom preset from current tokens
|
||||
- [ ] Import preset from JSON
|
||||
- [ ] Export preset to JSON
|
||||
- [ ] Delete custom preset
|
||||
- [ ] Preset preview matches actual result
|
||||
|
||||
---
|
||||
|
||||
## Success Criteria
|
||||
|
||||
- [ ] 5 built-in presets available
|
||||
- [ ] Preset selector in project creation dialog
|
||||
- [ ] Live preview in preset cards
|
||||
- [ ] Custom preset creation works
|
||||
- [ ] Import/export functionality
|
||||
- [ ] Presets correctly populate all tokens
|
||||
|
||||
---
|
||||
|
||||
## Future Considerations
|
||||
|
||||
### Dark Mode Variants
|
||||
|
||||
Each preset could have light/dark variants:
|
||||
|
||||
```typescript
|
||||
interface StylePreset {
|
||||
// ...
|
||||
modes: {
|
||||
light: Record<string, string>;
|
||||
dark: Record<string, string>;
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### Community Presets
|
||||
|
||||
Allow sharing presets via:
|
||||
- Export as JSON file
|
||||
- Copy/paste shareable code
|
||||
- (Future) Community preset gallery
|
||||
|
||||
### Preset Inheritance
|
||||
|
||||
Allow presets to extend other presets:
|
||||
|
||||
```typescript
|
||||
const MyPreset = {
|
||||
extends: 'modern',
|
||||
tokens: {
|
||||
'--primary': '#custom-color', // Override just this
|
||||
// Other tokens inherited from 'modern'
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Dependencies
|
||||
|
||||
**Blocked By:**
|
||||
- STYLE-001 (Token System) - presets populate tokens
|
||||
- STYLE-002 (Element Configs) - tokens flow to variants
|
||||
|
||||
**Blocks:**
|
||||
- WIZARD-001 (Project Wizard) - uses preset selector
|
||||
|
||||
---
|
||||
|
||||
*Last Updated: January 2026*
|
||||
Reference in New Issue
Block a user