mirror of
https://github.com/The-Low-Code-Foundation/OpenNoodl.git
synced 2026-01-11 23:02:56 +01:00
Started tasks to migrate runtime to React 19. Added phase 3 projects
This commit is contained in:
@@ -0,0 +1,220 @@
|
||||
# DASH-001: Tabbed Navigation System
|
||||
|
||||
## Overview
|
||||
|
||||
Replace the current single-view dashboard with a proper tabbed interface. This is the foundation task that enables all other dashboard improvements.
|
||||
|
||||
## Context
|
||||
|
||||
The current Noodl editor dashboard (`projectsview.ts`) uses a basic pane-switching mechanism with jQuery. A new launcher is being developed in `packages/noodl-core-ui/src/preview/launcher/` using React, which already has a sidebar-based navigation but needs proper tab support for the main content area.
|
||||
|
||||
This task focuses on the **new React-based launcher** only. The old jQuery launcher will be deprecated.
|
||||
|
||||
## Current State
|
||||
|
||||
### Existing New Launcher Structure
|
||||
```
|
||||
packages/noodl-core-ui/src/preview/launcher/
|
||||
├── Launcher/
|
||||
│ ├── Launcher.tsx # Main component with PAGES array
|
||||
│ ├── components/
|
||||
│ │ ├── LauncherSidebar/ # Left navigation
|
||||
│ │ ├── LauncherPage/ # Page wrapper
|
||||
│ │ ├── LauncherProjectCard/
|
||||
│ │ └── LauncherSearchBar/
|
||||
│ └── views/
|
||||
│ ├── Projects.tsx # Current projects view
|
||||
│ └── LearningCenter.tsx # Empty learning view
|
||||
└── template/
|
||||
└── LauncherApp/ # App shell template
|
||||
```
|
||||
|
||||
### Current Page Definition
|
||||
```typescript
|
||||
// In Launcher.tsx
|
||||
export enum LauncherPageId {
|
||||
LocalProjects,
|
||||
LearningCenter
|
||||
}
|
||||
|
||||
export const PAGES: LauncherPageMetaData[] = [
|
||||
{ id: LauncherPageId.LocalProjects, displayName: 'Recent Projects', icon: IconName.CircleDot },
|
||||
{ id: LauncherPageId.LearningCenter, displayName: 'Learn', icon: IconName.Rocket }
|
||||
];
|
||||
```
|
||||
|
||||
## Requirements
|
||||
|
||||
### Functional Requirements
|
||||
|
||||
1. **Tab Bar Component**
|
||||
- Horizontal tab bar at the top of the main content area
|
||||
- Visual indicator for active tab
|
||||
- Smooth transition when switching tabs
|
||||
- Keyboard navigation support (arrow keys, Enter)
|
||||
|
||||
2. **Tab Configuration**
|
||||
- Projects tab (default, opens first)
|
||||
- Learn tab (tutorials, guides)
|
||||
- Templates tab (project starters)
|
||||
- Extensible for future tabs (Marketplace, Settings)
|
||||
|
||||
3. **State Persistence**
|
||||
- Remember last active tab across sessions
|
||||
- Store in localStorage or electron-store
|
||||
|
||||
4. **URL/Deep Linking (Optional)**
|
||||
- Support for `noodl://dashboard/projects` style deep links
|
||||
- Query params for tab state
|
||||
|
||||
### Non-Functional Requirements
|
||||
|
||||
- Tab switching should feel instant (<100ms)
|
||||
- No layout shift when switching tabs
|
||||
- Accessible (WCAG 2.1 AA compliant)
|
||||
- Consistent with existing noodl-core-ui design system
|
||||
|
||||
## Technical Approach
|
||||
|
||||
### 1. Create Tab Bar Component
|
||||
|
||||
Create a new component in `noodl-core-ui` that can be reused:
|
||||
|
||||
```
|
||||
packages/noodl-core-ui/src/components/layout/TabBar/
|
||||
├── TabBar.tsx
|
||||
├── TabBar.module.scss
|
||||
├── TabBar.stories.tsx
|
||||
└── index.ts
|
||||
```
|
||||
|
||||
### 2. Update Launcher Structure
|
||||
|
||||
```typescript
|
||||
// New page structure
|
||||
export enum LauncherPageId {
|
||||
Projects = 'projects',
|
||||
Learn = 'learn',
|
||||
Templates = 'templates'
|
||||
}
|
||||
|
||||
export interface LauncherTab {
|
||||
id: LauncherPageId;
|
||||
label: string;
|
||||
icon?: IconName;
|
||||
component: React.ComponentType;
|
||||
}
|
||||
|
||||
export const LAUNCHER_TABS: LauncherTab[] = [
|
||||
{ id: LauncherPageId.Projects, label: 'Projects', icon: IconName.Folder, component: Projects },
|
||||
{ id: LauncherPageId.Learn, label: 'Learn', icon: IconName.Book, component: LearningCenter },
|
||||
{ id: LauncherPageId.Templates, label: 'Templates', icon: IconName.Components, component: Templates }
|
||||
];
|
||||
```
|
||||
|
||||
### 3. State Management
|
||||
|
||||
Use React context for tab state:
|
||||
|
||||
```typescript
|
||||
// LauncherContext.tsx
|
||||
interface LauncherContextValue {
|
||||
activeTab: LauncherPageId;
|
||||
setActiveTab: (tab: LauncherPageId) => void;
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Persistence Hook
|
||||
|
||||
```typescript
|
||||
// usePersistentTab.ts
|
||||
function usePersistentTab(key: string, defaultTab: LauncherPageId) {
|
||||
// Load from localStorage on mount
|
||||
// Save to localStorage on change
|
||||
}
|
||||
```
|
||||
|
||||
## Files to Create
|
||||
|
||||
1. `packages/noodl-core-ui/src/components/layout/TabBar/TabBar.tsx`
|
||||
2. `packages/noodl-core-ui/src/components/layout/TabBar/TabBar.module.scss`
|
||||
3. `packages/noodl-core-ui/src/components/layout/TabBar/TabBar.stories.tsx`
|
||||
4. `packages/noodl-core-ui/src/components/layout/TabBar/index.ts`
|
||||
5. `packages/noodl-core-ui/src/preview/launcher/Launcher/LauncherContext.tsx`
|
||||
6. `packages/noodl-core-ui/src/preview/launcher/Launcher/hooks/usePersistentTab.ts`
|
||||
7. `packages/noodl-core-ui/src/preview/launcher/Launcher/views/Templates.tsx`
|
||||
|
||||
## Files to Modify
|
||||
|
||||
1. `packages/noodl-core-ui/src/preview/launcher/Launcher/Launcher.tsx`
|
||||
- Import and use TabBar
|
||||
- Implement tab switching logic
|
||||
- Wrap with LauncherContext
|
||||
|
||||
2. `packages/noodl-core-ui/src/components/layout/index.ts`
|
||||
- Export TabBar component
|
||||
|
||||
## Implementation Steps
|
||||
|
||||
### Phase 1: TabBar Component
|
||||
1. Create TabBar component with basic functionality
|
||||
2. Add styling consistent with noodl-core-ui
|
||||
3. Write Storybook stories for testing
|
||||
4. Add keyboard navigation
|
||||
|
||||
### Phase 2: Launcher Integration
|
||||
1. Create LauncherContext
|
||||
2. Create usePersistentTab hook
|
||||
3. Integrate TabBar into Launcher.tsx
|
||||
4. Create empty Templates view
|
||||
|
||||
### Phase 3: Polish
|
||||
1. Add tab transition animations
|
||||
2. Test accessibility
|
||||
3. Add deep link support (if time permits)
|
||||
|
||||
## Testing Checklist
|
||||
|
||||
- [ ] Tabs render correctly
|
||||
- [ ] Clicking tab switches content
|
||||
- [ ] Active tab is visually indicated
|
||||
- [ ] Keyboard navigation works (Tab, Arrow keys, Enter)
|
||||
- [ ] Tab state persists after closing/reopening
|
||||
- [ ] No layout shift on tab switch
|
||||
- [ ] Works at different viewport sizes
|
||||
- [ ] Screen reader announces tab changes
|
||||
|
||||
## Design Reference
|
||||
|
||||
The tab bar should follow the existing Tabs component style in noodl-core-ui but be optimized for the launcher context (larger, more prominent).
|
||||
|
||||
See: `packages/noodl-core-ui/src/components/layout/Tabs/`
|
||||
|
||||
## Dependencies
|
||||
|
||||
- None (this is a foundation task)
|
||||
|
||||
## Blocked By
|
||||
|
||||
- None
|
||||
|
||||
## Blocks
|
||||
|
||||
- DASH-002 (Project List Redesign)
|
||||
- DASH-003 (Project Organization)
|
||||
- DASH-004 (Tutorial Section Redesign)
|
||||
|
||||
## Estimated Effort
|
||||
|
||||
- Component creation: 2-3 hours
|
||||
- Launcher integration: 2-3 hours
|
||||
- Polish and testing: 1-2 hours
|
||||
- **Total: 5-8 hours**
|
||||
|
||||
## Success Criteria
|
||||
|
||||
1. User can switch between Projects, Learn, and Templates tabs
|
||||
2. Tab state persists across sessions
|
||||
3. Component is reusable for other contexts
|
||||
4. Passes accessibility audit
|
||||
5. Matches existing design system aesthetics
|
||||
@@ -0,0 +1,292 @@
|
||||
# DASH-002: Project List Redesign
|
||||
|
||||
## Overview
|
||||
|
||||
Transform the project list from a thumbnail grid into a more functional table/list view optimized for users with many projects. Add sorting, better information density, and optional view modes.
|
||||
|
||||
## Context
|
||||
|
||||
The current dashboard shows projects as large cards with auto-generated thumbnails. This works for users with a few projects but becomes unwieldy with many projects. The thumbnails add visual noise without providing much value.
|
||||
|
||||
The new launcher in `noodl-core-ui/src/preview/launcher/` already has the beginnings of a table layout with columns for Name, Version Control, and Contributors.
|
||||
|
||||
## Current State
|
||||
|
||||
### Existing LauncherProjectCard
|
||||
```typescript
|
||||
// From LauncherProjectCard.tsx
|
||||
export interface LauncherProjectData {
|
||||
id: string;
|
||||
title: string;
|
||||
cloudSyncMeta: {
|
||||
type: CloudSyncType;
|
||||
source?: string;
|
||||
};
|
||||
localPath: string;
|
||||
lastOpened: string;
|
||||
pullAmount?: number;
|
||||
pushAmount?: number;
|
||||
uncommittedChangesAmount?: number;
|
||||
imageSrc: string;
|
||||
contributors?: UserBadgeProps[];
|
||||
}
|
||||
```
|
||||
|
||||
### Current Layout (Projects.tsx)
|
||||
- Table header with Name, Version control, Contributors columns
|
||||
- Cards with thumbnail images
|
||||
- Basic search functionality via LauncherSearchBar
|
||||
|
||||
## Requirements
|
||||
|
||||
### Functional Requirements
|
||||
|
||||
1. **List View (Primary)**
|
||||
- Compact row-based layout
|
||||
- Columns: Name, Last Modified, Git Status, Local Path (truncated)
|
||||
- Row hover state with quick actions
|
||||
- Sortable columns (click header to sort)
|
||||
- Resizable columns (stretch goal)
|
||||
|
||||
2. **Grid View (Secondary)**
|
||||
- Card-based layout for visual preference
|
||||
- Smaller cards than current (2-3x more per row)
|
||||
- Optional thumbnails (can be disabled)
|
||||
- View toggle in toolbar
|
||||
|
||||
3. **Sorting**
|
||||
- Sort by Name (A-Z, Z-A)
|
||||
- Sort by Last Modified (newest, oldest)
|
||||
- Sort by Git Status (synced first, needs attention first)
|
||||
- Persist sort preference
|
||||
|
||||
4. **Information Display**
|
||||
- Project name (primary)
|
||||
- Last modified timestamp (relative: "2 hours ago")
|
||||
- Git status indicator (icon + tooltip)
|
||||
- Local path (truncated with tooltip for full path)
|
||||
- Quick action buttons on hover (Open, Folder, Settings, Delete)
|
||||
|
||||
5. **Empty State**
|
||||
- Friendly message when no projects exist
|
||||
- Call-to-action to create new project or import
|
||||
|
||||
### Non-Functional Requirements
|
||||
|
||||
- Handle 100+ projects smoothly (virtual scrolling if needed)
|
||||
- Row click opens project
|
||||
- Right-click context menu
|
||||
- Responsive to window resize
|
||||
|
||||
## Technical Approach
|
||||
|
||||
### 1. Data Layer
|
||||
|
||||
Create a hook for project data with sorting:
|
||||
|
||||
```typescript
|
||||
// useProjectList.ts
|
||||
interface UseProjectListOptions {
|
||||
sortField: 'name' | 'lastModified' | 'gitStatus';
|
||||
sortDirection: 'asc' | 'desc';
|
||||
filter?: string;
|
||||
}
|
||||
|
||||
interface UseProjectListReturn {
|
||||
projects: LauncherProjectData[];
|
||||
isLoading: boolean;
|
||||
sortField: string;
|
||||
sortDirection: string;
|
||||
setSorting: (field: string, direction: string) => void;
|
||||
}
|
||||
```
|
||||
|
||||
### 2. List View Component
|
||||
|
||||
```
|
||||
packages/noodl-core-ui/src/preview/launcher/Launcher/components/
|
||||
├── ProjectList/
|
||||
│ ├── ProjectList.tsx # Main list component
|
||||
│ ├── ProjectListRow.tsx # Individual row
|
||||
│ ├── ProjectListHeader.tsx # Sortable header
|
||||
│ ├── ProjectList.module.scss
|
||||
│ └── index.ts
|
||||
```
|
||||
|
||||
### 3. View Mode Toggle
|
||||
|
||||
```typescript
|
||||
// ViewModeToggle.tsx
|
||||
export enum ViewMode {
|
||||
List = 'list',
|
||||
Grid = 'grid'
|
||||
}
|
||||
|
||||
interface ViewModeToggleProps {
|
||||
mode: ViewMode;
|
||||
onChange: (mode: ViewMode) => void;
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Git Status Display
|
||||
|
||||
```typescript
|
||||
// GitStatusBadge.tsx
|
||||
export enum GitStatusType {
|
||||
NotInitialized = 'not-initialized',
|
||||
LocalOnly = 'local-only',
|
||||
Synced = 'synced',
|
||||
Ahead = 'ahead', // Have local commits to push
|
||||
Behind = 'behind', // Have remote commits to pull
|
||||
Diverged = 'diverged', // Both ahead and behind
|
||||
Uncommitted = 'uncommitted'
|
||||
}
|
||||
|
||||
interface GitStatusBadgeProps {
|
||||
status: GitStatusType;
|
||||
details?: {
|
||||
ahead?: number;
|
||||
behind?: number;
|
||||
uncommitted?: number;
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
## Files to Create
|
||||
|
||||
1. `packages/noodl-core-ui/src/preview/launcher/Launcher/components/ProjectList/ProjectList.tsx`
|
||||
2. `packages/noodl-core-ui/src/preview/launcher/Launcher/components/ProjectList/ProjectListRow.tsx`
|
||||
3. `packages/noodl-core-ui/src/preview/launcher/Launcher/components/ProjectList/ProjectListHeader.tsx`
|
||||
4. `packages/noodl-core-ui/src/preview/launcher/Launcher/components/ProjectList/ProjectList.module.scss`
|
||||
5. `packages/noodl-core-ui/src/preview/launcher/Launcher/components/ProjectList/index.ts`
|
||||
6. `packages/noodl-core-ui/src/preview/launcher/Launcher/components/ViewModeToggle/ViewModeToggle.tsx`
|
||||
7. `packages/noodl-core-ui/src/preview/launcher/Launcher/components/GitStatusBadge/GitStatusBadge.tsx`
|
||||
8. `packages/noodl-core-ui/src/preview/launcher/Launcher/hooks/useProjectList.ts`
|
||||
9. `packages/noodl-core-ui/src/preview/launcher/Launcher/components/EmptyProjectsState/EmptyProjectsState.tsx`
|
||||
|
||||
## Files to Modify
|
||||
|
||||
1. `packages/noodl-core-ui/src/preview/launcher/Launcher/views/Projects.tsx`
|
||||
- Replace current layout with ProjectList component
|
||||
- Add view mode toggle
|
||||
- Wire up sorting
|
||||
|
||||
2. `packages/noodl-core-ui/src/preview/launcher/Launcher/components/LauncherProjectCard/LauncherProjectCard.tsx`
|
||||
- Refactor for grid view (smaller)
|
||||
- Make thumbnail optional
|
||||
|
||||
3. `packages/noodl-core-ui/src/preview/launcher/Launcher/Launcher.tsx`
|
||||
- Update mock data if needed
|
||||
- Add view mode to context
|
||||
|
||||
## Implementation Steps
|
||||
|
||||
### Phase 1: Core List View
|
||||
1. Create ProjectListHeader with sortable columns
|
||||
2. Create ProjectListRow with project info
|
||||
3. Create ProjectList combining header and rows
|
||||
4. Add basic sorting logic
|
||||
|
||||
### Phase 2: Git Status Display
|
||||
1. Create GitStatusBadge component
|
||||
2. Define status types and icons
|
||||
3. Add tooltips with details
|
||||
|
||||
### Phase 3: View Modes
|
||||
1. Create ViewModeToggle component
|
||||
2. Refactor LauncherProjectCard for grid mode
|
||||
3. Add view mode to Projects view
|
||||
4. Persist preference
|
||||
|
||||
### Phase 4: Polish
|
||||
1. Add empty state
|
||||
2. Add hover actions
|
||||
3. Implement virtual scrolling (if needed)
|
||||
4. Test with large project counts
|
||||
|
||||
## Component Specifications
|
||||
|
||||
### ProjectListHeader
|
||||
|
||||
| Column | Width | Sortable | Content |
|
||||
|--------|-------|----------|---------|
|
||||
| Name | 40% | Yes | Project name |
|
||||
| Last Modified | 20% | Yes | Relative timestamp |
|
||||
| Git Status | 15% | Yes | Status badge |
|
||||
| Path | 25% | No | Truncated local path |
|
||||
|
||||
### ProjectListRow
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────────────────┐
|
||||
│ 📁 My Project Name 2 hours ago ⚡ Ahead (3) ~/dev/... │
|
||||
│ [hover: Open 📂 ⚙️ 🗑️] │
|
||||
└──────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### GitStatusBadge Icons
|
||||
|
||||
| Status | Icon | Color | Tooltip |
|
||||
|--------|------|-------|---------|
|
||||
| not-initialized | ⚪ | Gray | "No version control" |
|
||||
| local-only | 💾 | Yellow | "Local git only, not synced" |
|
||||
| synced | ✅ | Green | "Up to date with remote" |
|
||||
| ahead | ⬆️ | Blue | "3 commits to push" |
|
||||
| behind | ⬇️ | Orange | "5 commits to pull" |
|
||||
| diverged | ⚠️ | Red | "3 ahead, 5 behind" |
|
||||
| uncommitted | ● | Yellow | "Uncommitted changes" |
|
||||
|
||||
## Testing Checklist
|
||||
|
||||
- [ ] List renders with mock data
|
||||
- [ ] Clicking row opens project (or shows FIXME alert)
|
||||
- [ ] Sorting by each column works
|
||||
- [ ] Sort direction toggles on repeated click
|
||||
- [ ] Sort preference persists
|
||||
- [ ] View mode toggle switches layouts
|
||||
- [ ] View mode preference persists
|
||||
- [ ] Git status badges display correctly
|
||||
- [ ] Tooltips show on hover
|
||||
- [ ] Right-click shows context menu
|
||||
- [ ] Empty state shows when no projects
|
||||
- [ ] Search filters projects correctly
|
||||
- [ ] Performance acceptable with 100+ mock projects
|
||||
|
||||
## Dependencies
|
||||
|
||||
- DASH-001 (Tabbed Navigation System) - for launcher context
|
||||
|
||||
## Blocked By
|
||||
|
||||
- DASH-001
|
||||
|
||||
## Blocks
|
||||
|
||||
- DASH-003 (needs list infrastructure for folder/tag filtering)
|
||||
|
||||
## Estimated Effort
|
||||
|
||||
- ProjectList components: 3-4 hours
|
||||
- GitStatusBadge: 1-2 hours
|
||||
- View mode toggle: 1-2 hours
|
||||
- Sorting & persistence: 2-3 hours
|
||||
- Polish & testing: 2-3 hours
|
||||
- **Total: 9-14 hours**
|
||||
|
||||
## Success Criteria
|
||||
|
||||
1. Projects display in a compact, sortable list
|
||||
2. Git status is immediately visible
|
||||
3. Users can switch to grid view if preferred
|
||||
4. Sorting and view preferences persist
|
||||
5. Empty state guides new users
|
||||
6. Context menu provides quick actions
|
||||
|
||||
## Design Notes
|
||||
|
||||
The list view should feel similar to:
|
||||
- VS Code's file explorer
|
||||
- macOS Finder list view
|
||||
- GitHub repository list
|
||||
|
||||
Keep information density high but avoid clutter. Use icons where possible to save space, with tooltips for details.
|
||||
@@ -0,0 +1,357 @@
|
||||
# DASH-003: Project Organization - Folders & Tags
|
||||
|
||||
## Overview
|
||||
|
||||
Add the ability to organize projects using folders and tags. This enables users with many projects to group related work, filter their view, and find projects quickly.
|
||||
|
||||
## Context
|
||||
|
||||
Currently, projects are displayed in a flat list sorted by recency. Users with many projects (10+) struggle to find specific projects. There's no way to group related projects (e.g., "Client Work", "Personal", "Tutorials").
|
||||
|
||||
This task adds a folder/tag system that works entirely client-side, storing metadata separately from the Noodl projects themselves.
|
||||
|
||||
## Requirements
|
||||
|
||||
### Functional Requirements
|
||||
|
||||
1. **Folders**
|
||||
- Create, rename, delete folders
|
||||
- Drag-and-drop projects into folders
|
||||
- Nested folders (1 level deep max)
|
||||
- "All Projects" virtual folder (shows everything)
|
||||
- "Uncategorized" virtual folder (shows unorganized projects)
|
||||
- Folder displayed in sidebar
|
||||
|
||||
2. **Tags**
|
||||
- Create, rename, delete tags
|
||||
- Assign multiple tags per project
|
||||
- Color-coded tags
|
||||
- Tag filtering (show projects with specific tags)
|
||||
- Tags displayed as pills on project rows
|
||||
|
||||
3. **Filtering**
|
||||
- Filter by folder (sidebar click)
|
||||
- Filter by tag (tag click or dropdown)
|
||||
- Combine folder + tag filters
|
||||
- Search within filtered view
|
||||
- Clear all filters button
|
||||
|
||||
4. **Persistence**
|
||||
- Store folder/tag data in electron-store (not in project files)
|
||||
- Data structure keyed by project path (stable identifier)
|
||||
- Export/import organization data (stretch goal)
|
||||
|
||||
### Non-Functional Requirements
|
||||
|
||||
- Organization changes feel instant
|
||||
- Drag-and-drop is smooth
|
||||
- Works offline
|
||||
- Survives app restart
|
||||
|
||||
## Data Model
|
||||
|
||||
### Storage Structure
|
||||
|
||||
```typescript
|
||||
// Stored in electron-store under 'projectOrganization'
|
||||
interface ProjectOrganizationData {
|
||||
version: 1;
|
||||
folders: Folder[];
|
||||
tags: Tag[];
|
||||
projectMeta: Record<string, ProjectMeta>; // keyed by project path
|
||||
}
|
||||
|
||||
interface Folder {
|
||||
id: string;
|
||||
name: string;
|
||||
parentId: string | null; // null = root level
|
||||
order: number;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
interface Tag {
|
||||
id: string;
|
||||
name: string;
|
||||
color: string; // hex color
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
interface ProjectMeta {
|
||||
folderId: string | null;
|
||||
tagIds: string[];
|
||||
customName?: string; // optional override
|
||||
notes?: string; // stretch goal
|
||||
}
|
||||
```
|
||||
|
||||
### Color Palette for Tags
|
||||
|
||||
```typescript
|
||||
const TAG_COLORS = [
|
||||
'#EF4444', // Red
|
||||
'#F97316', // Orange
|
||||
'#EAB308', // Yellow
|
||||
'#22C55E', // Green
|
||||
'#06B6D4', // Cyan
|
||||
'#3B82F6', // Blue
|
||||
'#8B5CF6', // Purple
|
||||
'#EC4899', // Pink
|
||||
'#6B7280', // Gray
|
||||
];
|
||||
```
|
||||
|
||||
## Technical Approach
|
||||
|
||||
### 1. Storage Service
|
||||
|
||||
```typescript
|
||||
// packages/noodl-editor/src/editor/src/services/ProjectOrganizationService.ts
|
||||
|
||||
class ProjectOrganizationService {
|
||||
private static instance: ProjectOrganizationService;
|
||||
|
||||
// Folder operations
|
||||
createFolder(name: string, parentId?: string): Folder;
|
||||
renameFolder(id: string, name: string): void;
|
||||
deleteFolder(id: string): void;
|
||||
reorderFolder(id: string, newOrder: number): void;
|
||||
|
||||
// Tag operations
|
||||
createTag(name: string, color: string): Tag;
|
||||
renameTag(id: string, name: string): void;
|
||||
deleteTag(id: string): void;
|
||||
changeTagColor(id: string, color: string): void;
|
||||
|
||||
// Project organization
|
||||
moveProjectToFolder(projectPath: string, folderId: string | null): void;
|
||||
addTagToProject(projectPath: string, tagId: string): void;
|
||||
removeTagFromProject(projectPath: string, tagId: string): void;
|
||||
|
||||
// Queries
|
||||
getFolders(): Folder[];
|
||||
getTags(): Tag[];
|
||||
getProjectMeta(projectPath: string): ProjectMeta | null;
|
||||
getProjectsInFolder(folderId: string | null): string[];
|
||||
getProjectsWithTag(tagId: string): string[];
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Sidebar Folder Tree
|
||||
|
||||
```
|
||||
packages/noodl-core-ui/src/preview/launcher/Launcher/components/
|
||||
├── FolderTree/
|
||||
│ ├── FolderTree.tsx # Tree container
|
||||
│ ├── FolderTreeItem.tsx # Individual folder row
|
||||
│ ├── FolderTree.module.scss
|
||||
│ └── index.ts
|
||||
```
|
||||
|
||||
### 3. Tag Components
|
||||
|
||||
```
|
||||
├── TagPill/
|
||||
│ ├── TagPill.tsx # Small colored tag display
|
||||
│ └── TagPill.module.scss
|
||||
├── TagSelector/
|
||||
│ ├── TagSelector.tsx # Dropdown to add/remove tags
|
||||
│ └── TagSelector.module.scss
|
||||
├── TagFilter/
|
||||
│ ├── TagFilter.tsx # Filter bar with active tags
|
||||
│ └── TagFilter.module.scss
|
||||
```
|
||||
|
||||
### 4. Drag and Drop
|
||||
|
||||
Use `@dnd-kit/core` for drag-and-drop:
|
||||
|
||||
```typescript
|
||||
// DragDropContext for launcher
|
||||
import { DndContext, DragOverlay } from '@dnd-kit/core';
|
||||
|
||||
// Draggable project row
|
||||
import { useDraggable } from '@dnd-kit/core';
|
||||
|
||||
// Droppable folder
|
||||
import { useDroppable } from '@dnd-kit/core';
|
||||
```
|
||||
|
||||
## Files to Create
|
||||
|
||||
1. `packages/noodl-editor/src/editor/src/services/ProjectOrganizationService.ts`
|
||||
2. `packages/noodl-core-ui/src/preview/launcher/Launcher/components/FolderTree/FolderTree.tsx`
|
||||
3. `packages/noodl-core-ui/src/preview/launcher/Launcher/components/FolderTree/FolderTreeItem.tsx`
|
||||
4. `packages/noodl-core-ui/src/preview/launcher/Launcher/components/FolderTree/FolderTree.module.scss`
|
||||
5. `packages/noodl-core-ui/src/preview/launcher/Launcher/components/TagPill/TagPill.tsx`
|
||||
6. `packages/noodl-core-ui/src/preview/launcher/Launcher/components/TagSelector/TagSelector.tsx`
|
||||
7. `packages/noodl-core-ui/src/preview/launcher/Launcher/components/TagFilter/TagFilter.tsx`
|
||||
8. `packages/noodl-core-ui/src/preview/launcher/Launcher/hooks/useProjectOrganization.ts`
|
||||
9. `packages/noodl-core-ui/src/preview/launcher/Launcher/components/CreateFolderModal/CreateFolderModal.tsx`
|
||||
10. `packages/noodl-core-ui/src/preview/launcher/Launcher/components/CreateTagModal/CreateTagModal.tsx`
|
||||
|
||||
## Files to Modify
|
||||
|
||||
1. `packages/noodl-core-ui/src/preview/launcher/Launcher/Launcher.tsx`
|
||||
- Add DndContext wrapper
|
||||
- Add organization state to context
|
||||
|
||||
2. `packages/noodl-core-ui/src/preview/launcher/Launcher/components/LauncherSidebar/LauncherSidebar.tsx`
|
||||
- Add FolderTree component
|
||||
- Add "Create Folder" button
|
||||
|
||||
3. `packages/noodl-core-ui/src/preview/launcher/Launcher/views/Projects.tsx`
|
||||
- Add TagFilter bar
|
||||
- Filter projects based on folder/tag selection
|
||||
- Make project rows draggable
|
||||
|
||||
4. `packages/noodl-core-ui/src/preview/launcher/Launcher/components/ProjectList/ProjectListRow.tsx`
|
||||
- Add tag pills
|
||||
- Add tag selector on hover/context menu
|
||||
- Make row draggable
|
||||
|
||||
## UI Mockups
|
||||
|
||||
### Sidebar with Folders
|
||||
|
||||
```
|
||||
┌─────────────────────────┐
|
||||
│ 📁 All Projects (24) │
|
||||
│ 📁 Uncategorized (5) │
|
||||
├─────────────────────────┤
|
||||
│ + Create Folder │
|
||||
├─────────────────────────┤
|
||||
│ 📂 Client Work (8) │
|
||||
│ └─ 📁 Acme Corp (3) │
|
||||
│ └─ 📁 BigCo (5) │
|
||||
│ 📂 Personal (6) │
|
||||
│ 📂 Tutorials (5) │
|
||||
└─────────────────────────┘
|
||||
```
|
||||
|
||||
### Project Row with Tags
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────────────────────┐
|
||||
│ 📁 E-commerce Dashboard 2h ago ✅ [🔴 Urgent] [🔵 Client] ~/dev/... │
|
||||
└──────────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Tag Filter Bar
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────────┐
|
||||
│ Filters: [🔴 Urgent ×] [🔵 Client ×] [+ Add Filter] [Clear All] │
|
||||
└─────────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Implementation Steps
|
||||
|
||||
### Phase 1: Storage Foundation
|
||||
1. Create ProjectOrganizationService
|
||||
2. Define data model and storage
|
||||
3. Create useProjectOrganization hook
|
||||
4. Add to launcher context
|
||||
|
||||
### Phase 2: Folders
|
||||
1. Create FolderTree component
|
||||
2. Add to sidebar
|
||||
3. Create folder modal
|
||||
4. Implement folder filtering
|
||||
5. Add context menu (rename, delete)
|
||||
|
||||
### Phase 3: Tags
|
||||
1. Create TagPill component
|
||||
2. Create TagSelector dropdown
|
||||
3. Create TagFilter bar
|
||||
4. Add tags to project rows
|
||||
5. Implement tag filtering
|
||||
|
||||
### Phase 4: Drag and Drop
|
||||
1. Add dnd-kit dependency
|
||||
2. Wrap launcher in DndContext
|
||||
3. Make project rows draggable
|
||||
4. Make folders droppable
|
||||
5. Handle drop events
|
||||
|
||||
### Phase 5: Polish
|
||||
1. Add keyboard shortcuts
|
||||
2. Improve animations
|
||||
3. Handle edge cases (deleted projects, etc.)
|
||||
4. Test thoroughly
|
||||
|
||||
## Testing Checklist
|
||||
|
||||
- [ ] Can create a folder
|
||||
- [ ] Can rename a folder
|
||||
- [ ] Can delete a folder (projects go to Uncategorized)
|
||||
- [ ] Can create nested folder
|
||||
- [ ] Clicking folder filters project list
|
||||
- [ ] Can create a tag
|
||||
- [ ] Can assign tag to project
|
||||
- [ ] Can remove tag from project
|
||||
- [ ] Clicking tag filters project list
|
||||
- [ ] Can combine folder + tag filters
|
||||
- [ ] Search works within filtered view
|
||||
- [ ] Clear filters button works
|
||||
- [ ] Drag project to folder works
|
||||
- [ ] Data persists after app restart
|
||||
- [ ] Removing project from disk shows appropriate state
|
||||
|
||||
## Dependencies
|
||||
|
||||
- DASH-001 (Tabbed Navigation System)
|
||||
- DASH-002 (Project List Redesign) - for project rows
|
||||
|
||||
### External Dependencies
|
||||
|
||||
Add to `package.json`:
|
||||
```json
|
||||
{
|
||||
"@dnd-kit/core": "^6.0.0",
|
||||
"@dnd-kit/sortable": "^7.0.0"
|
||||
}
|
||||
```
|
||||
|
||||
## Blocked By
|
||||
|
||||
- DASH-002
|
||||
|
||||
## Blocks
|
||||
|
||||
- None (this is end of the DASH chain)
|
||||
|
||||
## Estimated Effort
|
||||
|
||||
- Storage service: 2-3 hours
|
||||
- Folder tree UI: 3-4 hours
|
||||
- Tag components: 3-4 hours
|
||||
- Drag and drop: 3-4 hours
|
||||
- Filtering logic: 2-3 hours
|
||||
- Polish & testing: 3-4 hours
|
||||
- **Total: 16-22 hours**
|
||||
|
||||
## Success Criteria
|
||||
|
||||
1. Users can create folders and organize projects
|
||||
2. Users can create tags and assign them to projects
|
||||
3. Filtering by folder and tag works correctly
|
||||
4. Drag-and-drop feels natural
|
||||
5. Organization data persists across sessions
|
||||
6. System handles edge cases gracefully (deleted projects, etc.)
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
- Export/import organization data
|
||||
- Folder color customization
|
||||
- Project notes/descriptions
|
||||
- Bulk operations (move/tag multiple projects)
|
||||
- Smart folders (auto-organize by criteria)
|
||||
|
||||
## Design Notes
|
||||
|
||||
The folder tree should feel familiar like:
|
||||
- macOS Finder sidebar
|
||||
- VS Code Explorer
|
||||
- Notion page tree
|
||||
|
||||
Keep interactions lightweight - organization should help, not hinder, the workflow of quickly opening projects.
|
||||
@@ -0,0 +1,413 @@
|
||||
# DASH-004: Tutorial Section Redesign
|
||||
|
||||
## Overview
|
||||
|
||||
Redesign the tutorial section (Learn tab) to be more compact, informative, and useful. Move from large tiles to a structured learning center with categories, progress tracking, and better discoverability.
|
||||
|
||||
## Context
|
||||
|
||||
The current tutorial section (`projectsview.ts` and lessons model) shows tutorials as large tiles with progress bars. The tiles take up significant screen space, making it hard to browse many tutorials. There's no categorization beyond a linear list.
|
||||
|
||||
The new launcher has an empty `LearningCenter.tsx` view that needs to be built out.
|
||||
|
||||
### Current Tutorial System
|
||||
|
||||
The existing system uses:
|
||||
- `LessonProjectsModel` - manages lesson templates and progress
|
||||
- `lessonprojectsmodel.ts` - fetches from docs endpoint
|
||||
- Templates stored in docs repo with progress in localStorage
|
||||
|
||||
## Requirements
|
||||
|
||||
### Functional Requirements
|
||||
|
||||
1. **Category Organization**
|
||||
- Categories: Getting Started, Building UIs, Data & Logic, Advanced Topics, Integrations
|
||||
- Collapsible category sections
|
||||
- Category icons/colors
|
||||
|
||||
2. **Tutorial Cards (Compact)**
|
||||
- Title
|
||||
- Short description (1-2 lines)
|
||||
- Estimated duration
|
||||
- Difficulty level (Beginner, Intermediate, Advanced)
|
||||
- Progress indicator (not started, in progress, completed)
|
||||
- Thumbnail (small, optional)
|
||||
|
||||
3. **Progress Tracking**
|
||||
- Visual progress bar per tutorial
|
||||
- Overall progress stats ("5 of 12 completed")
|
||||
- "Continue where you left off" section at top
|
||||
- Reset progress option
|
||||
|
||||
4. **Filtering & Search**
|
||||
- Search tutorials by name/description
|
||||
- Filter by difficulty
|
||||
- Filter by category
|
||||
- Filter by progress (Not Started, In Progress, Completed)
|
||||
|
||||
5. **Tutorial Detail View**
|
||||
- Expanded description
|
||||
- Learning objectives
|
||||
- Prerequisites
|
||||
- "Start Tutorial" / "Continue" / "Restart" button
|
||||
- Estimated time remaining (for in-progress)
|
||||
|
||||
6. **Additional Content Types**
|
||||
- Video tutorials (embedded or linked)
|
||||
- Written guides
|
||||
- Interactive lessons (existing)
|
||||
- External resources
|
||||
|
||||
### Non-Functional Requirements
|
||||
|
||||
- Fast loading (tutorials list cached)
|
||||
- Works offline for previously loaded tutorials
|
||||
- Responsive layout
|
||||
- Accessible navigation
|
||||
|
||||
## Data Model
|
||||
|
||||
### Enhanced Tutorial Structure
|
||||
|
||||
```typescript
|
||||
interface Tutorial {
|
||||
id: string;
|
||||
title: string;
|
||||
description: string;
|
||||
longDescription?: string;
|
||||
category: TutorialCategory;
|
||||
difficulty: 'beginner' | 'intermediate' | 'advanced';
|
||||
estimatedMinutes: number;
|
||||
type: 'interactive' | 'video' | 'guide';
|
||||
thumbnailUrl?: string;
|
||||
objectives?: string[];
|
||||
prerequisites?: string[];
|
||||
|
||||
// For interactive tutorials
|
||||
templateUrl?: string;
|
||||
|
||||
// For video tutorials
|
||||
videoUrl?: string;
|
||||
|
||||
// For guides
|
||||
guideUrl?: string;
|
||||
}
|
||||
|
||||
interface TutorialCategory {
|
||||
id: string;
|
||||
name: string;
|
||||
icon: IconName;
|
||||
color: string;
|
||||
order: number;
|
||||
}
|
||||
|
||||
interface TutorialProgress {
|
||||
tutorialId: string;
|
||||
status: 'not-started' | 'in-progress' | 'completed';
|
||||
lastAccessedAt: string;
|
||||
completedAt?: string;
|
||||
currentStep?: number;
|
||||
totalSteps?: number;
|
||||
}
|
||||
```
|
||||
|
||||
### Default Categories
|
||||
|
||||
```typescript
|
||||
const TUTORIAL_CATEGORIES: TutorialCategory[] = [
|
||||
{ id: 'getting-started', name: 'Getting Started', icon: IconName.Rocket, color: '#22C55E', order: 0 },
|
||||
{ id: 'ui', name: 'Building UIs', icon: IconName.Palette, color: '#3B82F6', order: 1 },
|
||||
{ id: 'data', name: 'Data & Logic', icon: IconName.Database, color: '#8B5CF6', order: 2 },
|
||||
{ id: 'advanced', name: 'Advanced Topics', icon: IconName.Cog, color: '#F97316', order: 3 },
|
||||
{ id: 'integrations', name: 'Integrations', icon: IconName.Plug, color: '#EC4899', order: 4 },
|
||||
];
|
||||
```
|
||||
|
||||
## Technical Approach
|
||||
|
||||
### 1. Tutorial Service
|
||||
|
||||
Extend or replace `LessonProjectsModel`:
|
||||
|
||||
```typescript
|
||||
// packages/noodl-editor/src/editor/src/services/TutorialService.ts
|
||||
|
||||
class TutorialService {
|
||||
private static instance: TutorialService;
|
||||
|
||||
// Data fetching
|
||||
async fetchTutorials(): Promise<Tutorial[]>;
|
||||
async getTutorialById(id: string): Promise<Tutorial | null>;
|
||||
|
||||
// Progress
|
||||
getProgress(tutorialId: string): TutorialProgress;
|
||||
updateProgress(tutorialId: string, progress: Partial<TutorialProgress>): void;
|
||||
resetProgress(tutorialId: string): void;
|
||||
|
||||
// Queries
|
||||
getInProgressTutorials(): Tutorial[];
|
||||
getCompletedTutorials(): Tutorial[];
|
||||
getTutorialsByCategory(categoryId: string): Tutorial[];
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Component Structure
|
||||
|
||||
```
|
||||
packages/noodl-core-ui/src/preview/launcher/Launcher/
|
||||
├── views/
|
||||
│ └── LearningCenter/
|
||||
│ ├── LearningCenter.tsx # Main view
|
||||
│ ├── LearningCenter.module.scss
|
||||
│ ├── ContinueLearning.tsx # "Continue" section
|
||||
│ ├── TutorialCategory.tsx # Category section
|
||||
│ └── TutorialFilters.tsx # Filter bar
|
||||
├── components/
|
||||
│ ├── TutorialCard/
|
||||
│ │ ├── TutorialCard.tsx # Compact card
|
||||
│ │ ├── TutorialCard.module.scss
|
||||
│ │ └── index.ts
|
||||
│ ├── TutorialDetailModal/
|
||||
│ │ ├── TutorialDetailModal.tsx # Expanded detail view
|
||||
│ │ └── TutorialDetailModal.module.scss
|
||||
│ ├── DifficultyBadge/
|
||||
│ │ └── DifficultyBadge.tsx # Beginner/Intermediate/Advanced
|
||||
│ ├── ProgressRing/
|
||||
│ │ └── ProgressRing.tsx # Circular progress indicator
|
||||
│ └── DurationLabel/
|
||||
│ └── DurationLabel.tsx # "15 min" display
|
||||
```
|
||||
|
||||
### 3. Learning Center Layout
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ Learn [🔍 Search... ] │
|
||||
├─────────────────────────────────────────────────────────────────────┤
|
||||
│ Filters: [All ▾] [All Difficulties ▾] [All Progress ▾] [Clear] │
|
||||
├─────────────────────────────────────────────────────────────────────┤
|
||||
│ ⏸️ Continue Learning │
|
||||
│ ┌─────────────────────────────────────────────────────────────────┐ │
|
||||
│ │ 📚 OpenNoodl Basics 47% [●●●●●○○○○○] [Continue →] │ │
|
||||
│ │ Data-driven Components 12% [●○○○○○○○○○] [Continue →] │ │
|
||||
│ └─────────────────────────────────────────────────────────────────┘ │
|
||||
├─────────────────────────────────────────────────────────────────────┤
|
||||
│ 🚀 Getting Started ▼ │
|
||||
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
|
||||
│ │ AI Walkthru │ │ Basics │ │ Layout │ │
|
||||
│ │ 🟢 Beginner │ │ 🟢 Beginner │ │ 🟢 Beginner │ │
|
||||
│ │ 15 min │ │ 15 min │ │ 15 min │ │
|
||||
│ │ ✓ Complete │ │ ● 47% │ │ ○ Not started│ │
|
||||
│ └──────────────┘ └──────────────┘ └──────────────┘ │
|
||||
├─────────────────────────────────────────────────────────────────────┤
|
||||
│ 🎨 Building UIs ▼ │
|
||||
│ ... │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Files to Create
|
||||
|
||||
1. `packages/noodl-editor/src/editor/src/services/TutorialService.ts`
|
||||
2. `packages/noodl-core-ui/src/preview/launcher/Launcher/views/LearningCenter/LearningCenter.tsx`
|
||||
3. `packages/noodl-core-ui/src/preview/launcher/Launcher/views/LearningCenter/LearningCenter.module.scss`
|
||||
4. `packages/noodl-core-ui/src/preview/launcher/Launcher/views/LearningCenter/ContinueLearning.tsx`
|
||||
5. `packages/noodl-core-ui/src/preview/launcher/Launcher/views/LearningCenter/TutorialCategory.tsx`
|
||||
6. `packages/noodl-core-ui/src/preview/launcher/Launcher/views/LearningCenter/TutorialFilters.tsx`
|
||||
7. `packages/noodl-core-ui/src/preview/launcher/Launcher/components/TutorialCard/TutorialCard.tsx`
|
||||
8. `packages/noodl-core-ui/src/preview/launcher/Launcher/components/TutorialCard/TutorialCard.module.scss`
|
||||
9. `packages/noodl-core-ui/src/preview/launcher/Launcher/components/TutorialDetailModal/TutorialDetailModal.tsx`
|
||||
10. `packages/noodl-core-ui/src/preview/launcher/Launcher/components/DifficultyBadge/DifficultyBadge.tsx`
|
||||
11. `packages/noodl-core-ui/src/preview/launcher/Launcher/components/ProgressRing/ProgressRing.tsx`
|
||||
12. `packages/noodl-core-ui/src/preview/launcher/Launcher/components/DurationLabel/DurationLabel.tsx`
|
||||
13. `packages/noodl-core-ui/src/preview/launcher/Launcher/hooks/useTutorials.ts`
|
||||
|
||||
## Files to Modify
|
||||
|
||||
1. `packages/noodl-core-ui/src/preview/launcher/Launcher/views/LearningCenter.tsx`
|
||||
- Replace empty component with full implementation
|
||||
- Move to folder structure
|
||||
|
||||
2. `packages/noodl-core-ui/src/preview/launcher/Launcher/Launcher.tsx`
|
||||
- Update import for LearningCenter
|
||||
|
||||
3. `packages/noodl-editor/src/editor/src/models/lessonprojectsmodel.ts`
|
||||
- Either extend or create adapter for new TutorialService
|
||||
|
||||
## Implementation Steps
|
||||
|
||||
### Phase 1: Data Layer
|
||||
1. Create TutorialService
|
||||
2. Define data types
|
||||
3. Create useTutorials hook
|
||||
4. Migrate existing lesson data structure
|
||||
|
||||
### Phase 2: Core Components
|
||||
1. Create TutorialCard component
|
||||
2. Create DifficultyBadge
|
||||
3. Create ProgressRing
|
||||
4. Create DurationLabel
|
||||
|
||||
### Phase 3: Main Layout
|
||||
1. Build LearningCenter view
|
||||
2. Create TutorialCategory sections
|
||||
3. Add ContinueLearning section
|
||||
4. Implement category collapse/expand
|
||||
|
||||
### Phase 4: Filtering
|
||||
1. Create TutorialFilters component
|
||||
2. Implement search
|
||||
3. Implement filter dropdowns
|
||||
4. Wire up filter state
|
||||
|
||||
### Phase 5: Detail View
|
||||
1. Create TutorialDetailModal
|
||||
2. Add start/continue/restart logic
|
||||
3. Show objectives and prerequisites
|
||||
|
||||
### Phase 6: Polish
|
||||
1. Add loading states
|
||||
2. Add empty states
|
||||
3. Smooth animations
|
||||
4. Accessibility review
|
||||
|
||||
## Component Specifications
|
||||
|
||||
### TutorialCard
|
||||
|
||||
```
|
||||
┌────────────────────────────┐
|
||||
│ [📹] OpenNoodl Basics │ <- Type icon + Title
|
||||
│ Learn the fundamentals │ <- Description (truncated)
|
||||
│ 🟢 Beginner ⏱️ 15 min │ <- Difficulty + Duration
|
||||
│ [●●●●●○○○○○] 47% │ <- Progress bar
|
||||
└────────────────────────────┘
|
||||
```
|
||||
|
||||
Props:
|
||||
- `tutorial: Tutorial`
|
||||
- `progress: TutorialProgress`
|
||||
- `onClick: () => void`
|
||||
- `variant?: 'compact' | 'expanded'`
|
||||
|
||||
### DifficultyBadge
|
||||
|
||||
| Level | Color | Icon |
|
||||
|-------|-------|------|
|
||||
| Beginner | Green (#22C55E) | 🟢 |
|
||||
| Intermediate | Yellow (#EAB308) | 🟡 |
|
||||
| Advanced | Red (#EF4444) | 🔴 |
|
||||
|
||||
### ProgressRing
|
||||
|
||||
Small circular progress indicator:
|
||||
- Size: 24px
|
||||
- Stroke width: 3px
|
||||
- Background: gray
|
||||
- Fill: green (completing), green (complete)
|
||||
- Center: percentage or checkmark
|
||||
|
||||
## Compatibility Notes
|
||||
|
||||
### Existing Lesson System
|
||||
|
||||
The current system uses:
|
||||
```typescript
|
||||
// lessonprojectsmodel.ts
|
||||
interface LessonTemplate {
|
||||
name: string;
|
||||
description: string;
|
||||
iconURL: string;
|
||||
templateURL: string;
|
||||
progress?: number;
|
||||
}
|
||||
```
|
||||
|
||||
The new system should:
|
||||
1. Be backwards compatible with existing templates
|
||||
2. Migrate progress data from old format
|
||||
3. Support new enhanced metadata
|
||||
|
||||
### Migration Path
|
||||
|
||||
1. Keep `lessonprojectsmodel.ts` working during transition
|
||||
2. Create adapter in TutorialService to read old data
|
||||
3. Enhance existing tutorials with new metadata
|
||||
4. Eventually deprecate old model
|
||||
|
||||
## Testing Checklist
|
||||
|
||||
- [ ] Tutorials load from docs endpoint
|
||||
- [ ] Categories display correctly
|
||||
- [ ] Category collapse/expand works
|
||||
- [ ] Progress displays correctly
|
||||
- [ ] Continue Learning section shows in-progress tutorials
|
||||
- [ ] Search filters tutorials
|
||||
- [ ] Difficulty filter works
|
||||
- [ ] Progress filter works
|
||||
- [ ] Clicking card shows detail modal
|
||||
- [ ] Start Tutorial launches tutorial
|
||||
- [ ] Continue Tutorial resumes from last point
|
||||
- [ ] Restart Tutorial resets progress
|
||||
- [ ] Progress persists across sessions
|
||||
- [ ] Empty states display appropriately
|
||||
- [ ] Responsive at different window sizes
|
||||
|
||||
## Dependencies
|
||||
|
||||
- DASH-001 (Tabbed Navigation System)
|
||||
|
||||
### External Dependencies
|
||||
|
||||
None - uses existing noodl-core-ui components.
|
||||
|
||||
## Blocked By
|
||||
|
||||
- DASH-001
|
||||
|
||||
## Blocks
|
||||
|
||||
- None
|
||||
|
||||
## Estimated Effort
|
||||
|
||||
- TutorialService: 2-3 hours
|
||||
- TutorialCard components: 2-3 hours
|
||||
- LearningCenter layout: 3-4 hours
|
||||
- Filtering: 2-3 hours
|
||||
- Detail modal: 2-3 hours
|
||||
- Polish & testing: 2-3 hours
|
||||
- **Total: 13-19 hours**
|
||||
|
||||
## Success Criteria
|
||||
|
||||
1. Tutorials are organized by category
|
||||
2. Users can easily find tutorials by search/filter
|
||||
3. Progress is clearly visible
|
||||
4. "Continue Learning" helps users resume work
|
||||
5. Tutorial cards are compact but informative
|
||||
6. Detail modal provides all needed information
|
||||
7. System is backwards compatible with existing tutorials
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
- Video tutorial playback within app
|
||||
- Community-contributed tutorials
|
||||
- Tutorial recommendations based on usage
|
||||
- Learning paths (curated sequences)
|
||||
- Achievements/badges for completion
|
||||
- Tutorial ratings/feedback
|
||||
|
||||
## Design Notes
|
||||
|
||||
The learning center should feel like:
|
||||
- Duolingo's course browser (compact, progress-focused)
|
||||
- Coursera's course catalog (categorized, searchable)
|
||||
- VS Code's Getting Started (helpful, not overwhelming)
|
||||
|
||||
Prioritize getting users to relevant content quickly. The most common flow is:
|
||||
1. See "Continue Learning" → resume last tutorial
|
||||
2. Browse category → find new tutorial → start
|
||||
3. Search for specific topic → find tutorial → start
|
||||
|
||||
Don't make users click through multiple screens to start learning.
|
||||
@@ -0,0 +1,150 @@
|
||||
# DASH Series: Dashboard UX Foundation
|
||||
|
||||
## Overview
|
||||
|
||||
The DASH series modernizes the OpenNoodl editor dashboard, transforming it from a basic project launcher into a proper workspace management hub. These tasks focus on the **new React 19 launcher** in `packages/noodl-core-ui/src/preview/launcher/`.
|
||||
|
||||
## Target Environment
|
||||
|
||||
- **Editor**: React 19 version only
|
||||
- **Runtime**: React 19 version (if applicable)
|
||||
- **Backwards Compatibility**: Not required for old launcher
|
||||
|
||||
## Task Dependency Graph
|
||||
|
||||
```
|
||||
DASH-001 (Tabbed Navigation)
|
||||
│
|
||||
├── DASH-002 (Project List Redesign)
|
||||
│ │
|
||||
│ └── DASH-003 (Project Organization)
|
||||
│
|
||||
└── DASH-004 (Tutorial Section Redesign)
|
||||
```
|
||||
|
||||
## Task Summary
|
||||
|
||||
| Task ID | Name | Est. Hours | Priority |
|
||||
|---------|------|------------|----------|
|
||||
| DASH-001 | Tabbed Navigation System | 5-8 | Critical |
|
||||
| DASH-002 | Project List Redesign | 9-14 | High |
|
||||
| DASH-003 | Project Organization | 16-22 | Medium |
|
||||
| DASH-004 | Tutorial Section Redesign | 13-19 | Medium |
|
||||
|
||||
**Total Estimated: 43-63 hours**
|
||||
|
||||
## Implementation Order
|
||||
|
||||
### Week 1: Foundation
|
||||
1. **DASH-001** - Tabbed navigation (foundation for everything)
|
||||
2. **DASH-004** - Tutorial redesign (can parallel with DASH-002)
|
||||
|
||||
### Week 2: Project Management
|
||||
3. **DASH-002** - Project list redesign
|
||||
4. **DASH-003** - Folders and tags
|
||||
|
||||
## Key Technical Decisions
|
||||
|
||||
### Location
|
||||
All new components go in:
|
||||
```
|
||||
packages/noodl-core-ui/src/preview/launcher/Launcher/
|
||||
```
|
||||
|
||||
### State Management
|
||||
- Use React Context for launcher-wide state
|
||||
- Use electron-store for persistence
|
||||
- Keep component state minimal
|
||||
|
||||
### Styling
|
||||
- Use existing noodl-core-ui components
|
||||
- CSS Modules for custom styling
|
||||
- Follow existing color/spacing tokens
|
||||
|
||||
### Data
|
||||
- Services in `packages/noodl-editor/src/editor/src/services/`
|
||||
- Hooks in launcher `hooks/` folder
|
||||
- Types in component folders or shared types file
|
||||
|
||||
## Shared Components to Create
|
||||
|
||||
These components will be reused across DASH tasks:
|
||||
|
||||
| Component | Created In | Used By |
|
||||
|-----------|------------|---------|
|
||||
| TabBar | DASH-001 | All views |
|
||||
| GitStatusBadge | DASH-002 | Project list |
|
||||
| ViewModeToggle | DASH-002 | Project list |
|
||||
| FolderTree | DASH-003 | Sidebar |
|
||||
| TagPill | DASH-003 | Project rows |
|
||||
| ProgressRing | DASH-004 | Tutorial cards |
|
||||
| DifficultyBadge | DASH-004 | Tutorial cards |
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
Each task includes a testing checklist. Additionally:
|
||||
|
||||
1. **Visual Testing**: Use Storybook for component development
|
||||
2. **Integration Testing**: Test in actual launcher context
|
||||
3. **Persistence Testing**: Verify data survives app restart
|
||||
4. **Performance Testing**: Check with 100+ projects/tutorials
|
||||
|
||||
## Cline Usage Notes
|
||||
|
||||
### Before Starting Each Task
|
||||
|
||||
1. Read the task document completely
|
||||
2. Explore the existing code in `packages/noodl-core-ui/src/preview/launcher/`
|
||||
3. Check existing components in `packages/noodl-core-ui/src/components/`
|
||||
4. Understand the data flow
|
||||
|
||||
### During Implementation
|
||||
|
||||
1. Create components incrementally with Storybook stories
|
||||
2. Test in isolation before integration
|
||||
3. Update imports/exports in index files
|
||||
4. Follow existing code style
|
||||
|
||||
### Confidence Checkpoints
|
||||
|
||||
Rate confidence (1-10) at these points:
|
||||
- After reading task document
|
||||
- After exploring existing code
|
||||
- Before creating first component
|
||||
- After completing each phase
|
||||
- Before marking task complete
|
||||
|
||||
### Common Gotchas
|
||||
|
||||
1. **Mock Data**: The launcher currently uses mock data - don't try to connect to real data yet
|
||||
2. **FIXME Alerts**: Many click handlers are `alert('FIXME: ...')` - that's expected
|
||||
3. **Storybook**: Run `npm run storybook` in noodl-core-ui to test components
|
||||
4. **Imports**: noodl-core-ui uses path aliases - check existing imports for patterns
|
||||
|
||||
## Success Criteria (Series Complete)
|
||||
|
||||
1. ✅ Launcher has tabbed navigation (Projects, Learn, Templates)
|
||||
2. ✅ Projects display in sortable list with git status
|
||||
3. ✅ Projects can be organized with folders and tags
|
||||
4. ✅ Tutorials are organized by category with progress tracking
|
||||
5. ✅ All preferences persist across sessions
|
||||
6. ✅ UI is responsive and accessible
|
||||
7. ✅ New components are reusable
|
||||
|
||||
## Future Work (Post-DASH)
|
||||
|
||||
The DASH series sets up infrastructure for:
|
||||
- **GIT series**: GitHub integration, sync status
|
||||
- **COMP series**: Shared components system
|
||||
- **AI series**: AI project creation
|
||||
- **DEPLOY series**: Deployment automation
|
||||
|
||||
These will be documented separately.
|
||||
|
||||
## Files in This Series
|
||||
|
||||
- `DASH-001-tabbed-navigation.md`
|
||||
- `DASH-002-project-list-redesign.md`
|
||||
- `DASH-003-project-organization.md`
|
||||
- `DASH-004-tutorial-section-redesign.md`
|
||||
- `DASH-OVERVIEW.md` (this file)
|
||||
@@ -0,0 +1,335 @@
|
||||
# GIT-001: GitHub OAuth Integration
|
||||
|
||||
## Overview
|
||||
|
||||
Add GitHub OAuth as an authentication method alongside the existing Personal Access Token (PAT) approach. This provides a smoother onboarding experience and enables access to GitHub's API for advanced features like repository browsing and organization access.
|
||||
|
||||
## Context
|
||||
|
||||
Currently, Noodl uses Personal Access Tokens for GitHub authentication:
|
||||
- Stored per-project in `GitStore` (encrypted locally)
|
||||
- Prompted via `GitProviderPopout` component
|
||||
- Used by `trampoline-askpass-handler` for git operations
|
||||
|
||||
OAuth provides advantages:
|
||||
- No need to manually create and copy PATs
|
||||
- Automatic token refresh
|
||||
- Access to GitHub API (not just git operations)
|
||||
- Org/repo scope selection
|
||||
|
||||
## Current State
|
||||
|
||||
### Existing Authentication Flow
|
||||
```
|
||||
User → GitProviderPopout → Enter PAT → GitStore.set() → Git operations use PAT
|
||||
```
|
||||
|
||||
### Key Files
|
||||
- `packages/noodl-editor/src/editor/src/views/panels/VersionControlPanel/components/GitProviderPopout/`
|
||||
- `packages/noodl-store/src/GitStore.ts` (assumed location)
|
||||
- `packages/noodl-git/src/core/trampoline/trampoline-askpass-handler.ts`
|
||||
|
||||
## Requirements
|
||||
|
||||
### Functional Requirements
|
||||
|
||||
1. **OAuth Flow**
|
||||
- "Connect with GitHub" button in settings/dashboard
|
||||
- Opens GitHub OAuth in system browser
|
||||
- Handles callback via custom protocol (`noodl://github-callback`)
|
||||
- Exchanges code for access token
|
||||
- Stores token securely
|
||||
|
||||
2. **Scope Selection**
|
||||
- Request appropriate scopes: `repo`, `read:org`, `read:user`
|
||||
- Display what permissions are being requested
|
||||
- Option to request additional scopes later
|
||||
|
||||
3. **Account Management**
|
||||
- Show connected GitHub account (avatar, username)
|
||||
- "Disconnect" option
|
||||
- Support multiple accounts (stretch goal)
|
||||
|
||||
4. **Organization Access**
|
||||
- List user's organizations
|
||||
- Allow selecting which orgs to access
|
||||
- Remember org selection
|
||||
|
||||
5. **Token Management**
|
||||
- Secure storage using electron's safeStorage or keytar
|
||||
- Automatic token refresh (GitHub OAuth tokens don't expire but can be revoked)
|
||||
- Handle token revocation gracefully
|
||||
|
||||
6. **Fallback to PAT**
|
||||
- Keep existing PAT flow as alternative
|
||||
- "Use Personal Access Token instead" option
|
||||
- Clear migration path from PAT to OAuth
|
||||
|
||||
### Non-Functional Requirements
|
||||
|
||||
- OAuth flow completes in <30 seconds
|
||||
- Token stored securely (encrypted at rest)
|
||||
- Works behind corporate proxies
|
||||
- Graceful offline handling
|
||||
|
||||
## Technical Approach
|
||||
|
||||
### 1. GitHub OAuth App Setup
|
||||
|
||||
Register OAuth App in GitHub:
|
||||
- Application name: "OpenNoodl"
|
||||
- Homepage URL: `https://opennoodl.net`
|
||||
- Callback URL: `noodl://github-callback`
|
||||
|
||||
Store Client ID in app (Client Secret not needed for public clients using PKCE).
|
||||
|
||||
### 2. OAuth Flow Implementation
|
||||
|
||||
```typescript
|
||||
// packages/noodl-editor/src/editor/src/services/GitHubOAuthService.ts
|
||||
|
||||
class GitHubOAuthService {
|
||||
private static instance: GitHubOAuthService;
|
||||
|
||||
// OAuth flow
|
||||
async initiateOAuth(): Promise<void>;
|
||||
async handleCallback(code: string, state: string): Promise<GitHubToken>;
|
||||
|
||||
// Token management
|
||||
async getToken(): Promise<string | null>;
|
||||
async refreshToken(): Promise<string>;
|
||||
async revokeToken(): Promise<void>;
|
||||
|
||||
// Account info
|
||||
async getCurrentUser(): Promise<GitHubUser>;
|
||||
async getOrganizations(): Promise<GitHubOrg[]>;
|
||||
|
||||
// State
|
||||
isAuthenticated(): boolean;
|
||||
onAuthStateChanged(callback: (authenticated: boolean) => void): void;
|
||||
}
|
||||
```
|
||||
|
||||
### 3. PKCE Flow (Recommended for Desktop Apps)
|
||||
|
||||
```typescript
|
||||
// Generate PKCE challenge
|
||||
function generatePKCE(): { verifier: string; challenge: string } {
|
||||
const verifier = crypto.randomBytes(32).toString('base64url');
|
||||
const challenge = crypto
|
||||
.createHash('sha256')
|
||||
.update(verifier)
|
||||
.digest('base64url');
|
||||
return { verifier, challenge };
|
||||
}
|
||||
|
||||
// OAuth URL
|
||||
function getAuthorizationUrl(state: string, challenge: string): string {
|
||||
const params = new URLSearchParams({
|
||||
client_id: GITHUB_CLIENT_ID,
|
||||
redirect_uri: 'noodl://github-callback',
|
||||
scope: 'repo read:org read:user',
|
||||
state,
|
||||
code_challenge: challenge,
|
||||
code_challenge_method: 'S256'
|
||||
});
|
||||
return `https://github.com/login/oauth/authorize?${params}`;
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Deep Link Handler
|
||||
|
||||
```typescript
|
||||
// packages/noodl-editor/src/main/main.js
|
||||
|
||||
// Register protocol handler
|
||||
app.setAsDefaultProtocolClient('noodl');
|
||||
|
||||
// Handle deep links
|
||||
app.on('open-url', (event, url) => {
|
||||
event.preventDefault();
|
||||
if (url.startsWith('noodl://github-callback')) {
|
||||
const params = new URL(url).searchParams;
|
||||
const code = params.get('code');
|
||||
const state = params.get('state');
|
||||
handleGitHubCallback(code, state);
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### 5. Secure Token Storage
|
||||
|
||||
```typescript
|
||||
// Use electron's safeStorage API
|
||||
import { safeStorage } from 'electron';
|
||||
|
||||
async function storeToken(token: string): Promise<void> {
|
||||
const encrypted = safeStorage.encryptString(token);
|
||||
await store.set('github.token', encrypted.toString('base64'));
|
||||
}
|
||||
|
||||
async function getToken(): Promise<string | null> {
|
||||
const encrypted = await store.get('github.token');
|
||||
if (!encrypted) return null;
|
||||
return safeStorage.decryptString(Buffer.from(encrypted, 'base64'));
|
||||
}
|
||||
```
|
||||
|
||||
### 6. Integration with Existing Git Auth
|
||||
|
||||
```typescript
|
||||
// packages/noodl-utils/LocalProjectsModel.ts
|
||||
|
||||
setCurrentGlobalGitAuth(projectId: string) {
|
||||
const func = async (endpoint: string) => {
|
||||
if (endpoint.includes('github.com')) {
|
||||
// Try OAuth token first
|
||||
const oauthToken = await GitHubOAuthService.instance.getToken();
|
||||
if (oauthToken) {
|
||||
return {
|
||||
username: 'oauth2',
|
||||
password: oauthToken
|
||||
};
|
||||
}
|
||||
|
||||
// Fall back to PAT
|
||||
const config = await GitStore.get('github', projectId);
|
||||
return {
|
||||
username: 'noodl',
|
||||
password: config?.password
|
||||
};
|
||||
}
|
||||
// ... rest of existing logic
|
||||
};
|
||||
|
||||
setRequestGitAccount(func);
|
||||
}
|
||||
```
|
||||
|
||||
## Files to Create
|
||||
|
||||
1. `packages/noodl-editor/src/editor/src/services/GitHubOAuthService.ts`
|
||||
2. `packages/noodl-editor/src/editor/src/services/GitHubApiClient.ts`
|
||||
3. `packages/noodl-core-ui/src/preview/launcher/Launcher/components/GitHubAccountCard/GitHubAccountCard.tsx`
|
||||
4. `packages/noodl-core-ui/src/preview/launcher/Launcher/components/GitHubConnectButton/GitHubConnectButton.tsx`
|
||||
5. `packages/noodl-core-ui/src/preview/launcher/Launcher/components/OrgSelector/OrgSelector.tsx`
|
||||
6. `packages/noodl-editor/src/editor/src/views/panels/VersionControlPanel/components/GitProviderPopout/sections/OAuthSection.tsx`
|
||||
|
||||
## Files to Modify
|
||||
|
||||
1. `packages/noodl-editor/src/main/main.js`
|
||||
- Add deep link protocol handler for `noodl://`
|
||||
|
||||
2. `packages/noodl-utils/LocalProjectsModel.ts`
|
||||
- Update `setCurrentGlobalGitAuth` to prefer OAuth token
|
||||
|
||||
3. `packages/noodl-editor/src/editor/src/views/panels/VersionControlPanel/components/GitProviderPopout/GitProviderPopout.tsx`
|
||||
- Add OAuth option alongside PAT
|
||||
|
||||
4. `packages/noodl-core-ui/src/preview/launcher/Launcher/components/LauncherSidebar/LauncherSidebar.tsx`
|
||||
- Add GitHub account display/connect button
|
||||
|
||||
## Implementation Steps
|
||||
|
||||
### Phase 1: OAuth Service Foundation
|
||||
1. Create GitHubOAuthService class
|
||||
2. Implement PKCE flow
|
||||
3. Set up deep link handler in main process
|
||||
4. Implement secure token storage
|
||||
|
||||
### Phase 2: UI Components
|
||||
1. Create GitHubConnectButton
|
||||
2. Create GitHubAccountCard
|
||||
3. Add OAuth section to GitProviderPopout
|
||||
4. Add account display to launcher sidebar
|
||||
|
||||
### Phase 3: API Integration
|
||||
1. Create GitHubApiClient for REST API calls
|
||||
2. Implement user info fetching
|
||||
3. Implement organization listing
|
||||
4. Create OrgSelector component
|
||||
|
||||
### Phase 4: Git Integration
|
||||
1. Update LocalProjectsModel auth function
|
||||
2. Test with git operations
|
||||
3. Handle token expiry/revocation
|
||||
4. Add fallback to PAT
|
||||
|
||||
### Phase 5: Polish
|
||||
1. Error handling and messages
|
||||
2. Offline handling
|
||||
3. Loading states
|
||||
4. Settings persistence
|
||||
|
||||
## Security Considerations
|
||||
|
||||
1. **PKCE**: Use PKCE flow instead of client secret (more secure for desktop apps)
|
||||
2. **Token Storage**: Use electron's safeStorage API (OS-level encryption)
|
||||
3. **State Parameter**: Verify state to prevent CSRF attacks
|
||||
4. **Scope Limitation**: Request minimum required scopes
|
||||
5. **Token Exposure**: Never log tokens, clear from memory when not needed
|
||||
|
||||
## Testing Checklist
|
||||
|
||||
- [ ] OAuth flow completes successfully
|
||||
- [ ] Token stored securely
|
||||
- [ ] Token retrieved correctly for git operations
|
||||
- [ ] Clone works with OAuth token
|
||||
- [ ] Push works with OAuth token
|
||||
- [ ] Pull works with OAuth token
|
||||
- [ ] Disconnect clears token
|
||||
- [ ] Fallback to PAT works
|
||||
- [ ] Organizations listed correctly
|
||||
- [ ] Deep link works on macOS
|
||||
- [ ] Deep link works on Windows
|
||||
- [ ] Handles network errors gracefully
|
||||
- [ ] Handles token revocation gracefully
|
||||
|
||||
## Dependencies
|
||||
|
||||
- DASH-001 (for launcher context to display account)
|
||||
|
||||
### External Dependencies
|
||||
|
||||
May need to add:
|
||||
```json
|
||||
{
|
||||
"keytar": "^7.9.0" // Alternative to safeStorage for older Electron
|
||||
}
|
||||
```
|
||||
|
||||
## Blocked By
|
||||
|
||||
- DASH-001 (Tabbed Navigation) - for launcher UI placement
|
||||
|
||||
## Blocks
|
||||
|
||||
- GIT-003 (Repository Cloning) - needs auth for private repos
|
||||
- COMP-004 (Organization Components) - needs org access
|
||||
|
||||
## Estimated Effort
|
||||
|
||||
- OAuth service: 4-6 hours
|
||||
- Deep link handler: 2-3 hours
|
||||
- UI components: 3-4 hours
|
||||
- Git integration: 2-3 hours
|
||||
- Testing & polish: 3-4 hours
|
||||
- **Total: 14-20 hours**
|
||||
|
||||
## Success Criteria
|
||||
|
||||
1. Users can authenticate with GitHub via OAuth
|
||||
2. OAuth tokens are stored securely
|
||||
3. Git operations work with OAuth tokens
|
||||
4. Users can see their connected account
|
||||
5. Users can disconnect and reconnect
|
||||
6. PAT remains available as fallback
|
||||
7. Flow works on both macOS and Windows
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
- Multiple GitHub account support
|
||||
- GitLab OAuth
|
||||
- Bitbucket OAuth
|
||||
- GitHub Enterprise support
|
||||
- Fine-grained personal access tokens
|
||||
@@ -0,0 +1,426 @@
|
||||
# GIT-002: Git Status Dashboard Visibility
|
||||
|
||||
## Overview
|
||||
|
||||
Surface git status information directly in the project list on the dashboard, allowing users to see at a glance which projects need attention (uncommitted changes, unpushed commits, available updates) without opening each project.
|
||||
|
||||
## Context
|
||||
|
||||
Currently, git status is only visible inside the VersionControlPanel after opening a project. Users with many projects have no way to know which ones have uncommitted changes or need syncing.
|
||||
|
||||
The new launcher already has mock data for git sync status in `LauncherProjectCard`, but it's not connected to real data.
|
||||
|
||||
### Existing Infrastructure
|
||||
|
||||
From `LauncherProjectCard.tsx`:
|
||||
```typescript
|
||||
export enum CloudSyncType {
|
||||
None = 'none',
|
||||
Git = 'git'
|
||||
}
|
||||
|
||||
export interface LauncherProjectData {
|
||||
cloudSyncMeta: {
|
||||
type: CloudSyncType;
|
||||
source?: string; // Remote URL
|
||||
};
|
||||
pullAmount?: number;
|
||||
pushAmount?: number;
|
||||
uncommittedChangesAmount?: number;
|
||||
}
|
||||
```
|
||||
|
||||
From `VersionControlPanel/context/fetch.context.ts`:
|
||||
```typescript
|
||||
// Already calculates:
|
||||
localCommitCount // Commits ahead of remote
|
||||
remoteCommitCount // Commits behind remote
|
||||
workingDirectoryStatus // Uncommitted files
|
||||
```
|
||||
|
||||
## Requirements
|
||||
|
||||
### Functional Requirements
|
||||
|
||||
1. **Status Indicators in Project List**
|
||||
- Not Initialized: Gray indicator, no version control
|
||||
- Local Only: Yellow indicator, git but no remote
|
||||
- Synced: Green checkmark, up to date
|
||||
- Has Uncommitted Changes: Yellow dot, local modifications
|
||||
- Ahead: Blue up arrow, local commits to push
|
||||
- Behind: Orange down arrow, remote commits to pull
|
||||
- Diverged: Red warning, both ahead and behind
|
||||
|
||||
2. **Status Details**
|
||||
- Tooltip showing details on hover
|
||||
- "3 commits to push, 2 to pull"
|
||||
- "5 uncommitted files"
|
||||
- Last sync time
|
||||
|
||||
3. **Quick Actions**
|
||||
- Quick sync button (fetch + show status)
|
||||
- Link to open Version Control panel
|
||||
|
||||
4. **Background Refresh**
|
||||
- Check status on dashboard load
|
||||
- Periodic refresh (every 5 minutes)
|
||||
- Manual refresh button
|
||||
- Status cached to avoid repeated git operations
|
||||
|
||||
5. **Performance**
|
||||
- Parallel status checks for multiple projects
|
||||
- Debounced/throttled to avoid overwhelming git
|
||||
- Cached results with TTL
|
||||
|
||||
### Non-Functional Requirements
|
||||
|
||||
- Status check per project: <500ms
|
||||
- Dashboard load not blocked by status checks
|
||||
- Works offline (shows cached/stale data)
|
||||
|
||||
## Data Model
|
||||
|
||||
### Git Status Types
|
||||
|
||||
```typescript
|
||||
enum ProjectGitStatus {
|
||||
Unknown = 'unknown', // Haven't checked yet
|
||||
NotInitialized = 'not-init', // Not a git repo
|
||||
LocalOnly = 'local-only', // Git but no remote
|
||||
Synced = 'synced', // Up to date with remote
|
||||
Uncommitted = 'uncommitted', // Has local changes
|
||||
Ahead = 'ahead', // Has commits to push
|
||||
Behind = 'behind', // Has commits to pull
|
||||
Diverged = 'diverged', // Both ahead and behind
|
||||
Error = 'error' // Failed to check
|
||||
}
|
||||
|
||||
interface ProjectGitStatusDetails {
|
||||
status: ProjectGitStatus;
|
||||
aheadCount?: number;
|
||||
behindCount?: number;
|
||||
uncommittedCount?: number;
|
||||
lastFetchTime?: number;
|
||||
remoteUrl?: string;
|
||||
currentBranch?: string;
|
||||
error?: string;
|
||||
}
|
||||
```
|
||||
|
||||
### Cache Structure
|
||||
|
||||
```typescript
|
||||
interface GitStatusCache {
|
||||
[projectPath: string]: {
|
||||
status: ProjectGitStatusDetails;
|
||||
checkedAt: number;
|
||||
isStale: boolean;
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
## Technical Approach
|
||||
|
||||
### 1. Git Status Service
|
||||
|
||||
```typescript
|
||||
// packages/noodl-editor/src/editor/src/services/ProjectGitStatusService.ts
|
||||
|
||||
class ProjectGitStatusService {
|
||||
private static instance: ProjectGitStatusService;
|
||||
private cache: GitStatusCache = {};
|
||||
private checkQueue: Set<string> = new Set();
|
||||
private isChecking = false;
|
||||
|
||||
// Check single project
|
||||
async checkStatus(projectPath: string): Promise<ProjectGitStatusDetails>;
|
||||
|
||||
// Check multiple projects (batched)
|
||||
async checkStatusBatch(projectPaths: string[]): Promise<Map<string, ProjectGitStatusDetails>>;
|
||||
|
||||
// Get cached status
|
||||
getCachedStatus(projectPath: string): ProjectGitStatusDetails | null;
|
||||
|
||||
// Clear cache
|
||||
invalidateCache(projectPath?: string): void;
|
||||
|
||||
// Subscribe to status changes
|
||||
onStatusChanged(callback: (path: string, status: ProjectGitStatusDetails) => void): () => void;
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Status Check Implementation
|
||||
|
||||
```typescript
|
||||
async checkStatus(projectPath: string): Promise<ProjectGitStatusDetails> {
|
||||
const git = new Git(mergeProject);
|
||||
|
||||
try {
|
||||
// Check if it's a git repo
|
||||
const gitPath = await getTopLevelWorkingDirectory(projectPath);
|
||||
if (!gitPath) {
|
||||
return { status: ProjectGitStatus.NotInitialized };
|
||||
}
|
||||
|
||||
await git.openRepository(projectPath);
|
||||
|
||||
// Check for remote
|
||||
const remoteName = await git.getRemoteName();
|
||||
if (!remoteName) {
|
||||
return { status: ProjectGitStatus.LocalOnly };
|
||||
}
|
||||
|
||||
// Get working directory status
|
||||
const workingStatus = await git.status();
|
||||
const uncommittedCount = workingStatus.length;
|
||||
|
||||
// Get commit counts (requires fetch for accuracy)
|
||||
const commits = await git.getCommitsCurrentBranch();
|
||||
const aheadCount = commits.filter(c => c.isLocalAhead).length;
|
||||
const behindCount = commits.filter(c => c.isRemoteAhead).length;
|
||||
|
||||
// Determine status
|
||||
let status: ProjectGitStatus;
|
||||
if (uncommittedCount > 0) {
|
||||
status = ProjectGitStatus.Uncommitted;
|
||||
} else if (aheadCount > 0 && behindCount > 0) {
|
||||
status = ProjectGitStatus.Diverged;
|
||||
} else if (aheadCount > 0) {
|
||||
status = ProjectGitStatus.Ahead;
|
||||
} else if (behindCount > 0) {
|
||||
status = ProjectGitStatus.Behind;
|
||||
} else {
|
||||
status = ProjectGitStatus.Synced;
|
||||
}
|
||||
|
||||
return {
|
||||
status,
|
||||
aheadCount,
|
||||
behindCount,
|
||||
uncommittedCount,
|
||||
lastFetchTime: Date.now(),
|
||||
remoteUrl: git.OriginUrl,
|
||||
currentBranch: await git.getCurrentBranchName()
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
status: ProjectGitStatus.Error,
|
||||
error: error.message
|
||||
};
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Dashboard Integration Hook
|
||||
|
||||
```typescript
|
||||
// packages/noodl-core-ui/src/preview/launcher/Launcher/hooks/useProjectGitStatus.ts
|
||||
|
||||
function useProjectGitStatus(projectPaths: string[]) {
|
||||
const [statuses, setStatuses] = useState<Map<string, ProjectGitStatusDetails>>(new Map());
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
// Initial check
|
||||
ProjectGitStatusService.instance
|
||||
.checkStatusBatch(projectPaths)
|
||||
.then(setStatuses)
|
||||
.finally(() => setIsLoading(false));
|
||||
|
||||
// Subscribe to updates
|
||||
const unsubscribe = ProjectGitStatusService.instance.onStatusChanged((path, status) => {
|
||||
setStatuses(prev => new Map(prev).set(path, status));
|
||||
});
|
||||
|
||||
return unsubscribe;
|
||||
}, [projectPaths]);
|
||||
|
||||
const refresh = useCallback(() => {
|
||||
ProjectGitStatusService.instance.invalidateCache();
|
||||
// Re-trigger check
|
||||
}, []);
|
||||
|
||||
return { statuses, isLoading, refresh };
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Visual Status Badge
|
||||
|
||||
Already started in DASH-002 as `GitStatusBadge`, but needs real data connection:
|
||||
|
||||
```typescript
|
||||
// Enhanced GitStatusBadge props
|
||||
interface GitStatusBadgeProps {
|
||||
status: ProjectGitStatus;
|
||||
details: ProjectGitStatusDetails;
|
||||
showTooltip?: boolean;
|
||||
size?: 'small' | 'medium';
|
||||
}
|
||||
```
|
||||
|
||||
## Files to Create
|
||||
|
||||
1. `packages/noodl-editor/src/editor/src/services/ProjectGitStatusService.ts`
|
||||
2. `packages/noodl-core-ui/src/preview/launcher/Launcher/hooks/useProjectGitStatus.ts`
|
||||
3. `packages/noodl-core-ui/src/preview/launcher/Launcher/components/GitStatusBadge/GitStatusBadge.tsx` (if not created in DASH-002)
|
||||
4. `packages/noodl-core-ui/src/preview/launcher/Launcher/components/GitStatusBadge/GitStatusBadge.module.scss`
|
||||
5. `packages/noodl-core-ui/src/preview/launcher/Launcher/components/GitStatusTooltip/GitStatusTooltip.tsx`
|
||||
|
||||
## Files to Modify
|
||||
|
||||
1. `packages/noodl-core-ui/src/preview/launcher/Launcher/views/Projects.tsx`
|
||||
- Use `useProjectGitStatus` hook
|
||||
- Pass status to project cards/rows
|
||||
|
||||
2. `packages/noodl-core-ui/src/preview/launcher/Launcher/components/ProjectList/ProjectListRow.tsx`
|
||||
- Display GitStatusBadge with real data
|
||||
|
||||
3. `packages/noodl-core-ui/src/preview/launcher/Launcher/components/LauncherProjectCard/LauncherProjectCard.tsx`
|
||||
- Update to use real status data (for grid view)
|
||||
|
||||
4. `packages/noodl-core-ui/src/preview/launcher/Launcher/Launcher.tsx`
|
||||
- Replace mock project data with real data connection
|
||||
|
||||
## Visual Specifications
|
||||
|
||||
### Status Badge Icons & Colors
|
||||
|
||||
| Status | Icon | Color | Background |
|
||||
|--------|------|-------|------------|
|
||||
| Unknown | ◌ (spinner) | Gray | Transparent |
|
||||
| Not Initialized | ⊘ | Gray (#6B7280) | Transparent |
|
||||
| Local Only | 💾 | Yellow (#EAB308) | Yellow/10 |
|
||||
| Synced | ✓ | Green (#22C55E) | Green/10 |
|
||||
| Uncommitted | ● | Yellow (#EAB308) | Yellow/10 |
|
||||
| Ahead | ↑ | Blue (#3B82F6) | Blue/10 |
|
||||
| Behind | ↓ | Orange (#F97316) | Orange/10 |
|
||||
| Diverged | ⚠ | Red (#EF4444) | Red/10 |
|
||||
| Error | ✕ | Red (#EF4444) | Red/10 |
|
||||
|
||||
### Tooltip Content
|
||||
|
||||
```
|
||||
┌─────────────────────────────────┐
|
||||
│ main branch │
|
||||
│ ↑ 3 commits to push │
|
||||
│ ↓ 2 commits to pull │
|
||||
│ ● 5 uncommitted files │
|
||||
│ │
|
||||
│ Last synced: 10 minutes ago │
|
||||
│ Remote: github.com/user/repo │
|
||||
└─────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Implementation Steps
|
||||
|
||||
### Phase 1: Service Foundation
|
||||
1. Create ProjectGitStatusService
|
||||
2. Implement single project status check
|
||||
3. Add caching logic
|
||||
4. Create batch checking with parallelization
|
||||
|
||||
### Phase 2: Hook & Data Flow
|
||||
1. Create useProjectGitStatus hook
|
||||
2. Connect to Projects view
|
||||
3. Replace mock data with real data
|
||||
4. Add loading states
|
||||
|
||||
### Phase 3: Visual Components
|
||||
1. Create/update GitStatusBadge
|
||||
2. Create GitStatusTooltip
|
||||
3. Integrate into ProjectListRow
|
||||
4. Integrate into LauncherProjectCard
|
||||
|
||||
### Phase 4: Refresh & Background
|
||||
1. Add manual refresh button
|
||||
2. Implement periodic background refresh
|
||||
3. Add refresh on window focus
|
||||
4. Handle offline state
|
||||
|
||||
### Phase 5: Polish
|
||||
1. Performance optimization
|
||||
2. Error handling
|
||||
3. Stale data indicators
|
||||
4. Animation on status change
|
||||
|
||||
## Performance Considerations
|
||||
|
||||
1. **Parallel Checking**: Check up to 5 projects simultaneously
|
||||
2. **Debouncing**: Don't re-check same project within 10 seconds
|
||||
3. **Cache TTL**: Status valid for 5 minutes, stale after
|
||||
4. **Lazy Loading**: Only check visible projects first
|
||||
5. **Background Priority**: Use requestIdleCallback for non-visible
|
||||
|
||||
```typescript
|
||||
// Throttled batch check
|
||||
async checkStatusBatch(projectPaths: string[]): Promise<Map<string, ProjectGitStatusDetails>> {
|
||||
const CONCURRENCY = 5;
|
||||
const results = new Map();
|
||||
|
||||
for (let i = 0; i < projectPaths.length; i += CONCURRENCY) {
|
||||
const batch = projectPaths.slice(i, i + CONCURRENCY);
|
||||
const batchResults = await Promise.all(
|
||||
batch.map(path => this.checkStatus(path))
|
||||
);
|
||||
batch.forEach((path, idx) => results.set(path, batchResults[idx]));
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
```
|
||||
|
||||
## Testing Checklist
|
||||
|
||||
- [ ] Detects non-git project correctly
|
||||
- [ ] Detects git project without remote
|
||||
- [ ] Shows synced status when up to date
|
||||
- [ ] Shows uncommitted when local changes exist
|
||||
- [ ] Shows ahead when local commits exist
|
||||
- [ ] Shows behind when remote commits exist
|
||||
- [ ] Shows diverged when both ahead and behind
|
||||
- [ ] Tooltip shows correct details
|
||||
- [ ] Refresh updates status
|
||||
- [ ] Status persists across dashboard navigation
|
||||
- [ ] Handles deleted projects gracefully
|
||||
- [ ] Handles network errors gracefully
|
||||
- [ ] Performance acceptable with 20+ projects
|
||||
|
||||
## Dependencies
|
||||
|
||||
- DASH-002 (Project List Redesign) - for UI integration
|
||||
|
||||
## Blocked By
|
||||
|
||||
- DASH-002
|
||||
|
||||
## Blocks
|
||||
|
||||
- GIT-004 (Auto-initialization) - needs status detection
|
||||
- GIT-005 (Enhanced Push/Pull) - shares status infrastructure
|
||||
|
||||
## Estimated Effort
|
||||
|
||||
- Status service: 3-4 hours
|
||||
- Hook & data flow: 2-3 hours
|
||||
- Visual components: 2-3 hours
|
||||
- Background refresh: 2-3 hours
|
||||
- Polish & testing: 2-3 hours
|
||||
- **Total: 11-16 hours**
|
||||
|
||||
## Success Criteria
|
||||
|
||||
1. Git status visible at a glance in project list
|
||||
2. Status updates without manual refresh
|
||||
3. Tooltip provides actionable details
|
||||
4. Performance acceptable with many projects
|
||||
5. Works offline with cached data
|
||||
6. Handles edge cases gracefully
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
- Quick commit from dashboard
|
||||
- Quick push/pull buttons per project
|
||||
- Bulk sync all projects
|
||||
- Branch indicator
|
||||
- Last commit message preview
|
||||
- Contributor avatars (from git log)
|
||||
@@ -0,0 +1,346 @@
|
||||
# GIT-003: Repository Cloning
|
||||
|
||||
## Overview
|
||||
|
||||
Add the ability to clone GitHub repositories directly from the Noodl dashboard, similar to how VS Code handles cloning. Users can browse their repositories, select one, choose a local folder, and have the project cloned and opened automatically.
|
||||
|
||||
## Context
|
||||
|
||||
Currently, to work with an existing Noodl project from GitHub, users must:
|
||||
1. Clone the repo manually using git CLI or another tool
|
||||
2. Open Noodl
|
||||
3. Use "Open folder" to navigate to the cloned project
|
||||
|
||||
This task streamlines that to:
|
||||
1. Click "Clone from GitHub"
|
||||
2. Select repository
|
||||
3. Choose folder
|
||||
4. Project opens automatically
|
||||
|
||||
### Existing Infrastructure
|
||||
|
||||
The `noodl-git` package already has clone functionality:
|
||||
```typescript
|
||||
// From git.ts
|
||||
async clone({ url, directory, singleBranch, onProgress }: GitCloneOptions): Promise<void>
|
||||
```
|
||||
|
||||
And clone tests show it working:
|
||||
```typescript
|
||||
await git.clone({
|
||||
url: 'https://github.com/github/testrepo.git',
|
||||
directory: tempDir,
|
||||
onProgress: (progress) => { result.push(progress); }
|
||||
});
|
||||
```
|
||||
|
||||
## Requirements
|
||||
|
||||
### Functional Requirements
|
||||
|
||||
1. **Clone Entry Points**
|
||||
- "Clone Repository" button in dashboard toolbar
|
||||
- "Clone from GitHub" option in "Create Project" menu
|
||||
- Right-click empty area → "Clone Repository"
|
||||
|
||||
2. **Repository Browser**
|
||||
- List user's repositories (requires OAuth from GIT-001)
|
||||
- List organization repositories
|
||||
- Search/filter repositories
|
||||
- Show repo details: name, description, visibility, last updated
|
||||
- "Clone URL" input for direct URL entry
|
||||
|
||||
3. **Folder Selection**
|
||||
- Native folder picker dialog
|
||||
- Remember last used parent folder
|
||||
- Validate folder is empty or doesn't exist
|
||||
- Show full path before cloning
|
||||
|
||||
4. **Clone Process**
|
||||
- Progress indicator with stages
|
||||
- Cancel button
|
||||
- Error handling with clear messages
|
||||
- Retry option on failure
|
||||
|
||||
5. **Post-Clone Actions**
|
||||
- Automatically open project in editor
|
||||
- Add to recent projects
|
||||
- Show success notification
|
||||
|
||||
6. **Branch Selection (Optional)**
|
||||
- Default to main/master
|
||||
- Option to select different branch
|
||||
- Shallow clone option for large repos
|
||||
|
||||
### Non-Functional Requirements
|
||||
|
||||
- Clone progress updates smoothly
|
||||
- Cancellation works immediately
|
||||
- Handles large repositories
|
||||
- Works with private repositories (with auth)
|
||||
- Clear error messages for common failures
|
||||
|
||||
## Technical Approach
|
||||
|
||||
### 1. Clone Service
|
||||
|
||||
```typescript
|
||||
// packages/noodl-editor/src/editor/src/services/CloneService.ts
|
||||
|
||||
interface CloneOptions {
|
||||
url: string;
|
||||
directory: string;
|
||||
branch?: string;
|
||||
shallow?: boolean;
|
||||
onProgress?: (progress: CloneProgress) => void;
|
||||
}
|
||||
|
||||
interface CloneProgress {
|
||||
phase: 'counting' | 'compressing' | 'receiving' | 'resolving' | 'checking-out';
|
||||
percent: number;
|
||||
message: string;
|
||||
}
|
||||
|
||||
interface CloneResult {
|
||||
success: boolean;
|
||||
projectPath?: string;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
class CloneService {
|
||||
private static instance: CloneService;
|
||||
private activeClone: AbortController | null = null;
|
||||
|
||||
async clone(options: CloneOptions): Promise<CloneResult>;
|
||||
cancel(): void;
|
||||
|
||||
// GitHub API integration
|
||||
async listUserRepos(): Promise<GitHubRepo[]>;
|
||||
async listOrgRepos(orgName: string): Promise<GitHubRepo[]>;
|
||||
async searchRepos(query: string): Promise<GitHubRepo[]>;
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Repository Browser Component
|
||||
|
||||
```typescript
|
||||
// RepoBrowser.tsx
|
||||
|
||||
interface RepoBrowserProps {
|
||||
onSelect: (repo: GitHubRepo) => void;
|
||||
onUrlSubmit: (url: string) => void;
|
||||
}
|
||||
|
||||
interface GitHubRepo {
|
||||
id: number;
|
||||
name: string;
|
||||
fullName: string;
|
||||
description: string;
|
||||
private: boolean;
|
||||
htmlUrl: string;
|
||||
cloneUrl: string;
|
||||
sshUrl: string;
|
||||
defaultBranch: string;
|
||||
updatedAt: string;
|
||||
owner: {
|
||||
login: string;
|
||||
avatarUrl: string;
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Clone Modal Flow
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ Clone Repository [×] │
|
||||
├─────────────────────────────────────────────────────────────────────┤
|
||||
│ ┌─────────────────────────────────────────────────────────────────┐ │
|
||||
│ │ [🔍 Search repositories... ] │ │
|
||||
│ └─────────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ [Your Repositories ▾] [Organizations: acme-corp ▾] │
|
||||
│ │
|
||||
│ ┌─────────────────────────────────────────────────────────────────┐ │
|
||||
│ │ 📁 noodl-project-template ★ 12 2 days ago │ │
|
||||
│ │ A starter template for Noodl projects [Private 🔒] │ │
|
||||
│ ├─────────────────────────────────────────────────────────────────┤ │
|
||||
│ │ 📁 my-awesome-app ★ 5 1 week ago │ │
|
||||
│ │ An awesome application built with Noodl [Public 🌍] │ │
|
||||
│ ├─────────────────────────────────────────────────────────────────┤ │
|
||||
│ │ 📁 client-dashboard ★ 0 3 weeks ago │ │
|
||||
│ │ Dashboard for client project [Private 🔒] │ │
|
||||
│ └─────────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ─── OR enter repository URL ───────────────────────────────────── │
|
||||
│ ┌─────────────────────────────────────────────────────────────────┐ │
|
||||
│ │ https://github.com/user/repo.git │ │
|
||||
│ └─────────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ [Cancel] [Next →] │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 4. Folder Selection Step
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ Clone Repository [×] │
|
||||
├─────────────────────────────────────────────────────────────────────┤
|
||||
│ Repository: github.com/user/my-awesome-app │
|
||||
│ │
|
||||
│ Clone to: │
|
||||
│ ┌─────────────────────────────────────────────────────────────────┐ │
|
||||
│ │ /Users/richard/Projects/my-awesome-app [Browse...] │ │
|
||||
│ └─────────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ☐ Clone only the default branch (faster) │
|
||||
│ │
|
||||
│ [← Back] [Cancel] [Clone]│
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 5. Progress Step
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ Cloning Repository [×] │
|
||||
├─────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ Cloning my-awesome-app... │
|
||||
│ │
|
||||
│ ┌─────────────────────────────────────────────────────────────────┐ │
|
||||
│ │████████████████████████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░│ 42% │ │
|
||||
│ └─────────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ Receiving objects: 1,234 of 2,891 │
|
||||
│ │
|
||||
│ [Cancel] │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Files to Create
|
||||
|
||||
1. `packages/noodl-editor/src/editor/src/services/CloneService.ts`
|
||||
2. `packages/noodl-core-ui/src/preview/launcher/Launcher/components/CloneModal/CloneModal.tsx`
|
||||
3. `packages/noodl-core-ui/src/preview/launcher/Launcher/components/CloneModal/CloneModal.module.scss`
|
||||
4. `packages/noodl-core-ui/src/preview/launcher/Launcher/components/CloneModal/RepoBrowser.tsx`
|
||||
5. `packages/noodl-core-ui/src/preview/launcher/Launcher/components/CloneModal/FolderSelector.tsx`
|
||||
6. `packages/noodl-core-ui/src/preview/launcher/Launcher/components/CloneModal/CloneProgress.tsx`
|
||||
7. `packages/noodl-core-ui/src/preview/launcher/Launcher/components/RepoCard/RepoCard.tsx`
|
||||
8. `packages/noodl-editor/src/editor/src/services/GitHubApiClient.ts` (if not created in GIT-001)
|
||||
|
||||
## Files to Modify
|
||||
|
||||
1. `packages/noodl-core-ui/src/preview/launcher/Launcher/views/Projects.tsx`
|
||||
- Add "Clone Repository" button to toolbar
|
||||
|
||||
2. `packages/noodl-core-ui/src/preview/launcher/Launcher/Launcher.tsx`
|
||||
- Add clone modal state and rendering
|
||||
|
||||
3. `packages/noodl-utils/LocalProjectsModel.ts`
|
||||
- Add cloned project to recent projects list
|
||||
|
||||
4. `packages/noodl-editor/src/editor/src/views/projectsview.ts`
|
||||
- Ensure cloned project can be opened (may already work)
|
||||
|
||||
## Implementation Steps
|
||||
|
||||
### Phase 1: Clone Service
|
||||
1. Create CloneService wrapper around noodl-git
|
||||
2. Add progress normalization
|
||||
3. Add cancellation support
|
||||
4. Test with public repository
|
||||
|
||||
### Phase 2: URL-Based Cloning
|
||||
1. Create basic CloneModal with URL input
|
||||
2. Create FolderSelector component
|
||||
3. Create CloneProgress component
|
||||
4. Wire up clone flow
|
||||
|
||||
### Phase 3: Repository Browser
|
||||
1. Create GitHubApiClient (or extend from GIT-001)
|
||||
2. Create RepoBrowser component
|
||||
3. Create RepoCard component
|
||||
4. Add search/filter functionality
|
||||
|
||||
### Phase 4: Integration
|
||||
1. Add clone button to dashboard
|
||||
2. Open cloned project automatically
|
||||
3. Add to recent projects
|
||||
4. Handle errors gracefully
|
||||
|
||||
### Phase 5: Polish
|
||||
1. Remember last folder
|
||||
2. Add branch selection
|
||||
3. Add shallow clone option
|
||||
4. Improve error messages
|
||||
|
||||
## Error Handling
|
||||
|
||||
| Error | User Message | Recovery |
|
||||
|-------|--------------|----------|
|
||||
| Network error | "Unable to connect. Check your internet connection." | Retry button |
|
||||
| Auth required | "This repository requires authentication. Connect your GitHub account." | Link to OAuth |
|
||||
| Repo not found | "Repository not found. Check the URL and try again." | Edit URL |
|
||||
| Permission denied | "You don't have access to this repository." | Suggest checking permissions |
|
||||
| Folder not empty | "The selected folder is not empty. Choose an empty folder." | Folder picker |
|
||||
| Disk full | "Not enough disk space to clone this repository." | Show required space |
|
||||
|
||||
## Testing Checklist
|
||||
|
||||
- [ ] Clone public repository via URL
|
||||
- [ ] Clone private repository with OAuth token
|
||||
- [ ] Clone private repository with PAT
|
||||
- [ ] Repository browser shows user repos
|
||||
- [ ] Repository browser shows org repos
|
||||
- [ ] Search/filter works
|
||||
- [ ] Folder picker opens and works
|
||||
- [ ] Progress updates smoothly
|
||||
- [ ] Cancel stops clone in progress
|
||||
- [ ] Cloned project opens automatically
|
||||
- [ ] Project appears in recent projects
|
||||
- [ ] Error messages are helpful
|
||||
- [ ] Works with various repo sizes
|
||||
- [ ] Handles repos with submodules
|
||||
|
||||
## Dependencies
|
||||
|
||||
- GIT-001 (GitHub OAuth) - for repository browser with private repos
|
||||
- DASH-001 (Tabbed Navigation) - for dashboard integration
|
||||
|
||||
## Blocked By
|
||||
|
||||
- GIT-001 (partially - URL cloning works without OAuth)
|
||||
|
||||
## Blocks
|
||||
|
||||
- COMP-004 (Organization Components) - uses similar repo browsing
|
||||
|
||||
## Estimated Effort
|
||||
|
||||
- Clone service: 2-3 hours
|
||||
- URL-based clone modal: 3-4 hours
|
||||
- Repository browser: 4-5 hours
|
||||
- Integration & auto-open: 2-3 hours
|
||||
- Polish & error handling: 2-3 hours
|
||||
- **Total: 13-18 hours**
|
||||
|
||||
## Success Criteria
|
||||
|
||||
1. Users can clone by entering a URL
|
||||
2. Users can browse and select their repositories
|
||||
3. Clone progress is visible and accurate
|
||||
4. Cloned projects open automatically
|
||||
5. Private repos work with authentication
|
||||
6. Errors are handled gracefully
|
||||
7. Process can be cancelled
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
- Clone from other providers (GitLab, Bitbucket)
|
||||
- Clone specific branch/tag
|
||||
- Clone with submodules options
|
||||
- Clone into new project template
|
||||
- Clone history (recently cloned repos)
|
||||
- Detect Noodl projects vs generic repos
|
||||
@@ -0,0 +1,388 @@
|
||||
# GIT-004: Auto-Initialization & Commit Encouragement
|
||||
|
||||
## Overview
|
||||
|
||||
Make version control a default part of the Noodl workflow by automatically initializing git for new projects and gently encouraging regular commits. This helps users avoid losing work and prepares them for collaboration.
|
||||
|
||||
## Context
|
||||
|
||||
Currently:
|
||||
- New projects are not git-initialized by default
|
||||
- Users must manually open Version Control panel and initialize
|
||||
- There's no prompting to commit changes
|
||||
- Closing a project with uncommitted changes has no warning
|
||||
|
||||
Many Noodl users are designers or low-code developers who may not be familiar with git. By making version control automatic and unobtrusive, we help them develop good habits without requiring git expertise.
|
||||
|
||||
### Existing Infrastructure
|
||||
|
||||
From `LocalProjectsModel.ts`:
|
||||
```typescript
|
||||
async isGitProject(project: ProjectModel): Promise<boolean> {
|
||||
const gitPath = await getTopLevelWorkingDirectory(project._retainedProjectDirectory);
|
||||
return gitPath !== null;
|
||||
}
|
||||
```
|
||||
|
||||
From `git.ts`:
|
||||
```typescript
|
||||
async initNewRepo(baseDir: string, options?: { bare: boolean }): Promise<void> {
|
||||
if (this.baseDir) return;
|
||||
this.baseDir = await init(baseDir, options);
|
||||
await this._setupRepository();
|
||||
}
|
||||
```
|
||||
|
||||
## Requirements
|
||||
|
||||
### Functional Requirements
|
||||
|
||||
1. **Auto-Initialization**
|
||||
- New projects are git-initialized by default
|
||||
- Initial commit with project creation
|
||||
- Option to disable in settings
|
||||
- Existing non-git projects can be initialized easily
|
||||
|
||||
2. **Commit Encouragement**
|
||||
- Periodic reminder when changes are uncommitted
|
||||
- Reminder appears as subtle notification, not modal
|
||||
- "Commit now" quick action
|
||||
- "Remind me later" option
|
||||
- Configurable reminder interval
|
||||
|
||||
3. **Quick Commit**
|
||||
- One-click commit from notification
|
||||
- Simple commit message input
|
||||
- Default message suggestion
|
||||
- Option to open full Version Control panel
|
||||
|
||||
4. **Close Warning**
|
||||
- Warning when closing project with uncommitted changes
|
||||
- Show number of uncommitted files
|
||||
- Options: "Commit & Close", "Close Anyway", "Cancel"
|
||||
- Can be disabled in settings
|
||||
|
||||
5. **Settings**
|
||||
- Enable/disable auto-initialization
|
||||
- Enable/disable commit reminders
|
||||
- Reminder interval (15min, 30min, 1hr, 2hr)
|
||||
- Enable/disable close warning
|
||||
|
||||
### Non-Functional Requirements
|
||||
|
||||
- Reminders are non-intrusive
|
||||
- Quick commit is fast (<2 seconds)
|
||||
- Auto-init doesn't slow project creation
|
||||
- Works offline
|
||||
|
||||
## Technical Approach
|
||||
|
||||
### 1. Auto-Initialization in Project Creation
|
||||
|
||||
```typescript
|
||||
// packages/noodl-editor/src/editor/src/models/projectmodel.ts
|
||||
|
||||
async createNewProject(name: string, template?: string): Promise<ProjectModel> {
|
||||
const project = await this._createProject(name, template);
|
||||
|
||||
// Auto-initialize git if enabled
|
||||
if (EditorSettings.instance.get('git.autoInitialize') !== false) {
|
||||
try {
|
||||
const git = new Git(mergeProject);
|
||||
await git.initNewRepo(project._retainedProjectDirectory);
|
||||
await git.commit('Initial commit');
|
||||
} catch (error) {
|
||||
console.warn('Failed to auto-initialize git:', error);
|
||||
// Don't fail project creation if git init fails
|
||||
}
|
||||
}
|
||||
|
||||
return project;
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Commit Reminder Service
|
||||
|
||||
```typescript
|
||||
// packages/noodl-editor/src/editor/src/services/CommitReminderService.ts
|
||||
|
||||
class CommitReminderService {
|
||||
private static instance: CommitReminderService;
|
||||
private reminderTimer: NodeJS.Timer | null = null;
|
||||
private lastRemindedAt: number = 0;
|
||||
|
||||
// Start monitoring for uncommitted changes
|
||||
start(): void;
|
||||
stop(): void;
|
||||
|
||||
// Check if reminder should show
|
||||
shouldShowReminder(): Promise<boolean>;
|
||||
|
||||
// Show/dismiss reminder
|
||||
showReminder(): void;
|
||||
dismissReminder(snoozeMinutes?: number): void;
|
||||
|
||||
// Events
|
||||
onReminderTriggered(callback: () => void): () => void;
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Quick Commit Component
|
||||
|
||||
```typescript
|
||||
// packages/noodl-core-ui/src/components/git/QuickCommitPopup/QuickCommitPopup.tsx
|
||||
|
||||
interface QuickCommitPopupProps {
|
||||
uncommittedCount: number;
|
||||
suggestedMessage: string;
|
||||
onCommit: (message: string) => Promise<void>;
|
||||
onDismiss: () => void;
|
||||
onOpenFullPanel: () => void;
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Close Warning Dialog
|
||||
|
||||
```typescript
|
||||
// packages/noodl-core-ui/src/components/git/UnsavedChangesDialog/UnsavedChangesDialog.tsx
|
||||
|
||||
interface UnsavedChangesDialogProps {
|
||||
uncommittedCount: number;
|
||||
onCommitAndClose: () => Promise<void>;
|
||||
onCloseAnyway: () => void;
|
||||
onCancel: () => void;
|
||||
}
|
||||
```
|
||||
|
||||
### 5. Default Commit Messages
|
||||
|
||||
```typescript
|
||||
// Smart default commit message generation
|
||||
function generateDefaultCommitMessage(changes: GitStatus[]): string {
|
||||
const added = changes.filter(c => c.status === 'added');
|
||||
const modified = changes.filter(c => c.status === 'modified');
|
||||
const deleted = changes.filter(c => c.status === 'deleted');
|
||||
|
||||
const parts: string[] = [];
|
||||
|
||||
if (added.length > 0) {
|
||||
if (added.length === 1) {
|
||||
parts.push(`Add ${getComponentName(added[0].path)}`);
|
||||
} else {
|
||||
parts.push(`Add ${added.length} files`);
|
||||
}
|
||||
}
|
||||
|
||||
if (modified.length > 0) {
|
||||
if (modified.length === 1) {
|
||||
parts.push(`Update ${getComponentName(modified[0].path)}`);
|
||||
} else {
|
||||
parts.push(`Update ${modified.length} files`);
|
||||
}
|
||||
}
|
||||
|
||||
if (deleted.length > 0) {
|
||||
parts.push(`Remove ${deleted.length} files`);
|
||||
}
|
||||
|
||||
return parts.join(', ') || 'Update project';
|
||||
}
|
||||
```
|
||||
|
||||
## UI Mockups
|
||||
|
||||
### Commit Reminder Notification
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ 💾 You have 5 uncommitted changes │
|
||||
│ │
|
||||
│ It's been 30 minutes since your last commit. │
|
||||
│ │
|
||||
│ [Commit Now] [Remind Me Later ▾] [Dismiss] │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Quick Commit Popup
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Quick Commit [×] │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ 5 files changed │
|
||||
│ │
|
||||
│ Message: │
|
||||
│ ┌─────────────────────────────────────────────────────────┐ │
|
||||
│ │ Update LoginPage and add UserProfile component │ │
|
||||
│ └─────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ [Open Version Control] [Cancel] [Commit] │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Close Warning Dialog
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ ⚠️ Uncommitted Changes [×] │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ You have 5 uncommitted changes in this project. │
|
||||
│ │
|
||||
│ These changes will be preserved locally but not versioned. │
|
||||
│ To keep a history of your work, commit before closing. │
|
||||
│ │
|
||||
│ ☐ Don't show this again │
|
||||
│ │
|
||||
│ [Cancel] [Close Anyway] [Commit & Close] │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Files to Create
|
||||
|
||||
1. `packages/noodl-editor/src/editor/src/services/CommitReminderService.ts`
|
||||
2. `packages/noodl-core-ui/src/components/git/QuickCommitPopup/QuickCommitPopup.tsx`
|
||||
3. `packages/noodl-core-ui/src/components/git/QuickCommitPopup/QuickCommitPopup.module.scss`
|
||||
4. `packages/noodl-core-ui/src/components/git/UnsavedChangesDialog/UnsavedChangesDialog.tsx`
|
||||
5. `packages/noodl-core-ui/src/components/git/CommitReminderToast/CommitReminderToast.tsx`
|
||||
6. `packages/noodl-editor/src/editor/src/utils/git/defaultCommitMessage.ts`
|
||||
|
||||
## Files to Modify
|
||||
|
||||
1. `packages/noodl-editor/src/editor/src/models/projectmodel.ts`
|
||||
- Add auto-initialization in project creation
|
||||
|
||||
2. `packages/noodl-editor/src/editor/src/pages/EditorPage/EditorPage.tsx`
|
||||
- Add close warning handler
|
||||
- Integrate CommitReminderService
|
||||
|
||||
3. `packages/noodl-utils/editorsettings.ts`
|
||||
- Add git-related settings
|
||||
|
||||
4. `packages/noodl-editor/src/editor/src/views/panels/EditorSettingsPanel/`
|
||||
- Add git settings section
|
||||
|
||||
5. `packages/noodl-editor/src/main/main.js`
|
||||
- Handle close event for warning
|
||||
|
||||
## Settings Schema
|
||||
|
||||
```typescript
|
||||
interface GitSettings {
|
||||
// Auto-initialization
|
||||
'git.autoInitialize': boolean; // default: true
|
||||
|
||||
// Commit reminders
|
||||
'git.commitReminders.enabled': boolean; // default: true
|
||||
'git.commitReminders.intervalMinutes': number; // default: 30
|
||||
|
||||
// Close warning
|
||||
'git.closeWarning.enabled': boolean; // default: true
|
||||
|
||||
// Quick commit
|
||||
'git.quickCommit.suggestMessage': boolean; // default: true
|
||||
}
|
||||
```
|
||||
|
||||
## Implementation Steps
|
||||
|
||||
### Phase 1: Auto-Initialization
|
||||
1. Add git.autoInitialize setting
|
||||
2. Modify project creation to init git
|
||||
3. Add initial commit
|
||||
4. Test with new projects
|
||||
|
||||
### Phase 2: Settings UI
|
||||
1. Add Git section to Editor Settings panel
|
||||
2. Implement all settings toggles
|
||||
3. Store settings in EditorSettings
|
||||
|
||||
### Phase 3: Commit Reminder Service
|
||||
1. Create CommitReminderService
|
||||
2. Add timer-based reminder check
|
||||
3. Create CommitReminderToast component
|
||||
4. Integrate with editor lifecycle
|
||||
|
||||
### Phase 4: Quick Commit
|
||||
1. Create QuickCommitPopup component
|
||||
2. Implement default message generation
|
||||
3. Wire up commit action
|
||||
4. Add "Open full panel" option
|
||||
|
||||
### Phase 5: Close Warning
|
||||
1. Create UnsavedChangesDialog
|
||||
2. Hook into project close event
|
||||
3. Implement "Commit & Close" flow
|
||||
4. Add "Don't show again" option
|
||||
|
||||
### Phase 6: Polish
|
||||
1. Snooze functionality
|
||||
2. Notification stacking
|
||||
3. Animation/transitions
|
||||
4. Edge case handling
|
||||
|
||||
## Testing Checklist
|
||||
|
||||
- [ ] New project is git-initialized by default
|
||||
- [ ] Initial commit is created
|
||||
- [ ] Auto-init can be disabled
|
||||
- [ ] Commit reminder appears after interval
|
||||
- [ ] Reminder shows correct uncommitted count
|
||||
- [ ] "Commit Now" opens quick commit popup
|
||||
- [ ] "Remind Me Later" snoozes correctly
|
||||
- [ ] Quick commit works with default message
|
||||
- [ ] Quick commit works with custom message
|
||||
- [ ] Close warning appears with uncommitted changes
|
||||
- [ ] "Commit & Close" works
|
||||
- [ ] "Close Anyway" works
|
||||
- [ ] "Don't show again" persists
|
||||
- [ ] Settings toggle all features correctly
|
||||
- [ ] Works when offline
|
||||
|
||||
## Edge Cases
|
||||
|
||||
1. **Project already has git**: Don't re-initialize, just work with existing
|
||||
2. **Template with git**: Use template's git if present, else init fresh
|
||||
3. **Init fails**: Log warning, don't block project creation
|
||||
4. **Commit fails**: Show error, offer to open Version Control panel
|
||||
5. **Large commit**: Show progress, don't block UI
|
||||
6. **No changes on reminder check**: Don't show reminder
|
||||
|
||||
## Dependencies
|
||||
|
||||
- GIT-002 (Git Status Dashboard) - for status detection infrastructure
|
||||
|
||||
## Blocked By
|
||||
|
||||
- GIT-002 (shares status checking code)
|
||||
|
||||
## Blocks
|
||||
|
||||
- None
|
||||
|
||||
## Estimated Effort
|
||||
|
||||
- Auto-initialization: 2-3 hours
|
||||
- Settings UI: 2-3 hours
|
||||
- Commit reminder service: 3-4 hours
|
||||
- Quick commit popup: 2-3 hours
|
||||
- Close warning: 2-3 hours
|
||||
- Polish: 2-3 hours
|
||||
- **Total: 13-19 hours**
|
||||
|
||||
## Success Criteria
|
||||
|
||||
1. New projects have git by default
|
||||
2. Users are gently reminded to commit
|
||||
3. Committing is easy and fast
|
||||
4. Users are warned before losing work
|
||||
5. All features can be disabled
|
||||
6. Non-intrusive to workflow
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
- Commit streak/gamification
|
||||
- Auto-commit on significant changes
|
||||
- Commit templates
|
||||
- Branch suggestions
|
||||
- Integration with cloud backup
|
||||
@@ -0,0 +1,388 @@
|
||||
# GIT-005: Enhanced Push/Pull UI
|
||||
|
||||
## Overview
|
||||
|
||||
Improve the push/pull experience with better visibility, branch management, conflict previews, and dashboard-level sync controls. Make syncing with remotes more intuitive and less error-prone.
|
||||
|
||||
## Context
|
||||
|
||||
The current Version Control panel has push/pull functionality via `GitStatusButton`, but:
|
||||
- Only visible when the panel is open
|
||||
- Branch switching is buried in menus
|
||||
- No preview of what will be pulled
|
||||
- Conflict resolution is complex
|
||||
|
||||
This task brings sync operations to the forefront and adds safeguards.
|
||||
|
||||
### Existing Infrastructure
|
||||
|
||||
From `GitStatusButton.tsx`:
|
||||
```typescript
|
||||
// Status kinds: 'default', 'fetch', 'error-fetch', 'pull', 'push', 'push-repository', 'set-authorization'
|
||||
|
||||
case 'push': {
|
||||
label = localCommitCount === 1 ? `Push 1 local commit` : `Push ${localCommitCount} local commits`;
|
||||
}
|
||||
|
||||
case 'pull': {
|
||||
label = remoteCommitCount === 1 ? `Pull 1 remote commit` : `Pull ${remoteCommitCount} remote commits`;
|
||||
}
|
||||
```
|
||||
|
||||
From `fetch.context.ts`:
|
||||
```typescript
|
||||
localCommitCount // Commits ahead of remote
|
||||
remoteCommitCount // Commits behind remote
|
||||
currentBranch // Current branch info
|
||||
branches // All branches
|
||||
```
|
||||
|
||||
## Requirements
|
||||
|
||||
### Functional Requirements
|
||||
|
||||
1. **Dashboard Sync Button**
|
||||
- Visible sync button in project row (from GIT-002)
|
||||
- One-click fetch & show status
|
||||
- Quick push/pull from dashboard
|
||||
|
||||
2. **Branch Selector**
|
||||
- Dropdown showing current branch
|
||||
- Quick switch between branches
|
||||
- Create new branch option
|
||||
- Branch search for projects with many branches
|
||||
- Remote branch indicators
|
||||
|
||||
3. **Pull Preview**
|
||||
- Show what commits will be pulled
|
||||
- List affected files
|
||||
- Warning for potential conflicts
|
||||
- "Preview" mode before actual pull
|
||||
|
||||
4. **Conflict Prevention**
|
||||
- Check for conflicts before pull
|
||||
- Suggest stashing changes first
|
||||
- Clear conflict resolution workflow
|
||||
- "Abort" option during conflicts
|
||||
|
||||
5. **Push Confirmation**
|
||||
- Show commits being pushed
|
||||
- Branch protection warning (if pushing to main)
|
||||
- Force push warning (if needed)
|
||||
|
||||
6. **Sync Status Header**
|
||||
- Always-visible status in editor header
|
||||
- Current branch display
|
||||
- Quick sync actions
|
||||
- Connection indicator
|
||||
|
||||
### Non-Functional Requirements
|
||||
|
||||
- Sync operations don't block UI
|
||||
- Progress visible for long operations
|
||||
- Works offline (queues operations)
|
||||
- Clear error messages
|
||||
|
||||
## Technical Approach
|
||||
|
||||
### 1. Sync Status Header Component
|
||||
|
||||
```typescript
|
||||
// packages/noodl-core-ui/src/components/git/SyncStatusHeader/SyncStatusHeader.tsx
|
||||
|
||||
interface SyncStatusHeaderProps {
|
||||
currentBranch: string;
|
||||
aheadCount: number;
|
||||
behindCount: number;
|
||||
hasUncommitted: boolean;
|
||||
isOnline: boolean;
|
||||
lastFetchTime: number;
|
||||
onPush: () => void;
|
||||
onPull: () => void;
|
||||
onFetch: () => void;
|
||||
onBranchChange: (branch: string) => void;
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Branch Selector Component
|
||||
|
||||
```typescript
|
||||
// packages/noodl-core-ui/src/components/git/BranchSelector/BranchSelector.tsx
|
||||
|
||||
interface BranchSelectorProps {
|
||||
currentBranch: Branch;
|
||||
branches: Branch[];
|
||||
onSelect: (branch: Branch) => void;
|
||||
onCreate: (name: string) => void;
|
||||
}
|
||||
|
||||
interface Branch {
|
||||
name: string;
|
||||
nameWithoutRemote: string;
|
||||
isLocal: boolean;
|
||||
isRemote: boolean;
|
||||
isCurrent: boolean;
|
||||
lastCommit?: {
|
||||
sha: string;
|
||||
message: string;
|
||||
date: string;
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Pull Preview Modal
|
||||
|
||||
```typescript
|
||||
// packages/noodl-core-ui/src/components/git/PullPreviewModal/PullPreviewModal.tsx
|
||||
|
||||
interface PullPreviewModalProps {
|
||||
commits: Commit[];
|
||||
affectedFiles: FileChange[];
|
||||
hasConflicts: boolean;
|
||||
conflictFiles?: string[];
|
||||
onPull: () => Promise<void>;
|
||||
onCancel: () => void;
|
||||
}
|
||||
|
||||
interface Commit {
|
||||
sha: string;
|
||||
message: string;
|
||||
author: string;
|
||||
date: string;
|
||||
}
|
||||
|
||||
interface FileChange {
|
||||
path: string;
|
||||
status: 'added' | 'modified' | 'deleted';
|
||||
hasConflict: boolean;
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Conflict Resolution Flow
|
||||
|
||||
```typescript
|
||||
// packages/noodl-editor/src/editor/src/services/ConflictResolutionService.ts
|
||||
|
||||
class ConflictResolutionService {
|
||||
// Check for potential conflicts before pull
|
||||
async previewConflicts(): Promise<ConflictPreview>;
|
||||
|
||||
// Handle stashing
|
||||
async stashAndPull(): Promise<void>;
|
||||
|
||||
// Resolution strategies
|
||||
async resolveWithOurs(file: string): Promise<void>;
|
||||
async resolveWithTheirs(file: string): Promise<void>;
|
||||
async openMergeTool(file: string): Promise<void>;
|
||||
|
||||
// Abort
|
||||
async abortMerge(): Promise<void>;
|
||||
}
|
||||
```
|
||||
|
||||
## UI Mockups
|
||||
|
||||
### Sync Status Header (Editor)
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||
│ [main ▾] ↑3 ↓2 ●5 uncommitted 🟢 Connected [Fetch] [Pull] [Push] │
|
||||
└─────────────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Branch Selector Dropdown
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────┐
|
||||
│ 🔍 Search branches... │
|
||||
├─────────────────────────────────────┤
|
||||
│ LOCAL │
|
||||
│ ✓ main │
|
||||
│ feature/new-login │
|
||||
│ bugfix/header-styling │
|
||||
├─────────────────────────────────────┤
|
||||
│ REMOTE │
|
||||
│ origin/develop │
|
||||
│ origin/release-1.0 │
|
||||
├─────────────────────────────────────┤
|
||||
│ + Create new branch... │
|
||||
└─────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Pull Preview Modal
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ Pull Preview [×] │
|
||||
├─────────────────────────────────────────────────────────────────────┤
|
||||
│ Pulling 3 commits from origin/main │
|
||||
│ │
|
||||
│ COMMITS │
|
||||
│ ┌─────────────────────────────────────────────────────────────────┐ │
|
||||
│ │ a1b2c3d Fix login validation John Doe 2 hours ago │ │
|
||||
│ │ d4e5f6g Add password reset flow Jane Smith 5 hours ago │ │
|
||||
│ │ h7i8j9k Update dependencies John Doe 1 day ago │ │
|
||||
│ └─────────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ FILES CHANGED (12) │
|
||||
│ ┌─────────────────────────────────────────────────────────────────┐ │
|
||||
│ │ M components/LoginPage.ndjson │ │
|
||||
│ │ M components/Header.ndjson │ │
|
||||
│ │ A components/PasswordReset.ndjson │ │
|
||||
│ │ D components/OldLogin.ndjson │ │
|
||||
│ │ ⚠️ M project.json (potential conflict) │ │
|
||||
│ └─────────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ⚠️ You have uncommitted changes. They will be stashed before pull. │
|
||||
│ │
|
||||
│ [Cancel] [Pull Now] │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Conflict Warning
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ ⚠️ Potential Conflicts Detected [×] │
|
||||
├─────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ The following files have been modified both locally and remotely: │
|
||||
│ │
|
||||
│ • project.json │
|
||||
│ • components/LoginPage.ndjson │
|
||||
│ │
|
||||
│ Noodl will attempt to merge these changes automatically, but you │
|
||||
│ may need to resolve conflicts manually. │
|
||||
│ │
|
||||
│ Recommended: Commit your local changes first for a cleaner merge. │
|
||||
│ │
|
||||
│ [Cancel] [Commit First] [Pull Anyway] │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Files to Create
|
||||
|
||||
1. `packages/noodl-core-ui/src/components/git/SyncStatusHeader/SyncStatusHeader.tsx`
|
||||
2. `packages/noodl-core-ui/src/components/git/SyncStatusHeader/SyncStatusHeader.module.scss`
|
||||
3. `packages/noodl-core-ui/src/components/git/BranchSelector/BranchSelector.tsx`
|
||||
4. `packages/noodl-core-ui/src/components/git/BranchSelector/BranchSelector.module.scss`
|
||||
5. `packages/noodl-core-ui/src/components/git/PullPreviewModal/PullPreviewModal.tsx`
|
||||
6. `packages/noodl-core-ui/src/components/git/PushConfirmModal/PushConfirmModal.tsx`
|
||||
7. `packages/noodl-core-ui/src/components/git/ConflictWarningModal/ConflictWarningModal.tsx`
|
||||
8. `packages/noodl-editor/src/editor/src/services/ConflictResolutionService.ts`
|
||||
|
||||
## Files to Modify
|
||||
|
||||
1. `packages/noodl-editor/src/editor/src/pages/EditorPage/EditorPage.tsx`
|
||||
- Add SyncStatusHeader to editor layout
|
||||
|
||||
2. `packages/noodl-editor/src/editor/src/views/panels/VersionControlPanel/VersionControlPanel.tsx`
|
||||
- Integrate new BranchSelector
|
||||
- Add pull preview before pulling
|
||||
|
||||
3. `packages/noodl-editor/src/editor/src/views/panels/VersionControlPanel/components/GitStatusButton.tsx`
|
||||
- Update to use new pull/push flows
|
||||
|
||||
4. `packages/noodl-editor/src/editor/src/views/panels/VersionControlPanel/context/fetch.context.ts`
|
||||
- Add preview fetch logic
|
||||
- Add conflict detection
|
||||
|
||||
5. `packages/noodl-core-ui/src/preview/launcher/Launcher/components/ProjectList/ProjectListRow.tsx`
|
||||
- Add quick sync button (if not in GIT-002)
|
||||
|
||||
## Implementation Steps
|
||||
|
||||
### Phase 1: Branch Selector
|
||||
1. Create BranchSelector component
|
||||
2. Implement search/filter
|
||||
3. Add create branch flow
|
||||
4. Integrate into Version Control panel
|
||||
|
||||
### Phase 2: Sync Status Header
|
||||
1. Create SyncStatusHeader component
|
||||
2. Add to editor layout
|
||||
3. Wire up actions
|
||||
4. Add connection indicator
|
||||
|
||||
### Phase 3: Pull Preview
|
||||
1. Create PullPreviewModal
|
||||
2. Implement commit/file listing
|
||||
3. Add conflict detection
|
||||
4. Wire up pull action
|
||||
|
||||
### Phase 4: Conflict Handling
|
||||
1. Create ConflictWarningModal
|
||||
2. Create ConflictResolutionService
|
||||
3. Implement stash-before-pull
|
||||
4. Add abort functionality
|
||||
|
||||
### Phase 5: Push Enhancements
|
||||
1. Create PushConfirmModal
|
||||
2. Add branch protection warning
|
||||
3. Show commit list
|
||||
4. Handle force push
|
||||
|
||||
### Phase 6: Dashboard Integration
|
||||
1. Add sync button to project rows
|
||||
2. Quick push/pull from dashboard
|
||||
3. Update status after sync
|
||||
|
||||
## Testing Checklist
|
||||
|
||||
- [ ] Branch selector shows all branches
|
||||
- [ ] Branch search filters correctly
|
||||
- [ ] Switching branches works
|
||||
- [ ] Creating new branch works
|
||||
- [ ] Sync status header shows correct counts
|
||||
- [ ] Fetch updates status
|
||||
- [ ] Pull preview shows correct commits
|
||||
- [ ] Pull preview shows affected files
|
||||
- [ ] Conflict warning appears when appropriate
|
||||
- [ ] Stash-before-pull works
|
||||
- [ ] Pull completes successfully
|
||||
- [ ] Push confirmation shows commits
|
||||
- [ ] Push completes successfully
|
||||
- [ ] Dashboard sync button works
|
||||
- [ ] Offline state handled gracefully
|
||||
|
||||
## Dependencies
|
||||
|
||||
- GIT-002 (Git Status Dashboard) - for dashboard integration
|
||||
- GIT-001 (GitHub OAuth) - for authenticated operations
|
||||
|
||||
## Blocked By
|
||||
|
||||
- GIT-002
|
||||
|
||||
## Blocks
|
||||
|
||||
- None
|
||||
|
||||
## Estimated Effort
|
||||
|
||||
- Branch selector: 3-4 hours
|
||||
- Sync status header: 2-3 hours
|
||||
- Pull preview: 4-5 hours
|
||||
- Conflict handling: 4-5 hours
|
||||
- Push enhancements: 2-3 hours
|
||||
- Dashboard integration: 2-3 hours
|
||||
- **Total: 17-23 hours**
|
||||
|
||||
## Success Criteria
|
||||
|
||||
1. Branch switching is easy and visible
|
||||
2. Users can preview what will be pulled
|
||||
3. Conflict potential is detected before pull
|
||||
4. Stashing is automatic when needed
|
||||
5. Push shows what's being pushed
|
||||
6. Quick sync available from dashboard
|
||||
7. Status always visible in editor
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
- Pull request creation
|
||||
- Branch comparison
|
||||
- Revert/cherry-pick commits
|
||||
- Squash commits before push
|
||||
- Auto-sync on save (optional)
|
||||
- Branch naming conventions/templates
|
||||
@@ -0,0 +1,248 @@
|
||||
# GIT Series: Git & GitHub Integration
|
||||
|
||||
## Overview
|
||||
|
||||
The GIT series transforms Noodl's version control experience from a manual, expert-only feature into a seamless, integrated part of the development workflow. By adding GitHub OAuth, surfacing git status in the dashboard, and encouraging good version control habits, we make collaboration accessible to all Noodl users.
|
||||
|
||||
## Target Environment
|
||||
|
||||
- **Editor**: React 19 version only
|
||||
- **Runtime**: Not affected (git is editor-only)
|
||||
- **Backwards Compatibility**: Existing git projects continue to work
|
||||
|
||||
## Task Dependency Graph
|
||||
|
||||
```
|
||||
GIT-001 (GitHub OAuth)
|
||||
│
|
||||
├──────────────────────────┐
|
||||
│ │
|
||||
▼ ▼
|
||||
GIT-002 (Dashboard Status) GIT-003 (Repository Cloning)
|
||||
│
|
||||
├──────────────────────────┐
|
||||
│ │
|
||||
▼ ▼
|
||||
GIT-004 (Auto-Init) GIT-005 (Enhanced Push/Pull)
|
||||
```
|
||||
|
||||
## Task Summary
|
||||
|
||||
| Task ID | Name | Est. Hours | Priority |
|
||||
|---------|------|------------|----------|
|
||||
| GIT-001 | GitHub OAuth Integration | 14-20 | Critical |
|
||||
| GIT-002 | Git Status Dashboard Visibility | 11-16 | High |
|
||||
| GIT-003 | Repository Cloning | 13-18 | High |
|
||||
| GIT-004 | Auto-Initialization & Commit Encouragement | 13-19 | Medium |
|
||||
| GIT-005 | Enhanced Push/Pull UI | 17-23 | Medium |
|
||||
|
||||
**Total Estimated: 68-96 hours**
|
||||
|
||||
## Implementation Order
|
||||
|
||||
### Week 1-2: Authentication & Status
|
||||
1. **GIT-001** - GitHub OAuth (foundation for GitHub API access)
|
||||
2. **GIT-002** - Dashboard status (leverages DASH-002 project list)
|
||||
|
||||
### Week 3: Cloning & Basic Flow
|
||||
3. **GIT-003** - Repository cloning (depends on OAuth for private repos)
|
||||
|
||||
### Week 4: Polish & Encouragement
|
||||
4. **GIT-004** - Auto-initialization (depends on status detection)
|
||||
5. **GIT-005** - Enhanced push/pull (depends on status infrastructure)
|
||||
|
||||
## Existing Infrastructure
|
||||
|
||||
The codebase already has solid git foundations to build on:
|
||||
|
||||
### noodl-git Package
|
||||
```
|
||||
packages/noodl-git/src/
|
||||
├── git.ts # Main Git class
|
||||
├── core/
|
||||
│ ├── clone.ts # Clone operations
|
||||
│ ├── push.ts # Push operations
|
||||
│ ├── pull.ts # Pull operations
|
||||
│ └── ...
|
||||
├── actions/ # Higher-level actions
|
||||
└── constants.ts
|
||||
```
|
||||
|
||||
Key existing methods:
|
||||
- `git.initNewRepo()` - Initialize new repository
|
||||
- `git.clone()` - Clone with progress
|
||||
- `git.push()` - Push with progress
|
||||
- `git.pull()` - Pull with rebase
|
||||
- `git.status()` - Working directory status
|
||||
- `git.getBranches()` - List branches
|
||||
- `git.getCommitsCurrentBranch()` - Commit history
|
||||
|
||||
### Version Control Panel
|
||||
```
|
||||
packages/noodl-editor/src/editor/src/views/panels/VersionControlPanel/
|
||||
├── VersionControlPanel.tsx
|
||||
├── components/
|
||||
│ ├── GitStatusButton.tsx # Push/pull status
|
||||
│ ├── GitProviderPopout/ # Credentials management
|
||||
│ ├── LocalChanges.tsx # Uncommitted files
|
||||
│ ├── History.tsx # Commit history
|
||||
│ └── BranchMerge.tsx # Branch operations
|
||||
└── context/
|
||||
└── fetch.context.ts # Git state management
|
||||
```
|
||||
|
||||
### Credentials Storage
|
||||
- `GitStore` - Stores credentials per-project encrypted
|
||||
- `trampoline-askpass-handler` - Handles git credential prompts
|
||||
- Currently uses PAT (Personal Access Token) for GitHub
|
||||
|
||||
## Key Technical Decisions
|
||||
|
||||
### OAuth vs PAT
|
||||
|
||||
**Current**: Personal Access Token per project
|
||||
- User creates PAT on GitHub
|
||||
- Copies to Noodl per project
|
||||
- Stored encrypted in GitStore
|
||||
|
||||
**New (GIT-001)**: OAuth + PAT fallback
|
||||
- One-click GitHub OAuth
|
||||
- Token stored globally
|
||||
- PAT remains for non-GitHub remotes
|
||||
|
||||
### Status Checking Strategy
|
||||
|
||||
**Approach**: Batch + Cache
|
||||
- Check multiple projects in parallel
|
||||
- Cache results with TTL
|
||||
- Background refresh
|
||||
|
||||
**Why**: Git status requires opening each repo, which is slow. Caching makes dashboard responsive while keeping data fresh.
|
||||
|
||||
### Auto-Initialization
|
||||
|
||||
**Approach**: Opt-out
|
||||
- Git initialized by default
|
||||
- Initial commit created automatically
|
||||
- Can disable in settings
|
||||
|
||||
**Why**: Most users benefit from version control. Making it default reduces "I lost my work" issues.
|
||||
|
||||
## Services to Create
|
||||
|
||||
| Service | Location | Purpose |
|
||||
|---------|----------|---------|
|
||||
| GitHubOAuthService | noodl-editor/services | OAuth flow, token management |
|
||||
| GitHubApiClient | noodl-editor/services | GitHub REST API calls |
|
||||
| ProjectGitStatusService | noodl-editor/services | Batch status checking, caching |
|
||||
| CloneService | noodl-editor/services | Clone wrapper with progress |
|
||||
| CommitReminderService | noodl-editor/services | Periodic commit reminders |
|
||||
| ConflictResolutionService | noodl-editor/services | Conflict detection, resolution |
|
||||
|
||||
## Components to Create
|
||||
|
||||
| Component | Package | Purpose |
|
||||
|-----------|---------|---------|
|
||||
| GitHubConnectButton | noodl-core-ui | OAuth trigger button |
|
||||
| GitHubAccountCard | noodl-core-ui | Connected account display |
|
||||
| GitStatusBadge | noodl-core-ui | Status indicator in list |
|
||||
| CloneModal | noodl-core-ui | Clone flow modal |
|
||||
| RepoBrowser | noodl-core-ui | Repository list/search |
|
||||
| QuickCommitPopup | noodl-core-ui | Fast commit dialog |
|
||||
| SyncStatusHeader | noodl-core-ui | Editor header sync status |
|
||||
| BranchSelector | noodl-core-ui | Branch dropdown |
|
||||
| PullPreviewModal | noodl-core-ui | Preview before pull |
|
||||
|
||||
## Dependencies
|
||||
|
||||
### On DASH Series
|
||||
- GIT-002 → DASH-002 (project list for status display)
|
||||
- GIT-001 → DASH-001 (launcher context for account display)
|
||||
|
||||
### External Packages
|
||||
May need:
|
||||
```json
|
||||
{
|
||||
"@octokit/rest": "^20.0.0" // GitHub API client (optional)
|
||||
}
|
||||
```
|
||||
|
||||
## Security Considerations
|
||||
|
||||
1. **OAuth Tokens**: Store with electron's safeStorage API
|
||||
2. **PKCE Flow**: Use PKCE for OAuth (no client secret in app)
|
||||
3. **Token Scope**: Request minimum necessary (repo, read:org, read:user)
|
||||
4. **Credential Cache**: Clear on logout/disconnect
|
||||
5. **PAT Fallback**: Encrypted per-project storage continues
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
### Unit Tests
|
||||
- OAuth token exchange
|
||||
- Status calculation logic
|
||||
- Conflict detection
|
||||
- Default commit message generation
|
||||
|
||||
### Integration Tests
|
||||
- Clone from public repo
|
||||
- Clone from private repo with auth
|
||||
- Push/pull with mock remote
|
||||
- Branch operations
|
||||
|
||||
### Manual Testing
|
||||
- Full OAuth flow
|
||||
- Dashboard status refresh
|
||||
- Clone flow end-to-end
|
||||
- Commit reminder timing
|
||||
- Conflict resolution
|
||||
|
||||
## Cline Usage Notes
|
||||
|
||||
### Before Starting Each Task
|
||||
|
||||
1. Read the task document completely
|
||||
2. Review existing git infrastructure:
|
||||
- `packages/noodl-git/src/git.ts`
|
||||
- `packages/noodl-editor/src/editor/src/views/panels/VersionControlPanel/`
|
||||
3. Check GitStore and credential handling
|
||||
|
||||
### Key Gotchas
|
||||
|
||||
1. **Git operations are async**: Always use try/catch, git can fail
|
||||
2. **Repository paths**: Use `_retainedProjectDirectory` from ProjectModel
|
||||
3. **Merge strategy**: Noodl has custom merge for project.json (`mergeProject`)
|
||||
4. **Auth caching**: Credentials cached by trampoline, may need clearing
|
||||
5. **Electron context**: Some git ops need main process (deep links)
|
||||
|
||||
### Testing Git Operations
|
||||
|
||||
```bash
|
||||
# In tests directory, run git tests
|
||||
npm run test:editor -- --grep="Git"
|
||||
```
|
||||
|
||||
## Success Criteria (Series Complete)
|
||||
|
||||
1. ✅ Users can authenticate with GitHub via OAuth
|
||||
2. ✅ Git status visible in project dashboard
|
||||
3. ✅ Users can clone repositories from UI
|
||||
4. ✅ New projects have git by default
|
||||
5. ✅ Users are reminded to commit regularly
|
||||
6. ✅ Pull/push is intuitive with previews
|
||||
7. ✅ Branch management is accessible
|
||||
|
||||
## Future Work (Post-GIT)
|
||||
|
||||
The GIT series enables:
|
||||
- **COMP series**: Shared component repositories
|
||||
- **DEPLOY series**: Auto-push to frontend repo on deploy
|
||||
- **Community features**: Public component sharing
|
||||
|
||||
## Files in This Series
|
||||
|
||||
- `GIT-001-github-oauth.md`
|
||||
- `GIT-002-dashboard-git-status.md`
|
||||
- `GIT-003-repository-cloning.md`
|
||||
- `GIT-004-auto-init-commit-encouragement.md`
|
||||
- `GIT-005-enhanced-push-pull.md`
|
||||
- `GIT-OVERVIEW.md` (this file)
|
||||
@@ -0,0 +1,408 @@
|
||||
# COMP-001: Prefab System Refactoring
|
||||
|
||||
## Overview
|
||||
|
||||
Refactor the existing prefab system to support multiple sources (not just the docs endpoint). This creates the foundation for built-in prefabs, personal repositories, organization repositories, and community contributions.
|
||||
|
||||
## Context
|
||||
|
||||
The current prefab system is tightly coupled to the docs endpoint:
|
||||
- `ModuleLibraryModel` fetches from `${docsEndpoint}/library/prefabs/index.json`
|
||||
- Prefabs are zip files hosted on the docs site
|
||||
- No support for alternative sources
|
||||
|
||||
This task creates an abstraction layer that allows prefabs to come from multiple sources while maintaining the existing user experience.
|
||||
|
||||
### Current Architecture
|
||||
|
||||
```
|
||||
User clicks "Clone" in NodePicker
|
||||
↓
|
||||
ModuleLibraryModel.installPrefab(url)
|
||||
↓
|
||||
getModuleTemplateRoot(url) ← Downloads & extracts zip
|
||||
↓
|
||||
ProjectImporter.listComponentsAndDependencies()
|
||||
↓
|
||||
ProjectImporter.checkForCollisions()
|
||||
↓
|
||||
_showImportPopup() if collisions
|
||||
↓
|
||||
_doImport()
|
||||
```
|
||||
|
||||
### Key Files
|
||||
|
||||
- `packages/noodl-editor/src/editor/src/models/modulelibrarymodel.ts`
|
||||
- `packages/noodl-editor/src/editor/src/utils/projectimporter.js`
|
||||
- `packages/noodl-editor/src/editor/src/views/NodePicker/`
|
||||
|
||||
## Requirements
|
||||
|
||||
### Functional Requirements
|
||||
|
||||
1. **Source Abstraction**
|
||||
- Define `PrefabSource` interface for different sources
|
||||
- Support multiple sources simultaneously
|
||||
- Each source provides: list, search, fetch, metadata
|
||||
|
||||
2. **Source Types**
|
||||
- `DocsSource` - Existing docs endpoint (default)
|
||||
- `BuiltInSource` - Bundled with editor (COMP-002)
|
||||
- `GitHubSource` - GitHub repositories (COMP-003+)
|
||||
- `LocalSource` - Local filesystem (for development)
|
||||
|
||||
3. **Unified Prefab Model**
|
||||
- Consistent metadata across all sources
|
||||
- Version information
|
||||
- Source tracking (where did this come from?)
|
||||
- Dependencies and requirements
|
||||
|
||||
4. **Enhanced Metadata**
|
||||
- Author information
|
||||
- Version number
|
||||
- Noodl version compatibility
|
||||
- Screenshots/previews
|
||||
- Changelog
|
||||
- License
|
||||
|
||||
5. **Backwards Compatibility**
|
||||
- Existing prefabs continue to work
|
||||
- No changes to user workflow
|
||||
- Migration path for enhanced metadata
|
||||
|
||||
### Non-Functional Requirements
|
||||
|
||||
- Source fetching is async and non-blocking
|
||||
- Caching for performance
|
||||
- Graceful degradation if source unavailable
|
||||
- Extensible for future sources
|
||||
|
||||
## Technical Approach
|
||||
|
||||
### 1. Prefab Source Interface
|
||||
|
||||
```typescript
|
||||
// packages/noodl-editor/src/editor/src/models/prefab/PrefabSource.ts
|
||||
|
||||
interface PrefabMetadata {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
version: string;
|
||||
author?: {
|
||||
name: string;
|
||||
email?: string;
|
||||
url?: string;
|
||||
};
|
||||
noodlVersion?: string; // Minimum compatible version
|
||||
tags: string[];
|
||||
icon?: string;
|
||||
screenshots?: string[];
|
||||
docs?: string;
|
||||
license?: string;
|
||||
repository?: string;
|
||||
dependencies?: string[]; // Other prefabs this depends on
|
||||
createdAt?: string;
|
||||
updatedAt?: string;
|
||||
}
|
||||
|
||||
interface PrefabSourceConfig {
|
||||
id: string;
|
||||
name: string;
|
||||
priority: number; // Higher = shown first
|
||||
enabled: boolean;
|
||||
}
|
||||
|
||||
interface PrefabSource {
|
||||
readonly config: PrefabSourceConfig;
|
||||
|
||||
// Lifecycle
|
||||
initialize(): Promise<void>;
|
||||
dispose(): void;
|
||||
|
||||
// Listing
|
||||
listPrefabs(): Promise<PrefabMetadata[]>;
|
||||
searchPrefabs(query: string): Promise<PrefabMetadata[]>;
|
||||
|
||||
// Fetching
|
||||
getPrefabDetails(id: string): Promise<PrefabMetadata>;
|
||||
downloadPrefab(id: string): Promise<string>; // Returns local path to extracted content
|
||||
|
||||
// State
|
||||
isAvailable(): boolean;
|
||||
getLastError(): Error | null;
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Source Implementations
|
||||
|
||||
```typescript
|
||||
// DocsSource - existing functionality wrapped
|
||||
class DocsPrefabSource implements PrefabSource {
|
||||
config = {
|
||||
id: 'docs',
|
||||
name: 'Community Prefabs',
|
||||
priority: 50,
|
||||
enabled: true
|
||||
};
|
||||
|
||||
async listPrefabs(): Promise<PrefabMetadata[]> {
|
||||
// Existing fetch logic from ModuleLibraryModel
|
||||
const endpoint = getDocsEndpoint();
|
||||
const response = await fetch(`${endpoint}/library/prefabs/index.json`);
|
||||
const items = await response.json();
|
||||
|
||||
// Transform to new metadata format
|
||||
return items.map(item => this.transformLegacyItem(item));
|
||||
}
|
||||
|
||||
private transformLegacyItem(item: IModule): PrefabMetadata {
|
||||
return {
|
||||
id: `docs:${item.label}`,
|
||||
name: item.label,
|
||||
description: item.desc,
|
||||
version: '1.0.0', // Legacy items don't have versions
|
||||
tags: item.tags || [],
|
||||
icon: item.icon,
|
||||
docs: item.docs
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// BuiltInSource - for COMP-002
|
||||
class BuiltInPrefabSource implements PrefabSource {
|
||||
config = {
|
||||
id: 'builtin',
|
||||
name: 'Built-in Prefabs',
|
||||
priority: 100,
|
||||
enabled: true
|
||||
};
|
||||
|
||||
// Implementation in COMP-002
|
||||
}
|
||||
|
||||
// GitHubSource - for COMP-003+
|
||||
class GitHubPrefabSource implements PrefabSource {
|
||||
config = {
|
||||
id: 'github',
|
||||
name: 'GitHub',
|
||||
priority: 75,
|
||||
enabled: true
|
||||
};
|
||||
|
||||
constructor(private repoUrl: string) {}
|
||||
|
||||
// Implementation in COMP-003
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Prefab Registry
|
||||
|
||||
```typescript
|
||||
// packages/noodl-editor/src/editor/src/models/prefab/PrefabRegistry.ts
|
||||
|
||||
class PrefabRegistry {
|
||||
private static instance: PrefabRegistry;
|
||||
private sources: Map<string, PrefabSource> = new Map();
|
||||
private cache: Map<string, PrefabMetadata[]> = new Map();
|
||||
|
||||
// Source management
|
||||
registerSource(source: PrefabSource): void;
|
||||
unregisterSource(sourceId: string): void;
|
||||
getSource(sourceId: string): PrefabSource | undefined;
|
||||
getSources(): PrefabSource[];
|
||||
|
||||
// Aggregated operations
|
||||
async getAllPrefabs(): Promise<PrefabMetadata[]>;
|
||||
async searchAllPrefabs(query: string): Promise<PrefabMetadata[]>;
|
||||
|
||||
// Installation
|
||||
async installPrefab(prefabId: string, options?: InstallOptions): Promise<void>;
|
||||
|
||||
// Cache
|
||||
invalidateCache(sourceId?: string): void;
|
||||
|
||||
// Events
|
||||
onSourcesChanged(callback: () => void): () => void;
|
||||
onPrefabsUpdated(callback: () => void): () => void;
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Updated ModuleLibraryModel
|
||||
|
||||
```typescript
|
||||
// Refactored to use PrefabRegistry
|
||||
|
||||
export class ModuleLibraryModel extends Model {
|
||||
private registry: PrefabRegistry;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.registry = PrefabRegistry.instance;
|
||||
|
||||
// Register default sources
|
||||
this.registry.registerSource(new DocsPrefabSource());
|
||||
this.registry.registerSource(new BuiltInPrefabSource());
|
||||
|
||||
// Listen for updates
|
||||
this.registry.onPrefabsUpdated(() => {
|
||||
this.notifyListeners('libraryUpdated');
|
||||
});
|
||||
}
|
||||
|
||||
// Backwards compatible API
|
||||
get prefabs(): IModule[] {
|
||||
return this.registry.getAllPrefabsSync()
|
||||
.map(p => this.transformToLegacy(p));
|
||||
}
|
||||
|
||||
async installPrefab(url: string, ...): Promise<void> {
|
||||
// Detect source from URL or use legacy path
|
||||
const prefabId = this.detectPrefabId(url);
|
||||
await this.registry.installPrefab(prefabId);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Files to Create
|
||||
|
||||
1. `packages/noodl-editor/src/editor/src/models/prefab/PrefabSource.ts` - Interface definitions
|
||||
2. `packages/noodl-editor/src/editor/src/models/prefab/PrefabRegistry.ts` - Central registry
|
||||
3. `packages/noodl-editor/src/editor/src/models/prefab/sources/DocsPrefabSource.ts` - Docs implementation
|
||||
4. `packages/noodl-editor/src/editor/src/models/prefab/sources/BuiltInPrefabSource.ts` - Stub for COMP-002
|
||||
5. `packages/noodl-editor/src/editor/src/models/prefab/sources/GitHubPrefabSource.ts` - Stub for COMP-003+
|
||||
6. `packages/noodl-editor/src/editor/src/models/prefab/sources/LocalPrefabSource.ts` - For development
|
||||
7. `packages/noodl-editor/src/editor/src/models/prefab/index.ts` - Barrel exports
|
||||
|
||||
## Files to Modify
|
||||
|
||||
1. `packages/noodl-editor/src/editor/src/models/modulelibrarymodel.ts`
|
||||
- Refactor to use PrefabRegistry
|
||||
- Maintain backwards compatible API
|
||||
- Delegate to sources
|
||||
|
||||
2. `packages/noodl-editor/src/editor/src/views/NodePicker/tabs/NodePickerSearchView/NodePickerSearchView.tsx`
|
||||
- Update to work with new metadata format
|
||||
- Add source indicators
|
||||
|
||||
3. `packages/noodl-editor/src/editor/src/views/NodePicker/components/ModuleCard/ModuleCard.tsx`
|
||||
- Add source badge
|
||||
- Add version display
|
||||
- Handle enhanced metadata
|
||||
|
||||
## Implementation Steps
|
||||
|
||||
### Phase 1: Interfaces & Registry
|
||||
1. Define PrefabSource interface
|
||||
2. Define PrefabMetadata interface
|
||||
3. Create PrefabRegistry class
|
||||
4. Add source registration
|
||||
|
||||
### Phase 2: Docs Source Migration
|
||||
1. Create DocsPrefabSource
|
||||
2. Migrate existing fetch logic
|
||||
3. Add metadata transformation
|
||||
4. Test backwards compatibility
|
||||
|
||||
### Phase 3: ModuleLibraryModel Refactor
|
||||
1. Integrate PrefabRegistry
|
||||
2. Maintain backwards compatible API
|
||||
3. Update install methods
|
||||
4. Add source detection
|
||||
|
||||
### Phase 4: UI Updates
|
||||
1. Add source indicators to cards
|
||||
2. Show version information
|
||||
3. Handle multiple sources in search
|
||||
|
||||
### Phase 5: Stub Sources
|
||||
1. Create BuiltInPrefabSource stub
|
||||
2. Create GitHubPrefabSource stub
|
||||
3. Create LocalPrefabSource for development
|
||||
|
||||
## Metadata Schema
|
||||
|
||||
```json
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"type": "object",
|
||||
"required": ["id", "name", "version"],
|
||||
"properties": {
|
||||
"id": { "type": "string" },
|
||||
"name": { "type": "string" },
|
||||
"description": { "type": "string" },
|
||||
"version": { "type": "string", "pattern": "^\\d+\\.\\d+\\.\\d+$" },
|
||||
"author": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": { "type": "string" },
|
||||
"email": { "type": "string" },
|
||||
"url": { "type": "string" }
|
||||
}
|
||||
},
|
||||
"noodlVersion": { "type": "string" },
|
||||
"tags": { "type": "array", "items": { "type": "string" } },
|
||||
"icon": { "type": "string" },
|
||||
"screenshots": { "type": "array", "items": { "type": "string" } },
|
||||
"docs": { "type": "string" },
|
||||
"license": { "type": "string" },
|
||||
"repository": { "type": "string" },
|
||||
"dependencies": { "type": "array", "items": { "type": "string" } }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Testing Checklist
|
||||
|
||||
- [ ] PrefabRegistry initializes correctly
|
||||
- [ ] DocsPrefabSource fetches from docs endpoint
|
||||
- [ ] Legacy prefabs continue to work
|
||||
- [ ] Metadata transformation preserves data
|
||||
- [ ] Multiple sources aggregate correctly
|
||||
- [ ] Search works across sources
|
||||
- [ ] Install works from any source
|
||||
- [ ] Source indicators display correctly
|
||||
- [ ] Cache invalidation works
|
||||
- [ ] Error handling for unavailable sources
|
||||
|
||||
## Dependencies
|
||||
|
||||
- None (foundation task)
|
||||
|
||||
## Blocked By
|
||||
|
||||
- None
|
||||
|
||||
## Blocks
|
||||
|
||||
- COMP-002 (Built-in Prefabs)
|
||||
- COMP-003 (Component Export)
|
||||
- COMP-004 (Organization Components)
|
||||
|
||||
## Estimated Effort
|
||||
|
||||
- Interfaces & types: 2-3 hours
|
||||
- PrefabRegistry: 3-4 hours
|
||||
- DocsPrefabSource: 2-3 hours
|
||||
- ModuleLibraryModel refactor: 3-4 hours
|
||||
- UI updates: 2-3 hours
|
||||
- Testing: 2-3 hours
|
||||
- **Total: 14-20 hours**
|
||||
|
||||
## Success Criteria
|
||||
|
||||
1. New source abstraction in place
|
||||
2. Existing prefabs continue to work identically
|
||||
3. Multiple sources can be registered
|
||||
4. UI shows source indicators
|
||||
5. Foundation ready for built-in and GitHub sources
|
||||
|
||||
## Future Considerations
|
||||
|
||||
- Source priority/ordering configuration
|
||||
- Source enable/disable in settings
|
||||
- Custom source plugins
|
||||
- Prefab ratings/popularity
|
||||
- Usage analytics per source
|
||||
@@ -0,0 +1,394 @@
|
||||
# COMP-002: Built-in Prefabs
|
||||
|
||||
## Overview
|
||||
|
||||
Bundle essential prefabs directly with the OpenNoodl editor, so they're available immediately without network access. This improves the onboarding experience and ensures core functionality is always available.
|
||||
|
||||
## Context
|
||||
|
||||
Currently, all prefabs are fetched from the docs endpoint at runtime:
|
||||
- Requires network connectivity
|
||||
- Adds latency on first load
|
||||
- No prefabs available offline
|
||||
- New users see empty prefab library initially
|
||||
|
||||
By bundling prefabs with the editor:
|
||||
- Instant availability
|
||||
- Works offline
|
||||
- Consistent experience for all users
|
||||
- Core prefabs versioned with editor releases
|
||||
|
||||
### Existing Export/Import
|
||||
|
||||
From `exportProjectComponents.ts` and `projectimporter.js`:
|
||||
- Components exported as zip files
|
||||
- Import handles collision detection
|
||||
- Styles, variants, resources included
|
||||
- Dependency tracking exists
|
||||
|
||||
## Requirements
|
||||
|
||||
### Functional Requirements
|
||||
|
||||
1. **Built-in Prefab Bundle**
|
||||
- Essential prefabs bundled in editor distribution
|
||||
- Loaded from local filesystem, not network
|
||||
- Versioned with editor releases
|
||||
|
||||
2. **Prefab Selection**
|
||||
- Form components (Input, Button, Checkbox, etc.)
|
||||
- Layout helpers (Card, Modal, Drawer)
|
||||
- Data utilities (REST caller, LocalStorage, etc.)
|
||||
- Authentication flows (basic patterns)
|
||||
- Navigation patterns
|
||||
|
||||
3. **UI Distinction**
|
||||
- "Built-in" badge on bundled prefabs
|
||||
- Shown first in prefab list
|
||||
- Separate section or filter option
|
||||
|
||||
4. **Update Mechanism**
|
||||
- Built-in prefabs update with editor
|
||||
- No manual update needed
|
||||
- Changelog visible for what's new
|
||||
|
||||
5. **Offline First**
|
||||
- Available immediately on fresh install
|
||||
- No network request needed
|
||||
- Graceful handling when docs unavailable
|
||||
|
||||
### Non-Functional Requirements
|
||||
|
||||
- Bundle size impact < 5MB
|
||||
- Load time < 500ms
|
||||
- No runtime network dependency
|
||||
- Works in air-gapped environments
|
||||
|
||||
## Technical Approach
|
||||
|
||||
### 1. Bundle Structure
|
||||
|
||||
```
|
||||
packages/noodl-editor/
|
||||
├── static/
|
||||
│ └── builtin-prefabs/
|
||||
│ ├── index.json # Manifest of built-in prefabs
|
||||
│ └── prefabs/
|
||||
│ ├── form-input/
|
||||
│ │ ├── prefab.json # Metadata
|
||||
│ │ └── components/ # Component files
|
||||
│ ├── form-button/
|
||||
│ ├── card-layout/
|
||||
│ ├── modal-dialog/
|
||||
│ ├── rest-client/
|
||||
│ └── ...
|
||||
```
|
||||
|
||||
### 2. Manifest Format
|
||||
|
||||
```json
|
||||
{
|
||||
"version": "1.0.0",
|
||||
"noodlVersion": "2.10.0",
|
||||
"prefabs": [
|
||||
{
|
||||
"id": "builtin:form-input",
|
||||
"name": "Form Input",
|
||||
"description": "Styled text input with label, validation, and error states",
|
||||
"version": "1.0.0",
|
||||
"category": "Forms",
|
||||
"tags": ["form", "input", "text", "validation"],
|
||||
"icon": "input-icon.svg",
|
||||
"path": "prefabs/form-input"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 3. BuiltInPrefabSource Implementation
|
||||
|
||||
```typescript
|
||||
// packages/noodl-editor/src/editor/src/models/prefab/sources/BuiltInPrefabSource.ts
|
||||
|
||||
import { platform } from '@noodl/platform';
|
||||
|
||||
class BuiltInPrefabSource implements PrefabSource {
|
||||
config = {
|
||||
id: 'builtin',
|
||||
name: 'Built-in',
|
||||
priority: 100, // Highest priority - show first
|
||||
enabled: true
|
||||
};
|
||||
|
||||
private manifest: BuiltInManifest | null = null;
|
||||
private basePath: string;
|
||||
|
||||
async initialize(): Promise<void> {
|
||||
// Get path to bundled prefabs
|
||||
this.basePath = platform.getBuiltInPrefabsPath();
|
||||
|
||||
// Load manifest
|
||||
const manifestPath = path.join(this.basePath, 'index.json');
|
||||
const content = await fs.readFile(manifestPath, 'utf-8');
|
||||
this.manifest = JSON.parse(content);
|
||||
}
|
||||
|
||||
async listPrefabs(): Promise<PrefabMetadata[]> {
|
||||
if (!this.manifest) await this.initialize();
|
||||
|
||||
return this.manifest.prefabs.map(p => ({
|
||||
id: p.id,
|
||||
name: p.name,
|
||||
description: p.description,
|
||||
version: p.version,
|
||||
tags: p.tags,
|
||||
icon: this.resolveIcon(p.icon),
|
||||
source: 'builtin',
|
||||
category: p.category
|
||||
}));
|
||||
}
|
||||
|
||||
async downloadPrefab(id: string): Promise<string> {
|
||||
// No download needed - return local path
|
||||
const prefab = this.manifest.prefabs.find(p => p.id === id);
|
||||
return path.join(this.basePath, prefab.path);
|
||||
}
|
||||
|
||||
private resolveIcon(iconPath: string): string {
|
||||
return `file://${path.join(this.basePath, 'icons', iconPath)}`;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Build-time Prefab Bundling
|
||||
|
||||
```typescript
|
||||
// scripts/bundle-prefabs.ts
|
||||
|
||||
/**
|
||||
* Run during build to prepare built-in prefabs
|
||||
* 1. Reads prefab source projects
|
||||
* 2. Exports components
|
||||
* 3. Generates manifest
|
||||
* 4. Copies to static directory
|
||||
*/
|
||||
|
||||
async function bundlePrefabs() {
|
||||
const prefabSources = await glob('prefab-sources/*');
|
||||
const manifest: BuiltInManifest = {
|
||||
version: packageJson.version,
|
||||
noodlVersion: packageJson.version,
|
||||
prefabs: []
|
||||
};
|
||||
|
||||
for (const source of prefabSources) {
|
||||
const metadata = await readPrefabMetadata(source);
|
||||
const outputPath = path.join(OUTPUT_DIR, metadata.id);
|
||||
|
||||
await exportPrefabComponents(source, outputPath);
|
||||
|
||||
manifest.prefabs.push({
|
||||
id: `builtin:${metadata.id}`,
|
||||
name: metadata.name,
|
||||
description: metadata.description,
|
||||
version: metadata.version,
|
||||
category: metadata.category,
|
||||
tags: metadata.tags,
|
||||
icon: metadata.icon,
|
||||
path: metadata.id
|
||||
});
|
||||
}
|
||||
|
||||
await writeManifest(manifest);
|
||||
}
|
||||
```
|
||||
|
||||
### 5. Prefab Categories
|
||||
|
||||
```typescript
|
||||
enum PrefabCategory {
|
||||
Forms = 'Forms',
|
||||
Layout = 'Layout',
|
||||
Navigation = 'Navigation',
|
||||
Data = 'Data',
|
||||
Authentication = 'Authentication',
|
||||
Feedback = 'Feedback',
|
||||
Media = 'Media'
|
||||
}
|
||||
|
||||
const BUILT_IN_PREFABS: BuiltInPrefabConfig[] = [
|
||||
// Forms
|
||||
{ id: 'form-input', category: PrefabCategory.Forms },
|
||||
{ id: 'form-textarea', category: PrefabCategory.Forms },
|
||||
{ id: 'form-checkbox', category: PrefabCategory.Forms },
|
||||
{ id: 'form-radio', category: PrefabCategory.Forms },
|
||||
{ id: 'form-select', category: PrefabCategory.Forms },
|
||||
{ id: 'form-button', category: PrefabCategory.Forms },
|
||||
|
||||
// Layout
|
||||
{ id: 'card', category: PrefabCategory.Layout },
|
||||
{ id: 'modal', category: PrefabCategory.Layout },
|
||||
{ id: 'drawer', category: PrefabCategory.Layout },
|
||||
{ id: 'accordion', category: PrefabCategory.Layout },
|
||||
{ id: 'tabs', category: PrefabCategory.Layout },
|
||||
|
||||
// Navigation
|
||||
{ id: 'navbar', category: PrefabCategory.Navigation },
|
||||
{ id: 'sidebar', category: PrefabCategory.Navigation },
|
||||
{ id: 'breadcrumb', category: PrefabCategory.Navigation },
|
||||
{ id: 'pagination', category: PrefabCategory.Navigation },
|
||||
|
||||
// Data
|
||||
{ id: 'rest-client', category: PrefabCategory.Data },
|
||||
{ id: 'local-storage', category: PrefabCategory.Data },
|
||||
{ id: 'data-table', category: PrefabCategory.Data },
|
||||
|
||||
// Feedback
|
||||
{ id: 'toast', category: PrefabCategory.Feedback },
|
||||
{ id: 'loading-spinner', category: PrefabCategory.Feedback },
|
||||
{ id: 'progress-bar', category: PrefabCategory.Feedback },
|
||||
];
|
||||
```
|
||||
|
||||
## Files to Create
|
||||
|
||||
1. `packages/noodl-editor/static/builtin-prefabs/index.json` - Manifest
|
||||
2. `packages/noodl-editor/static/builtin-prefabs/prefabs/` - Prefab directories
|
||||
3. `packages/noodl-editor/src/editor/src/models/prefab/sources/BuiltInPrefabSource.ts` - Source implementation
|
||||
4. `scripts/bundle-prefabs.ts` - Build script
|
||||
5. `prefab-sources/` - Source projects for built-in prefabs
|
||||
|
||||
## Files to Modify
|
||||
|
||||
1. `packages/noodl-editor/src/editor/src/models/prefab/PrefabRegistry.ts`
|
||||
- Register BuiltInPrefabSource
|
||||
- Add category support
|
||||
|
||||
2. `packages/noodl-editor/src/editor/src/views/NodePicker/tabs/NodePickerSearchView/NodePickerSearchView.tsx`
|
||||
- Add category filtering
|
||||
- Show "Built-in" badge
|
||||
|
||||
3. `packages/noodl-editor/src/editor/src/views/NodePicker/components/ModuleCard/ModuleCard.tsx`
|
||||
- Add "Built-in" badge styling
|
||||
- Show category
|
||||
|
||||
4. `package.json`
|
||||
- Add bundle-prefabs script
|
||||
|
||||
5. `webpack.config.js` or equivalent
|
||||
- Include static/builtin-prefabs in build
|
||||
|
||||
## Implementation Steps
|
||||
|
||||
### Phase 1: Infrastructure
|
||||
1. Create bundle directory structure
|
||||
2. Implement BuiltInPrefabSource
|
||||
3. Create manifest format
|
||||
4. Register source in PrefabRegistry
|
||||
|
||||
### Phase 2: Build Pipeline
|
||||
1. Create bundle-prefabs script
|
||||
2. Add to build process
|
||||
3. Test bundling works
|
||||
|
||||
### Phase 3: Initial Prefabs
|
||||
1. Create Form Input prefab
|
||||
2. Create Form Button prefab
|
||||
3. Create Card layout prefab
|
||||
4. Test import/collision handling
|
||||
|
||||
### Phase 4: UI Updates
|
||||
1. Add "Built-in" badge
|
||||
2. Add category filter
|
||||
3. Show built-in prefabs first
|
||||
|
||||
### Phase 5: Full Prefab Set
|
||||
1. Create remaining form prefabs
|
||||
2. Create layout prefabs
|
||||
3. Create data prefabs
|
||||
4. Create navigation prefabs
|
||||
|
||||
### Phase 6: Documentation
|
||||
1. Document built-in prefabs
|
||||
2. Add usage examples
|
||||
3. Create component docs
|
||||
|
||||
## Initial Built-in Prefabs
|
||||
|
||||
### Priority 1 (MVP)
|
||||
| Prefab | Category | Components |
|
||||
|--------|----------|------------|
|
||||
| Form Input | Forms | TextInput, Label, ErrorMessage |
|
||||
| Form Button | Forms | Button, LoadingState |
|
||||
| Card | Layout | Card, CardHeader, CardBody |
|
||||
| Modal | Layout | Modal, ModalTrigger, ModalContent |
|
||||
| REST Client | Data | RESTRequest, ResponseHandler |
|
||||
|
||||
### Priority 2
|
||||
| Prefab | Category | Components |
|
||||
|--------|----------|------------|
|
||||
| Form Textarea | Forms | Textarea, CharCount |
|
||||
| Form Checkbox | Forms | Checkbox, CheckboxGroup |
|
||||
| Form Select | Forms | Select, Option |
|
||||
| Drawer | Layout | Drawer, DrawerTrigger |
|
||||
| Toast | Feedback | Toast, ToastContainer |
|
||||
|
||||
### Priority 3
|
||||
| Prefab | Category | Components |
|
||||
|--------|----------|------------|
|
||||
| Tabs | Layout | TabBar, TabPanel |
|
||||
| Accordion | Layout | Accordion, AccordionItem |
|
||||
| Navbar | Navigation | Navbar, NavItem |
|
||||
| Data Table | Data | Table, Column, Row, Cell |
|
||||
|
||||
## Testing Checklist
|
||||
|
||||
- [ ] Built-in prefabs load without network
|
||||
- [ ] Prefabs appear first in list
|
||||
- [ ] "Built-in" badge displays correctly
|
||||
- [ ] Category filter works
|
||||
- [ ] Import works for each prefab
|
||||
- [ ] Collision detection works
|
||||
- [ ] Styles import correctly
|
||||
- [ ] Works in air-gapped environment
|
||||
- [ ] Bundle size is acceptable
|
||||
- [ ] Load time is acceptable
|
||||
|
||||
## Dependencies
|
||||
|
||||
- COMP-001 (Prefab System Refactoring)
|
||||
|
||||
## Blocked By
|
||||
|
||||
- COMP-001
|
||||
|
||||
## Blocks
|
||||
|
||||
- None (can proceed in parallel with COMP-003+)
|
||||
|
||||
## Estimated Effort
|
||||
|
||||
- Infrastructure: 3-4 hours
|
||||
- Build pipeline: 2-3 hours
|
||||
- BuiltInPrefabSource: 2-3 hours
|
||||
- MVP prefabs (5): 8-10 hours
|
||||
- UI updates: 2-3 hours
|
||||
- Testing: 2-3 hours
|
||||
- **Total: 19-26 hours**
|
||||
|
||||
## Success Criteria
|
||||
|
||||
1. Built-in prefabs available immediately
|
||||
2. Work offline without network
|
||||
3. Clear "Built-in" distinction in UI
|
||||
4. Categories organize prefabs logically
|
||||
5. Import flow works smoothly
|
||||
6. Bundle size < 5MB
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
- User can hide built-in prefabs
|
||||
- Community voting for built-in inclusion
|
||||
- Per-category enable/disable
|
||||
- Built-in prefab updates notification
|
||||
- Prefab source code viewing
|
||||
@@ -0,0 +1,380 @@
|
||||
# COMP-003: Component Export to Repository
|
||||
|
||||
## Overview
|
||||
|
||||
Enable users to export components from their project to a GitHub repository, creating a personal component library. This allows sharing components across projects and with team members.
|
||||
|
||||
## Context
|
||||
|
||||
Currently, component sharing is manual:
|
||||
1. Export components as zip (Cmd+Shift+E)
|
||||
2. Manually upload to GitHub or share file
|
||||
3. Others download and import
|
||||
|
||||
This task streamlines the process:
|
||||
1. Right-click component → "Export to Repository"
|
||||
2. Select target repository
|
||||
3. Component is committed with metadata
|
||||
4. Available in NodePicker for other projects
|
||||
|
||||
### Existing Export Flow
|
||||
|
||||
From `exportProjectComponents.ts`:
|
||||
```typescript
|
||||
export function exportProjectComponents() {
|
||||
ProjectImporter.instance.listComponentsAndDependencies(
|
||||
ProjectModel.instance._retainedProjectDirectory,
|
||||
(components) => {
|
||||
// Shows export popup
|
||||
// User selects components
|
||||
// Creates zip file
|
||||
}
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Requirements
|
||||
|
||||
### Functional Requirements
|
||||
|
||||
1. **Export Entry Points**
|
||||
- Right-click component → "Export to Repository"
|
||||
- Component sheet context menu → "Export Sheet to Repository"
|
||||
- File menu → "Export Components to Repository"
|
||||
|
||||
2. **Repository Selection**
|
||||
- List user's GitHub repositories
|
||||
- "Create new repository" option
|
||||
- Remember last used repository
|
||||
- Suggest `noodl-components` naming convention
|
||||
|
||||
3. **Component Selection**
|
||||
- Select individual components
|
||||
- Select entire sheets
|
||||
- Auto-select dependencies
|
||||
- Preview what will be exported
|
||||
|
||||
4. **Metadata Entry**
|
||||
- Component name (prefilled)
|
||||
- Description
|
||||
- Tags
|
||||
- Version (auto-increment option)
|
||||
- Category selection
|
||||
|
||||
5. **Export Process**
|
||||
- Create component directory structure
|
||||
- Generate prefab.json manifest
|
||||
- Commit to repository
|
||||
- Optional: Push immediately or stage
|
||||
|
||||
6. **Repository Structure**
|
||||
- Standard directory layout
|
||||
- index.json manifest for discovery
|
||||
- README generation
|
||||
- License file option
|
||||
|
||||
### Non-Functional Requirements
|
||||
|
||||
- Export completes in < 30 seconds
|
||||
- Works with existing repositories
|
||||
- Handles large components (100+ nodes)
|
||||
- Conflict detection with existing exports
|
||||
|
||||
## Technical Approach
|
||||
|
||||
### 1. Repository Structure Convention
|
||||
|
||||
```
|
||||
my-noodl-components/
|
||||
├── index.json # Repository manifest
|
||||
├── README.md # Auto-generated docs
|
||||
├── LICENSE # Optional license
|
||||
└── components/
|
||||
├── my-button/
|
||||
│ ├── prefab.json # Component metadata
|
||||
│ ├── component.ndjson # Noodl component data
|
||||
│ ├── dependencies/ # Style/variant dependencies
|
||||
│ └── assets/ # Images, fonts
|
||||
├── my-card/
|
||||
│ └── ...
|
||||
└── my-form/
|
||||
└── ...
|
||||
```
|
||||
|
||||
### 2. Repository Manifest (index.json)
|
||||
|
||||
```json
|
||||
{
|
||||
"$schema": "https://opennoodl.net/schemas/component-repo-v1.json",
|
||||
"name": "My Noodl Components",
|
||||
"description": "Personal component library",
|
||||
"author": {
|
||||
"name": "John Doe",
|
||||
"github": "johndoe"
|
||||
},
|
||||
"version": "1.0.0",
|
||||
"noodlVersion": ">=2.10.0",
|
||||
"components": [
|
||||
{
|
||||
"id": "my-button",
|
||||
"name": "My Button",
|
||||
"description": "Custom styled button",
|
||||
"version": "1.2.0",
|
||||
"path": "components/my-button",
|
||||
"tags": ["form", "button"],
|
||||
"category": "Forms"
|
||||
}
|
||||
],
|
||||
"updatedAt": "2024-01-15T10:30:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Component Export Service
|
||||
|
||||
```typescript
|
||||
// packages/noodl-editor/src/editor/src/services/ComponentExportService.ts
|
||||
|
||||
interface ExportOptions {
|
||||
components: ComponentModel[];
|
||||
repository: GitHubRepo;
|
||||
metadata: {
|
||||
description: string;
|
||||
tags: string[];
|
||||
category: string;
|
||||
version?: string;
|
||||
};
|
||||
commitMessage?: string;
|
||||
pushImmediately?: boolean;
|
||||
}
|
||||
|
||||
interface ExportResult {
|
||||
success: boolean;
|
||||
exportedComponents: string[];
|
||||
commitSha?: string;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
class ComponentExportService {
|
||||
private static instance: ComponentExportService;
|
||||
|
||||
// Export flow
|
||||
async exportToRepository(options: ExportOptions): Promise<ExportResult>;
|
||||
|
||||
// Repository management
|
||||
async listUserRepositories(): Promise<GitHubRepo[]>;
|
||||
async createComponentRepository(name: string): Promise<GitHubRepo>;
|
||||
async validateRepository(repo: GitHubRepo): Promise<boolean>;
|
||||
|
||||
// Component preparation
|
||||
async prepareExport(components: ComponentModel[]): Promise<ExportPackage>;
|
||||
async resolveExportDependencies(components: ComponentModel[]): Promise<ComponentModel[]>;
|
||||
|
||||
// File generation
|
||||
generatePrefabManifest(component: ComponentModel, metadata: ExportMetadata): PrefabManifest;
|
||||
generateRepoManifest(repo: GitHubRepo, components: PrefabManifest[]): RepoManifest;
|
||||
generateReadme(repo: GitHubRepo, components: PrefabManifest[]): string;
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Export Modal Flow
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ Export to Repository [×] │
|
||||
├─────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ COMPONENTS TO EXPORT │
|
||||
│ ┌─────────────────────────────────────────────────────────────────┐ │
|
||||
│ │ ☑ MyButton + 2 dependencies │ │
|
||||
│ │ └─ ☑ ButtonStyles (variant) │ │
|
||||
│ │ └─ ☑ PrimaryColor (color style) │ │
|
||||
│ └─────────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ TARGET REPOSITORY │
|
||||
│ ┌─────────────────────────────────────────────────────────────────┐ │
|
||||
│ │ johndoe/noodl-components [▾] │ │
|
||||
│ └─────────────────────────────────────────────────────────────────┘ │
|
||||
│ [+ Create new repository] │
|
||||
│ │
|
||||
│ METADATA │
|
||||
│ Name: [My Button ] │
|
||||
│ Description: [Custom styled button with loading state ] │
|
||||
│ Tags: [form] [button] [+] │
|
||||
│ Category: [Forms ▾] │
|
||||
│ Version: [1.0.0 ] ☑ Auto-increment │
|
||||
│ │
|
||||
│ COMMIT │
|
||||
│ Message: [Add MyButton component ] │
|
||||
│ ☑ Push to GitHub immediately │
|
||||
│ │
|
||||
│ [Cancel] [Export] │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 5. Export Process Flow
|
||||
|
||||
```typescript
|
||||
async exportToRepository(options: ExportOptions): Promise<ExportResult> {
|
||||
const { components, repository, metadata } = options;
|
||||
|
||||
// 1. Clone or open repository locally
|
||||
const localRepo = await this.getLocalRepository(repository);
|
||||
|
||||
// 2. Resolve all dependencies
|
||||
const allComponents = await this.resolveExportDependencies(components);
|
||||
|
||||
// 3. Generate component files
|
||||
for (const component of allComponents) {
|
||||
const componentDir = path.join(localRepo.path, 'components', component.id);
|
||||
|
||||
// Export component data
|
||||
await this.exportComponentData(component, componentDir);
|
||||
|
||||
// Generate prefab manifest
|
||||
const manifest = this.generatePrefabManifest(component, metadata);
|
||||
await fs.writeJson(path.join(componentDir, 'prefab.json'), manifest);
|
||||
}
|
||||
|
||||
// 4. Update repository manifest
|
||||
const repoManifest = await this.updateRepoManifest(localRepo, allComponents);
|
||||
|
||||
// 5. Update README
|
||||
await this.updateReadme(localRepo, repoManifest);
|
||||
|
||||
// 6. Commit changes
|
||||
const git = new Git(mergeProject);
|
||||
await git.openRepository(localRepo.path);
|
||||
await git.commit(options.commitMessage || `Add ${components[0].name}`);
|
||||
|
||||
// 7. Push if requested
|
||||
if (options.pushImmediately) {
|
||||
await git.push({});
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
exportedComponents: allComponents.map(c => c.name),
|
||||
commitSha: await git.getHeadCommitId()
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
## Files to Create
|
||||
|
||||
1. `packages/noodl-editor/src/editor/src/services/ComponentExportService.ts`
|
||||
2. `packages/noodl-core-ui/src/components/modals/ExportToRepoModal/ExportToRepoModal.tsx`
|
||||
3. `packages/noodl-core-ui/src/components/modals/ExportToRepoModal/ComponentSelector.tsx`
|
||||
4. `packages/noodl-core-ui/src/components/modals/ExportToRepoModal/RepoSelector.tsx`
|
||||
5. `packages/noodl-core-ui/src/components/modals/ExportToRepoModal/MetadataForm.tsx`
|
||||
6. `packages/noodl-core-ui/src/components/modals/CreateRepoModal/CreateRepoModal.tsx`
|
||||
7. `packages/noodl-editor/src/editor/src/utils/componentExporter.ts` - Low-level export utilities
|
||||
|
||||
## Files to Modify
|
||||
|
||||
1. `packages/noodl-editor/src/editor/src/views/nodegrapheditor.js`
|
||||
- Add right-click context menu option
|
||||
|
||||
2. `packages/noodl-editor/src/editor/src/views/panels/componentspanel.tsx`
|
||||
- Add export option to component context menu
|
||||
|
||||
3. `packages/noodl-editor/src/editor/src/utils/exportProjectComponents.ts`
|
||||
- Refactor to share code with repository export
|
||||
|
||||
4. `packages/noodl-editor/src/editor/src/models/prefab/sources/GitHubPrefabSource.ts`
|
||||
- Implement full source for reading from component repos
|
||||
|
||||
## Implementation Steps
|
||||
|
||||
### Phase 1: Export Service Foundation
|
||||
1. Create ComponentExportService
|
||||
2. Implement dependency resolution
|
||||
3. Create file generation utilities
|
||||
4. Define repository structure
|
||||
|
||||
### Phase 2: Repository Management
|
||||
1. List user repositories (via GitHub API)
|
||||
2. Create new repository flow
|
||||
3. Local repository management
|
||||
4. Clone/pull existing repos
|
||||
|
||||
### Phase 3: Export Modal
|
||||
1. Create ExportToRepoModal
|
||||
2. Create ComponentSelector
|
||||
3. Create RepoSelector
|
||||
4. Create MetadataForm
|
||||
|
||||
### Phase 4: Git Integration
|
||||
1. Stage exported files
|
||||
2. Commit with message
|
||||
3. Push to remote
|
||||
4. Handle conflicts
|
||||
|
||||
### Phase 5: Context Menu Integration
|
||||
1. Add to component right-click menu
|
||||
2. Add to sheet context menu
|
||||
3. Add to File menu
|
||||
|
||||
### Phase 6: Testing & Polish
|
||||
1. Test with various component types
|
||||
2. Test dependency resolution
|
||||
3. Error handling
|
||||
4. Progress indication
|
||||
|
||||
## Testing Checklist
|
||||
|
||||
- [ ] Export single component works
|
||||
- [ ] Export multiple components works
|
||||
- [ ] Dependencies auto-selected
|
||||
- [ ] Repository selection lists repos
|
||||
- [ ] Create new repository works
|
||||
- [ ] Metadata saved correctly
|
||||
- [ ] Files committed to repo
|
||||
- [ ] Push to GitHub works
|
||||
- [ ] Repository manifest updated
|
||||
- [ ] README generated/updated
|
||||
- [ ] Handles existing components (update)
|
||||
- [ ] Version auto-increment works
|
||||
- [ ] Error messages helpful
|
||||
|
||||
## Dependencies
|
||||
|
||||
- COMP-001 (Prefab System Refactoring)
|
||||
- GIT-001 (GitHub OAuth) - for repository access
|
||||
|
||||
## Blocked By
|
||||
|
||||
- COMP-001
|
||||
- GIT-001
|
||||
|
||||
## Blocks
|
||||
|
||||
- COMP-004 (Organization Components)
|
||||
- COMP-005 (Component Import with Version Control)
|
||||
|
||||
## Estimated Effort
|
||||
|
||||
- Export service: 4-5 hours
|
||||
- Repository management: 3-4 hours
|
||||
- Export modal: 4-5 hours
|
||||
- Git integration: 3-4 hours
|
||||
- Context menu: 2-3 hours
|
||||
- Testing & polish: 3-4 hours
|
||||
- **Total: 19-25 hours**
|
||||
|
||||
## Success Criteria
|
||||
|
||||
1. Components can be exported via right-click
|
||||
2. Dependencies are automatically included
|
||||
3. Repository structure is consistent
|
||||
4. Manifests are generated correctly
|
||||
5. Git operations work smoothly
|
||||
6. Components are importable via COMP-004+
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
- Export to npm package
|
||||
- Export to Noodl marketplace
|
||||
- Batch export multiple components
|
||||
- Export templates/starters
|
||||
- Preview component before export
|
||||
- Export history/versioning
|
||||
@@ -0,0 +1,396 @@
|
||||
# COMP-004: Organization Components Repository
|
||||
|
||||
## Overview
|
||||
|
||||
Enable teams to share a central component repository at the organization level. When a user belongs to a GitHub organization, they can access shared components from that org's component repository, creating a design system that's consistent across all team projects.
|
||||
|
||||
## Context
|
||||
|
||||
Individual developers can export components to personal repos (COMP-003), but teams need:
|
||||
- Shared component library accessible to all org members
|
||||
- Consistent design system across projects
|
||||
- Centralized component governance
|
||||
- Version control for team components
|
||||
|
||||
This task adds organization-level component repositories to the prefab source system.
|
||||
|
||||
### Organization Flow
|
||||
|
||||
```
|
||||
User authenticates with GitHub (GIT-001)
|
||||
↓
|
||||
System detects user's organizations
|
||||
↓
|
||||
For each org, check for `noodl-components` repo
|
||||
↓
|
||||
Register as prefab source if found
|
||||
↓
|
||||
Components appear in NodePicker
|
||||
```
|
||||
|
||||
## Requirements
|
||||
|
||||
### Functional Requirements
|
||||
|
||||
1. **Organization Detection**
|
||||
- Detect user's GitHub organizations
|
||||
- Check for component repository in each org
|
||||
- Support custom repo names (configurable)
|
||||
- Handle multiple organizations
|
||||
|
||||
2. **Repository Discovery**
|
||||
- Auto-detect `{org}/noodl-components` repos
|
||||
- Validate repository structure
|
||||
- Read repository manifest
|
||||
- Cache organization components
|
||||
|
||||
3. **Component Access**
|
||||
- List org components in NodePicker
|
||||
- Show org badge on components
|
||||
- Filter by organization
|
||||
- Search across all org repos
|
||||
|
||||
4. **Permission Handling**
|
||||
- Respect GitHub permissions
|
||||
- Handle private repositories
|
||||
- Clear error messages for access issues
|
||||
- Re-auth prompt when needed
|
||||
|
||||
5. **Organization Settings**
|
||||
- Enable/disable specific org repos
|
||||
- Priority ordering between orgs
|
||||
- Refresh/sync controls
|
||||
- View org repo on GitHub
|
||||
|
||||
### Non-Functional Requirements
|
||||
|
||||
- Org components load within 3 seconds
|
||||
- Cached for offline use after first load
|
||||
- Handles orgs with 100+ components
|
||||
- Works with GitHub Enterprise (future)
|
||||
|
||||
## Technical Approach
|
||||
|
||||
### 1. Organization Prefab Source
|
||||
|
||||
```typescript
|
||||
// packages/noodl-editor/src/editor/src/models/prefab/sources/OrganizationPrefabSource.ts
|
||||
|
||||
interface OrganizationConfig {
|
||||
orgName: string;
|
||||
repoName: string;
|
||||
enabled: boolean;
|
||||
priority: number;
|
||||
}
|
||||
|
||||
class OrganizationPrefabSource implements PrefabSource {
|
||||
config: PrefabSourceConfig;
|
||||
|
||||
constructor(private orgConfig: OrganizationConfig) {
|
||||
this.config = {
|
||||
id: `org:${orgConfig.orgName}`,
|
||||
name: orgConfig.orgName,
|
||||
priority: orgConfig.priority,
|
||||
enabled: orgConfig.enabled
|
||||
};
|
||||
}
|
||||
|
||||
async initialize(): Promise<void> {
|
||||
// Verify repo access
|
||||
const hasAccess = await this.verifyRepoAccess();
|
||||
if (!hasAccess) {
|
||||
throw new PrefabSourceError('No access to organization repository');
|
||||
}
|
||||
|
||||
// Load manifest
|
||||
await this.loadManifest();
|
||||
}
|
||||
|
||||
async listPrefabs(): Promise<PrefabMetadata[]> {
|
||||
const manifest = await this.getManifest();
|
||||
return manifest.components.map(c => ({
|
||||
...c,
|
||||
id: `org:${this.orgConfig.orgName}:${c.id}`,
|
||||
source: 'organization',
|
||||
organization: this.orgConfig.orgName
|
||||
}));
|
||||
}
|
||||
|
||||
async downloadPrefab(id: string): Promise<string> {
|
||||
// Clone specific component from repo
|
||||
const componentPath = this.getComponentPath(id);
|
||||
return await this.downloadFromGitHub(componentPath);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Organization Discovery Service
|
||||
|
||||
```typescript
|
||||
// packages/noodl-editor/src/editor/src/services/OrganizationService.ts
|
||||
|
||||
interface Organization {
|
||||
name: string;
|
||||
displayName: string;
|
||||
avatarUrl: string;
|
||||
hasComponentRepo: boolean;
|
||||
componentRepoUrl?: string;
|
||||
memberCount?: number;
|
||||
}
|
||||
|
||||
class OrganizationService {
|
||||
private static instance: OrganizationService;
|
||||
|
||||
// Discovery
|
||||
async discoverOrganizations(): Promise<Organization[]>;
|
||||
async checkForComponentRepo(orgName: string): Promise<boolean>;
|
||||
async validateComponentRepo(orgName: string, repoName: string): Promise<boolean>;
|
||||
|
||||
// Registration
|
||||
async registerOrgSource(org: Organization): Promise<void>;
|
||||
async unregisterOrgSource(orgName: string): Promise<void>;
|
||||
|
||||
// Settings
|
||||
getOrgSettings(orgName: string): OrganizationConfig;
|
||||
updateOrgSettings(orgName: string, settings: Partial<OrganizationConfig>): void;
|
||||
|
||||
// Refresh
|
||||
async refreshOrgComponents(orgName: string): Promise<void>;
|
||||
async refreshAllOrgs(): Promise<void>;
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Auto-Registration on Login
|
||||
|
||||
```typescript
|
||||
// Integration with GitHub OAuth
|
||||
|
||||
async function onGitHubAuthenticated(token: string): Promise<void> {
|
||||
const orgService = OrganizationService.instance;
|
||||
const registry = PrefabRegistry.instance;
|
||||
|
||||
// Discover user's organizations
|
||||
const orgs = await orgService.discoverOrganizations();
|
||||
|
||||
for (const org of orgs) {
|
||||
// Check for component repo
|
||||
const hasRepo = await orgService.checkForComponentRepo(org.name);
|
||||
|
||||
if (hasRepo) {
|
||||
// Register as prefab source
|
||||
const source = new OrganizationPrefabSource({
|
||||
orgName: org.name,
|
||||
repoName: 'noodl-components',
|
||||
enabled: true,
|
||||
priority: 80 // Below built-in, above docs
|
||||
});
|
||||
|
||||
registry.registerSource(source);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Organization Settings UI
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ Organization Components │
|
||||
├─────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ Connected Organizations │
|
||||
│ │
|
||||
│ ┌─────────────────────────────────────────────────────────────────┐ │
|
||||
│ │ [🏢] Acme Corp │ │
|
||||
│ │ noodl-components • 24 components • Last synced: 2h ago │ │
|
||||
│ │ [☑ Enabled] [⚙️ Settings] [🔄 Sync] [↗️ View on GitHub] │ │
|
||||
│ ├─────────────────────────────────────────────────────────────────┤ │
|
||||
│ │ [🏢] StartupXYZ │ │
|
||||
│ │ noodl-components • 8 components • Last synced: 1d ago │ │
|
||||
│ │ [☑ Enabled] [⚙️ Settings] [🔄 Sync] [↗️ View on GitHub] │ │
|
||||
│ ├─────────────────────────────────────────────────────────────────┤ │
|
||||
│ │ [🏢] OpenSource Collective │ │
|
||||
│ │ ⚠️ No component repository found │ │
|
||||
│ │ [Create Repository] │ │
|
||||
│ └─────────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ [🔄 Refresh Organizations] │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 5. NodePicker Integration
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ Prefabs │
|
||||
├─────────────────────────────────────────────────────────────────────┤
|
||||
│ 🔍 Search prefabs... │
|
||||
├─────────────────────────────────────────────────────────────────────┤
|
||||
│ Source: [All Sources ▾] Category: [All ▾] │
|
||||
│ • All Sources │
|
||||
│ • Built-in │
|
||||
│ • Acme Corp │
|
||||
│ • StartupXYZ │
|
||||
│ • Community │
|
||||
├─────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ACME CORP │
|
||||
│ ┌────────────────────────────────────────────────────────────────┐ │
|
||||
│ │ 🏢 AcmeButton v2.1.0 [Clone] │ │
|
||||
│ │ Standard button following Acme design system │ │
|
||||
│ ├────────────────────────────────────────────────────────────────┤ │
|
||||
│ │ 🏢 AcmeCard v1.3.0 [Clone] │ │
|
||||
│ │ Card component with Acme styling │ │
|
||||
│ └────────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ BUILT-IN │
|
||||
│ ┌────────────────────────────────────────────────────────────────┐ │
|
||||
│ │ 📦 Form Input v1.0.0 [Clone] │ │
|
||||
│ │ Standard form input with validation │ │
|
||||
│ └────────────────────────────────────────────────────────────────┘ │
|
||||
```
|
||||
|
||||
## Files to Create
|
||||
|
||||
1. `packages/noodl-editor/src/editor/src/models/prefab/sources/OrganizationPrefabSource.ts`
|
||||
2. `packages/noodl-editor/src/editor/src/services/OrganizationService.ts`
|
||||
3. `packages/noodl-core-ui/src/components/settings/OrganizationSettings/OrganizationSettings.tsx`
|
||||
4. `packages/noodl-core-ui/src/components/settings/OrganizationSettings/OrgCard.tsx`
|
||||
5. `packages/noodl-core-ui/src/preview/launcher/Launcher/views/OrganizationsView.tsx`
|
||||
|
||||
## Files to Modify
|
||||
|
||||
1. `packages/noodl-editor/src/editor/src/services/GitHubOAuthService.ts`
|
||||
- Trigger org discovery on auth
|
||||
|
||||
2. `packages/noodl-editor/src/editor/src/models/prefab/PrefabRegistry.ts`
|
||||
- Handle org sources dynamically
|
||||
- Add source filtering
|
||||
|
||||
3. `packages/noodl-editor/src/editor/src/views/NodePicker/tabs/NodePickerSearchView/NodePickerSearchView.tsx`
|
||||
- Add source filter dropdown
|
||||
- Show org badges
|
||||
|
||||
4. `packages/noodl-editor/src/editor/src/views/NodePicker/components/ModuleCard/ModuleCard.tsx`
|
||||
- Show organization name
|
||||
- Different styling for org components
|
||||
|
||||
5. `packages/noodl-core-ui/src/preview/launcher/Launcher/Launcher.tsx`
|
||||
- Add Organizations section/page
|
||||
|
||||
## Implementation Steps
|
||||
|
||||
### Phase 1: Organization Discovery
|
||||
1. Create OrganizationService
|
||||
2. Implement GitHub org listing
|
||||
3. Check for component repos
|
||||
4. Store org data
|
||||
|
||||
### Phase 2: Organization Source
|
||||
1. Create OrganizationPrefabSource
|
||||
2. Implement manifest loading
|
||||
3. Implement component downloading
|
||||
4. Add to PrefabRegistry
|
||||
|
||||
### Phase 3: Auto-Registration
|
||||
1. Hook into OAuth flow
|
||||
2. Auto-register on login
|
||||
3. Handle permission changes
|
||||
4. Persist org settings
|
||||
|
||||
### Phase 4: Settings UI
|
||||
1. Create OrganizationSettings component
|
||||
2. Create OrgCard component
|
||||
3. Add to Settings panel
|
||||
4. Implement enable/disable
|
||||
|
||||
### Phase 5: NodePicker Integration
|
||||
1. Add source filter
|
||||
2. Show org grouping
|
||||
3. Add org badges
|
||||
4. Update search
|
||||
|
||||
### Phase 6: Polish
|
||||
1. Sync/refresh functionality
|
||||
2. Error handling
|
||||
3. Offline support
|
||||
4. Performance optimization
|
||||
|
||||
## Testing Checklist
|
||||
|
||||
- [ ] Organizations discovered on login
|
||||
- [ ] Component repos detected
|
||||
- [ ] Source registered for orgs with repos
|
||||
- [ ] Components appear in NodePicker
|
||||
- [ ] Source filter works
|
||||
- [ ] Org badge displays
|
||||
- [ ] Enable/disable works
|
||||
- [ ] Sync refreshes components
|
||||
- [ ] Private repos accessible
|
||||
- [ ] Permission errors handled
|
||||
- [ ] Works with multiple orgs
|
||||
- [ ] Caching works offline
|
||||
- [ ] Settings persist
|
||||
|
||||
## Dependencies
|
||||
|
||||
- COMP-001 (Prefab System Refactoring)
|
||||
- COMP-003 (Component Export) - for repository structure
|
||||
- GIT-001 (GitHub OAuth) - for organization access
|
||||
|
||||
## Blocked By
|
||||
|
||||
- COMP-001
|
||||
- GIT-001
|
||||
|
||||
## Blocks
|
||||
|
||||
- COMP-005 (depends on org repos existing)
|
||||
- COMP-006 (depends on org repos existing)
|
||||
|
||||
## Estimated Effort
|
||||
|
||||
- Organization discovery: 3-4 hours
|
||||
- OrganizationPrefabSource: 4-5 hours
|
||||
- Auto-registration: 2-3 hours
|
||||
- Settings UI: 3-4 hours
|
||||
- NodePicker integration: 3-4 hours
|
||||
- Polish & testing: 3-4 hours
|
||||
- **Total: 18-24 hours**
|
||||
|
||||
## Success Criteria
|
||||
|
||||
1. Orgs auto-detected on GitHub login
|
||||
2. Component repos discovered automatically
|
||||
3. Org components appear in NodePicker
|
||||
4. Can filter by organization
|
||||
5. Settings allow enable/disable
|
||||
6. Works with private repositories
|
||||
7. Clear error messages for access issues
|
||||
|
||||
## Repository Setup Guide (For Users)
|
||||
|
||||
To create an organization component repository:
|
||||
|
||||
1. Create repo named `noodl-components` in your org
|
||||
2. Add `index.json` manifest file:
|
||||
```json
|
||||
{
|
||||
"name": "Acme Components",
|
||||
"version": "1.0.0",
|
||||
"components": []
|
||||
}
|
||||
```
|
||||
3. Export components using COMP-003
|
||||
4. Noodl will auto-detect the repository
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
- GitHub Enterprise support
|
||||
- Repository templates
|
||||
- Permission levels (read/write per component)
|
||||
- Component approval workflow
|
||||
- Usage analytics per org
|
||||
- Component deprecation notices
|
||||
- Multi-repo per org support
|
||||
@@ -0,0 +1,414 @@
|
||||
# COMP-005: Component Import with Version Control
|
||||
|
||||
## Overview
|
||||
|
||||
Track the source and version of imported components, enabling update notifications, selective updates, and clear understanding of component provenance. When a component is imported from a repository, remember where it came from and notify users when updates are available.
|
||||
|
||||
## Context
|
||||
|
||||
Currently, imported components lose connection to their source:
|
||||
- No tracking of where component came from
|
||||
- No awareness of available updates
|
||||
- No way to re-sync with source
|
||||
- Manual process to check for new versions
|
||||
|
||||
This task adds version tracking and update management:
|
||||
- Track component source (built-in, org, personal, docs)
|
||||
- Store version information
|
||||
- Check for updates periodically
|
||||
- Enable selective component updates
|
||||
|
||||
### Import Flow Today
|
||||
|
||||
```
|
||||
User clicks "Clone" → Component imported → No source tracking
|
||||
```
|
||||
|
||||
### Import Flow After This Task
|
||||
|
||||
```
|
||||
User clicks "Clone" → Component imported → Source/version tracked
|
||||
↓
|
||||
Background: Check for updates periodically
|
||||
↓
|
||||
Notification: "2 components have updates available"
|
||||
↓
|
||||
User reviews and selects updates
|
||||
```
|
||||
|
||||
## Requirements
|
||||
|
||||
### Functional Requirements
|
||||
|
||||
1. **Source Tracking**
|
||||
- Record source repository/location for each import
|
||||
- Store version at time of import
|
||||
- Track import timestamp
|
||||
- Handle components without source (legacy)
|
||||
|
||||
2. **Version Information**
|
||||
- Display current version in component panel
|
||||
- Show source badge (Built-in, Org name, etc.)
|
||||
- Link to source documentation
|
||||
- View changelog
|
||||
|
||||
3. **Update Detection**
|
||||
- Background check for available updates
|
||||
- Badge/indicator for components with updates
|
||||
- List all updatable components
|
||||
- Compare current vs available version
|
||||
|
||||
4. **Update Process**
|
||||
- Preview what changes in update
|
||||
- Selective update (choose which to update)
|
||||
- Backup current before update
|
||||
- Rollback option if update fails
|
||||
|
||||
5. **Import Metadata Storage**
|
||||
- Store in project metadata
|
||||
- Survive project export/import
|
||||
- Handle renamed components
|
||||
|
||||
### Non-Functional Requirements
|
||||
|
||||
- Update check < 5 seconds
|
||||
- No performance impact on project load
|
||||
- Works offline (shows cached status)
|
||||
- Handles 100+ tracked components
|
||||
|
||||
## Technical Approach
|
||||
|
||||
### 1. Import Metadata Schema
|
||||
|
||||
```typescript
|
||||
// Stored in project.json metadata
|
||||
interface ComponentImportMetadata {
|
||||
components: ImportedComponent[];
|
||||
lastUpdateCheck: string; // ISO timestamp
|
||||
}
|
||||
|
||||
interface ImportedComponent {
|
||||
componentId: string; // Internal Noodl component ID
|
||||
componentName: string; // Display name at import time
|
||||
source: ComponentSource;
|
||||
importedVersion: string;
|
||||
importedAt: string; // ISO timestamp
|
||||
lastUpdatedAt?: string; // When user last updated
|
||||
updateAvailable?: string; // Available version if any
|
||||
checksum?: string; // For detecting local modifications
|
||||
}
|
||||
|
||||
interface ComponentSource {
|
||||
type: 'builtin' | 'organization' | 'personal' | 'docs' | 'unknown';
|
||||
repository?: string; // GitHub repo URL
|
||||
organization?: string; // Org name if type is 'organization'
|
||||
prefabId: string; // ID in source manifest
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Import Tracking Service
|
||||
|
||||
```typescript
|
||||
// packages/noodl-editor/src/editor/src/services/ComponentTrackingService.ts
|
||||
|
||||
class ComponentTrackingService {
|
||||
private static instance: ComponentTrackingService;
|
||||
|
||||
// On import
|
||||
async trackImport(
|
||||
componentId: string,
|
||||
source: ComponentSource,
|
||||
version: string
|
||||
): Promise<void>;
|
||||
|
||||
// Queries
|
||||
getImportedComponents(): ImportedComponent[];
|
||||
getComponentSource(componentId: string): ComponentSource | null;
|
||||
getComponentsWithUpdates(): ImportedComponent[];
|
||||
|
||||
// Update checking
|
||||
async checkForUpdates(): Promise<UpdateCheckResult>;
|
||||
async checkComponentUpdate(componentId: string): Promise<UpdateInfo | null>;
|
||||
|
||||
// Update application
|
||||
async updateComponent(componentId: string): Promise<UpdateResult>;
|
||||
async updateAllComponents(componentIds: string[]): Promise<UpdateResult[]>;
|
||||
async rollbackUpdate(componentId: string): Promise<void>;
|
||||
|
||||
// Metadata
|
||||
async saveMetadata(): Promise<void>;
|
||||
async loadMetadata(): Promise<void>;
|
||||
}
|
||||
|
||||
interface UpdateCheckResult {
|
||||
checked: number;
|
||||
updatesAvailable: number;
|
||||
components: {
|
||||
componentId: string;
|
||||
currentVersion: string;
|
||||
availableVersion: string;
|
||||
changelogUrl?: string;
|
||||
}[];
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Update Check Process
|
||||
|
||||
```typescript
|
||||
async checkForUpdates(): Promise<UpdateCheckResult> {
|
||||
const imported = this.getImportedComponents();
|
||||
const result: UpdateCheckResult = {
|
||||
checked: 0,
|
||||
updatesAvailable: 0,
|
||||
components: []
|
||||
};
|
||||
|
||||
// Group by source for efficient checking
|
||||
const bySource = groupBy(imported, c => c.source.repository);
|
||||
|
||||
for (const [repo, components] of Object.entries(bySource)) {
|
||||
const source = PrefabRegistry.instance.getSource(repo);
|
||||
if (!source) continue;
|
||||
|
||||
// Fetch latest manifest
|
||||
const manifest = await source.getManifest();
|
||||
|
||||
for (const component of components) {
|
||||
result.checked++;
|
||||
|
||||
const latest = manifest.components.find(
|
||||
c => c.id === component.source.prefabId
|
||||
);
|
||||
|
||||
if (latest && semver.gt(latest.version, component.importedVersion)) {
|
||||
result.updatesAvailable++;
|
||||
result.components.push({
|
||||
componentId: component.componentId,
|
||||
currentVersion: component.importedVersion,
|
||||
availableVersion: latest.version,
|
||||
changelogUrl: latest.changelog
|
||||
});
|
||||
|
||||
// Update metadata
|
||||
component.updateAvailable = latest.version;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await this.saveMetadata();
|
||||
return result;
|
||||
}
|
||||
```
|
||||
|
||||
### 4. UI Components
|
||||
|
||||
#### Component Panel Badge
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ Components │
|
||||
├─────────────────────────────────────────────────────────────────────┤
|
||||
│ ├── Pages │
|
||||
│ │ └── HomePage │
|
||||
│ │ └── LoginPage │
|
||||
│ ├── Components │
|
||||
│ │ └── AcmeButton [🏢 v2.1.0] [⬆️ Update] │
|
||||
│ │ └── AcmeCard [🏢 v1.3.0] │
|
||||
│ │ └── MyCustomButton │
|
||||
│ │ └── FormInput [📦 v1.0.0] │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
#### Update Available Notification
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ 🔔 Component Updates Available │
|
||||
│ │
|
||||
│ 2 components have updates available from your organization. │
|
||||
│ │
|
||||
│ [View Updates] [Remind Me Later] │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
#### Update Modal
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ Component Updates [×] │
|
||||
├─────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ Available Updates │
|
||||
│ │
|
||||
│ ┌─────────────────────────────────────────────────────────────────┐ │
|
||||
│ │ ☑ AcmeButton │ │
|
||||
│ │ Current: v2.1.0 → Available: v2.2.0 │ │
|
||||
│ │ Source: Acme Corp │ │
|
||||
│ │ Changes: Added loading state, fixed hover color │ │
|
||||
│ │ [View Full Changelog] │ │
|
||||
│ ├─────────────────────────────────────────────────────────────────┤ │
|
||||
│ │ ☑ AcmeCard │ │
|
||||
│ │ Current: v1.3.0 → Available: v1.4.0 │ │
|
||||
│ │ Source: Acme Corp │ │
|
||||
│ │ Changes: Added shadow variants │ │
|
||||
│ └─────────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ⚠️ Updates will replace your imported components. Local │
|
||||
│ modifications may be lost. │
|
||||
│ │
|
||||
│ [Cancel] [Update Selected (2)] │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 5. Integration Points
|
||||
|
||||
```typescript
|
||||
// Hook into existing import flow
|
||||
// packages/noodl-editor/src/editor/src/models/modulelibrarymodel.ts
|
||||
|
||||
async installPrefab(prefabId: string, options?: InstallOptions): Promise<void> {
|
||||
// ... existing import logic ...
|
||||
|
||||
// After successful import, track it
|
||||
const source = this.detectSource(prefabId);
|
||||
const version = await this.getPrefabVersion(prefabId);
|
||||
|
||||
for (const componentId of importedComponentIds) {
|
||||
await ComponentTrackingService.instance.trackImport(
|
||||
componentId,
|
||||
source,
|
||||
version
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Files to Create
|
||||
|
||||
1. `packages/noodl-editor/src/editor/src/services/ComponentTrackingService.ts`
|
||||
2. `packages/noodl-core-ui/src/components/common/ComponentSourceBadge/ComponentSourceBadge.tsx`
|
||||
3. `packages/noodl-core-ui/src/components/modals/ComponentUpdatesModal/ComponentUpdatesModal.tsx`
|
||||
4. `packages/noodl-core-ui/src/components/modals/ComponentUpdatesModal/UpdateItem.tsx`
|
||||
5. `packages/noodl-core-ui/src/components/notifications/UpdateAvailableToast/UpdateAvailableToast.tsx`
|
||||
|
||||
## Files to Modify
|
||||
|
||||
1. `packages/noodl-editor/src/editor/src/models/modulelibrarymodel.ts`
|
||||
- Track imports after install
|
||||
- Add version detection
|
||||
|
||||
2. `packages/noodl-editor/src/editor/src/views/panels/componentspanel.tsx`
|
||||
- Show source badge
|
||||
- Show update indicator
|
||||
- Add "Check for Updates" action
|
||||
|
||||
3. `packages/noodl-editor/src/editor/src/models/projectmodel.ts`
|
||||
- Store/load import metadata
|
||||
- Add to project.json
|
||||
|
||||
4. `packages/noodl-editor/src/editor/src/pages/EditorPage/EditorPage.tsx`
|
||||
- Periodic update check
|
||||
- Show update notification
|
||||
|
||||
5. `packages/noodl-editor/src/editor/src/utils/projectimporter.js`
|
||||
- Return component IDs after import
|
||||
- Support update (re-import)
|
||||
|
||||
## Implementation Steps
|
||||
|
||||
### Phase 1: Tracking Infrastructure
|
||||
1. Create ComponentTrackingService
|
||||
2. Define metadata schema
|
||||
3. Add to project.json structure
|
||||
4. Implement track/load/save
|
||||
|
||||
### Phase 2: Import Integration
|
||||
1. Hook into installPrefab
|
||||
2. Extract version from manifest
|
||||
3. Track after successful import
|
||||
4. Handle import errors
|
||||
|
||||
### Phase 3: Update Checking
|
||||
1. Implement checkForUpdates
|
||||
2. Compare versions (semver)
|
||||
3. Store update availability
|
||||
4. Background check timer
|
||||
|
||||
### Phase 4: UI - Badges & Indicators
|
||||
1. Create ComponentSourceBadge
|
||||
2. Add to component panel
|
||||
3. Show update indicator
|
||||
4. Add "Check for Updates" button
|
||||
|
||||
### Phase 5: UI - Update Modal
|
||||
1. Create ComponentUpdatesModal
|
||||
2. Show changelog summaries
|
||||
3. Selective update checkboxes
|
||||
4. Implement update action
|
||||
|
||||
### Phase 6: Update Application
|
||||
1. Backup current component
|
||||
2. Re-import from source
|
||||
3. Update metadata
|
||||
4. Handle errors/rollback
|
||||
|
||||
## Testing Checklist
|
||||
|
||||
- [ ] Import tracks source correctly
|
||||
- [ ] Version stored in metadata
|
||||
- [ ] Badge shows in component panel
|
||||
- [ ] Update check finds updates
|
||||
- [ ] Notification appears when updates available
|
||||
- [ ] Update modal lists all updates
|
||||
- [ ] Selective update works
|
||||
- [ ] Update replaces component correctly
|
||||
- [ ] Changelog link works
|
||||
- [ ] Rollback restores previous
|
||||
- [ ] Works with built-in prefabs
|
||||
- [ ] Works with org prefabs
|
||||
- [ ] Legacy imports show "unknown" source
|
||||
- [ ] Offline shows cached status
|
||||
|
||||
## Dependencies
|
||||
|
||||
- COMP-001 (Prefab System Refactoring)
|
||||
- COMP-002 (Built-in Prefabs) - for version tracking
|
||||
- COMP-004 (Organization Components) - for org tracking
|
||||
|
||||
## Blocked By
|
||||
|
||||
- COMP-001
|
||||
- COMP-002
|
||||
|
||||
## Blocks
|
||||
|
||||
- COMP-006 (extends tracking for forking)
|
||||
|
||||
## Estimated Effort
|
||||
|
||||
- Tracking service: 4-5 hours
|
||||
- Import integration: 3-4 hours
|
||||
- Update checking: 3-4 hours
|
||||
- UI badges/indicators: 3-4 hours
|
||||
- Update modal: 3-4 hours
|
||||
- Update application: 3-4 hours
|
||||
- **Total: 19-25 hours**
|
||||
|
||||
## Success Criteria
|
||||
|
||||
1. Imported components track their source
|
||||
2. Version visible in component panel
|
||||
3. Updates detected automatically
|
||||
4. Users notified of available updates
|
||||
5. Selective update works smoothly
|
||||
6. Update preserves project integrity
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
- Auto-update option (for trusted sources)
|
||||
- Diff view before update
|
||||
- Local modification detection
|
||||
- Update scheduling
|
||||
- Update history
|
||||
- Component dependency updates
|
||||
- Breaking change warnings
|
||||
@@ -0,0 +1,498 @@
|
||||
# COMP-006: Component Forking & PR Workflow
|
||||
|
||||
## Overview
|
||||
|
||||
Enable users to fork imported components, make modifications, and contribute changes back to the source repository via pull requests. This creates a collaborative component ecosystem where improvements can flow back to the team or community.
|
||||
|
||||
## Context
|
||||
|
||||
With COMP-005, users can import components and track their source. But when they need to modify a component:
|
||||
- Modifications are local only
|
||||
- No way to share improvements back
|
||||
- No way to propose changes to org components
|
||||
- Forked components lose connection to source
|
||||
|
||||
This task enables:
|
||||
- Fork components with upstream tracking
|
||||
- Local modifications tracked separately
|
||||
- Contribute changes via PR workflow
|
||||
- Merge upstream updates into forked components
|
||||
|
||||
### Forking Flow
|
||||
|
||||
```
|
||||
Import component (COMP-005)
|
||||
↓
|
||||
User modifies component
|
||||
↓
|
||||
System detects local modifications ("forked")
|
||||
↓
|
||||
User can:
|
||||
- Submit PR to upstream
|
||||
- Merge upstream updates into fork
|
||||
- Revert to upstream version
|
||||
```
|
||||
|
||||
## Requirements
|
||||
|
||||
### Functional Requirements
|
||||
|
||||
1. **Fork Detection**
|
||||
- Detect when imported component is modified
|
||||
- Mark as "forked" in tracking metadata
|
||||
- Track original vs modified state
|
||||
- Calculate diff from upstream
|
||||
|
||||
2. **Fork Management**
|
||||
- View fork status in component panel
|
||||
- See what changed from upstream
|
||||
- Option to "unfork" (reset to upstream)
|
||||
- Maintain fork while pulling upstream updates
|
||||
|
||||
3. **PR Creation**
|
||||
- "Contribute Back" action on forked components
|
||||
- Opens PR creation flow
|
||||
- Exports component changes
|
||||
- Creates branch in upstream repo
|
||||
- Opens GitHub PR interface
|
||||
|
||||
4. **Upstream Sync**
|
||||
- Pull upstream changes into fork
|
||||
- Merge or rebase local changes
|
||||
- Conflict detection
|
||||
- Selective merge (choose what to pull)
|
||||
|
||||
5. **Visual Indicators**
|
||||
- "Forked" badge on modified components
|
||||
- "Modified from v2.1.0" indicator
|
||||
- Diff count ("3 changes")
|
||||
- PR status if submitted
|
||||
|
||||
### Non-Functional Requirements
|
||||
|
||||
- Fork detection < 1 second
|
||||
- Diff calculation < 3 seconds
|
||||
- Works with large components (100+ nodes)
|
||||
- No performance impact on editing
|
||||
|
||||
## Technical Approach
|
||||
|
||||
### 1. Fork Tracking Extension
|
||||
|
||||
```typescript
|
||||
// Extension to COMP-005 ImportedComponent
|
||||
interface ImportedComponent {
|
||||
// ... existing fields ...
|
||||
|
||||
// Fork tracking
|
||||
isFork: boolean;
|
||||
forkStatus?: ForkStatus;
|
||||
originalChecksum?: string; // Checksum at import time
|
||||
currentChecksum?: string; // Checksum of current state
|
||||
upstreamVersion?: string; // Latest upstream version
|
||||
|
||||
// PR tracking
|
||||
activePR?: {
|
||||
number: number;
|
||||
url: string;
|
||||
status: 'open' | 'merged' | 'closed';
|
||||
branch: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface ForkStatus {
|
||||
changesCount: number;
|
||||
lastModified: string;
|
||||
canMergeUpstream: boolean;
|
||||
hasConflicts: boolean;
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Fork Detection Service
|
||||
|
||||
```typescript
|
||||
// packages/noodl-editor/src/editor/src/services/ComponentForkService.ts
|
||||
|
||||
class ComponentForkService {
|
||||
private static instance: ComponentForkService;
|
||||
|
||||
// Fork detection
|
||||
async detectForks(): Promise<ForkDetectionResult>;
|
||||
async isComponentForked(componentId: string): Promise<boolean>;
|
||||
async calculateDiff(componentId: string): Promise<ComponentDiff>;
|
||||
|
||||
// Fork management
|
||||
async markAsForked(componentId: string): Promise<void>;
|
||||
async unfork(componentId: string): Promise<void>; // Reset to upstream
|
||||
|
||||
// Upstream sync
|
||||
async canMergeUpstream(componentId: string): Promise<MergeCheck>;
|
||||
async mergeUpstream(componentId: string): Promise<MergeResult>;
|
||||
async previewMerge(componentId: string): Promise<MergePreview>;
|
||||
|
||||
// PR workflow
|
||||
async createContribution(componentId: string): Promise<ContributionResult>;
|
||||
async checkPRStatus(componentId: string): Promise<PRStatus>;
|
||||
|
||||
// Diff/comparison
|
||||
async exportDiff(componentId: string): Promise<ComponentDiff>;
|
||||
async compareWithUpstream(componentId: string): Promise<ComparisonResult>;
|
||||
}
|
||||
|
||||
interface ComponentDiff {
|
||||
componentId: string;
|
||||
changes: Change[];
|
||||
nodesAdded: number;
|
||||
nodesRemoved: number;
|
||||
nodesModified: number;
|
||||
propertiesChanged: number;
|
||||
}
|
||||
|
||||
interface Change {
|
||||
type: 'added' | 'removed' | 'modified';
|
||||
path: string; // Path in component tree
|
||||
description: string;
|
||||
before?: any;
|
||||
after?: any;
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Checksum Calculation
|
||||
|
||||
```typescript
|
||||
// Calculate stable checksum for component state
|
||||
function calculateComponentChecksum(component: ComponentModel): string {
|
||||
// Serialize component in stable order
|
||||
const serialized = stableSerialize({
|
||||
nodes: component.nodes.map(serializeNode),
|
||||
connections: component.connections.map(serializeConnection),
|
||||
properties: component.properties,
|
||||
// Exclude metadata that changes (ids, timestamps)
|
||||
});
|
||||
|
||||
return crypto.createHash('sha256').update(serialized).digest('hex');
|
||||
}
|
||||
|
||||
// Detect if component was modified
|
||||
async function detectModification(componentId: string): Promise<boolean> {
|
||||
const metadata = ComponentTrackingService.instance.getComponentSource(componentId);
|
||||
if (!metadata?.originalChecksum) return false;
|
||||
|
||||
const component = ProjectModel.instance.getComponentWithId(componentId);
|
||||
const currentChecksum = calculateComponentChecksum(component);
|
||||
|
||||
return currentChecksum !== metadata.originalChecksum;
|
||||
}
|
||||
```
|
||||
|
||||
### 4. PR Creation Flow
|
||||
|
||||
```typescript
|
||||
async createContribution(componentId: string): Promise<ContributionResult> {
|
||||
const tracking = ComponentTrackingService.instance;
|
||||
const metadata = tracking.getComponentSource(componentId);
|
||||
|
||||
if (!metadata?.source.repository) {
|
||||
throw new Error('Cannot contribute: no upstream repository');
|
||||
}
|
||||
|
||||
// 1. Export modified component
|
||||
const component = ProjectModel.instance.getComponentWithId(componentId);
|
||||
const exportedFiles = await exportComponent(component);
|
||||
|
||||
// 2. Create branch in upstream repo
|
||||
const branchName = `component-update/${metadata.componentName}-${Date.now()}`;
|
||||
const github = GitHubApiClient.instance;
|
||||
|
||||
await github.createBranch(
|
||||
metadata.source.repository,
|
||||
branchName,
|
||||
'main'
|
||||
);
|
||||
|
||||
// 3. Commit changes to branch
|
||||
await github.commitFiles(
|
||||
metadata.source.repository,
|
||||
branchName,
|
||||
exportedFiles,
|
||||
`Update ${metadata.componentName} component`
|
||||
);
|
||||
|
||||
// 4. Create PR
|
||||
const pr = await github.createPullRequest(
|
||||
metadata.source.repository,
|
||||
{
|
||||
title: `Update ${metadata.componentName} component`,
|
||||
body: generatePRDescription(metadata, exportedFiles),
|
||||
head: branchName,
|
||||
base: 'main'
|
||||
}
|
||||
);
|
||||
|
||||
// 5. Track PR in metadata
|
||||
metadata.activePR = {
|
||||
number: pr.number,
|
||||
url: pr.html_url,
|
||||
status: 'open',
|
||||
branch: branchName
|
||||
};
|
||||
await tracking.saveMetadata();
|
||||
|
||||
return {
|
||||
success: true,
|
||||
prUrl: pr.html_url,
|
||||
prNumber: pr.number
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### 5. UI Components
|
||||
|
||||
#### Fork Badge in Component Panel
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ Components │
|
||||
├─────────────────────────────────────────────────────────────────────┤
|
||||
│ ├── AcmeButton [🏢 v2.1.0] [🔀 Forked +3] │
|
||||
│ │ ├── Right-click options: │
|
||||
│ │ │ • View Changes from Upstream │
|
||||
│ │ │ • Merge Upstream Changes │
|
||||
│ │ │ • Contribute Changes (Create PR) │
|
||||
│ │ │ • Reset to Upstream │
|
||||
│ │ │ ────────────────────── │
|
||||
│ │ │ • PR #42 Open ↗ │
|
||||
│ │ └── │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
#### Diff View Modal
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ Changes in AcmeButton [×] │
|
||||
│ Forked from v2.1.0 │
|
||||
├─────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ Summary: 3 nodes modified, 1 added, 0 removed │
|
||||
│ │
|
||||
│ CHANGES │
|
||||
│ ┌─────────────────────────────────────────────────────────────────┐ │
|
||||
│ │ + Added: LoadingSpinner node │ │
|
||||
│ │ └─ Displays while button action is processing │ │
|
||||
│ ├─────────────────────────────────────────────────────────────────┤ │
|
||||
│ │ ~ Modified: Button/backgroundColor │ │
|
||||
│ │ └─ #3B82F6 → #2563EB (darker blue) │ │
|
||||
│ ├─────────────────────────────────────────────────────────────────┤ │
|
||||
│ │ ~ Modified: Button/borderRadius │ │
|
||||
│ │ └─ 4px → 8px │ │
|
||||
│ ├─────────────────────────────────────────────────────────────────┤ │
|
||||
│ │ ~ Modified: HoverState/scale │ │
|
||||
│ │ └─ 1.02 → 1.05 │ │
|
||||
│ └─────────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ [Reset to Upstream] [Contribute Changes] [Close] │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
#### PR Creation Modal
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ Contribute Changes [×] │
|
||||
├─────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ You're about to create a Pull Request to: │
|
||||
│ 🏢 acme-corp/noodl-components │
|
||||
│ │
|
||||
│ Component: AcmeButton │
|
||||
│ Changes: 3 modifications, 1 addition │
|
||||
│ │
|
||||
│ PR Title: │
|
||||
│ ┌─────────────────────────────────────────────────────────────────┐ │
|
||||
│ │ Update AcmeButton: add loading state, adjust styling │ │
|
||||
│ └─────────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ Description: │
|
||||
│ ┌─────────────────────────────────────────────────────────────────┐ │
|
||||
│ │ This PR updates the AcmeButton component with: │ │
|
||||
│ │ - Added loading spinner during async actions │ │
|
||||
│ │ - Darker blue for better contrast │ │
|
||||
│ │ - Larger border radius for modern look │ │
|
||||
│ │ - More pronounced hover effect │ │
|
||||
│ └─────────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ☑ Open PR in browser after creation │
|
||||
│ │
|
||||
│ [Cancel] [Create PR] │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 6. Upstream Merge Flow
|
||||
|
||||
```typescript
|
||||
async mergeUpstream(componentId: string): Promise<MergeResult> {
|
||||
const tracking = ComponentTrackingService.instance;
|
||||
const metadata = tracking.getComponentSource(componentId);
|
||||
|
||||
// 1. Get upstream version
|
||||
const source = PrefabRegistry.instance.getSource(metadata.source.repository);
|
||||
const upstreamPath = await source.downloadPrefab(metadata.source.prefabId);
|
||||
|
||||
// 2. Get current component
|
||||
const currentComponent = ProjectModel.instance.getComponentWithId(componentId);
|
||||
|
||||
// 3. Get original version (at import time)
|
||||
const originalPath = await this.getOriginalVersion(componentId);
|
||||
|
||||
// 4. Three-way merge
|
||||
const mergeResult = await mergeComponents(
|
||||
originalPath, // Base
|
||||
upstreamPath, // Theirs (upstream)
|
||||
currentComponent // Ours (local modifications)
|
||||
);
|
||||
|
||||
if (mergeResult.hasConflicts) {
|
||||
// Show conflict resolution UI
|
||||
return { success: false, conflicts: mergeResult.conflicts };
|
||||
}
|
||||
|
||||
// 5. Apply merged result
|
||||
await applyMergedComponent(componentId, mergeResult.merged);
|
||||
|
||||
// 6. Update metadata
|
||||
metadata.importedVersion = upstreamVersion;
|
||||
metadata.originalChecksum = calculateChecksum(mergeResult.merged);
|
||||
await tracking.saveMetadata();
|
||||
|
||||
return { success: true };
|
||||
}
|
||||
```
|
||||
|
||||
## Files to Create
|
||||
|
||||
1. `packages/noodl-editor/src/editor/src/services/ComponentForkService.ts`
|
||||
2. `packages/noodl-editor/src/editor/src/utils/componentChecksum.ts`
|
||||
3. `packages/noodl-editor/src/editor/src/utils/componentMerge.ts`
|
||||
4. `packages/noodl-core-ui/src/components/modals/ComponentDiffModal/ComponentDiffModal.tsx`
|
||||
5. `packages/noodl-core-ui/src/components/modals/CreatePRModal/CreatePRModal.tsx`
|
||||
6. `packages/noodl-core-ui/src/components/modals/MergeUpstreamModal/MergeUpstreamModal.tsx`
|
||||
7. `packages/noodl-core-ui/src/components/common/ForkBadge/ForkBadge.tsx`
|
||||
|
||||
## Files to Modify
|
||||
|
||||
1. `packages/noodl-editor/src/editor/src/services/ComponentTrackingService.ts`
|
||||
- Add fork tracking fields
|
||||
- Add checksum calculation
|
||||
- Integration with ForkService
|
||||
|
||||
2. `packages/noodl-editor/src/editor/src/views/panels/componentspanel.tsx`
|
||||
- Add fork badge
|
||||
- Add fork-related context menu items
|
||||
|
||||
3. `packages/noodl-editor/src/editor/src/services/GitHubApiClient.ts`
|
||||
- Add branch creation
|
||||
- Add file commit
|
||||
- Add PR creation
|
||||
|
||||
4. `packages/noodl-editor/src/editor/src/models/projectmodel.ts`
|
||||
- Hook component save to detect modifications
|
||||
|
||||
## Implementation Steps
|
||||
|
||||
### Phase 1: Fork Detection
|
||||
1. Implement checksum calculation
|
||||
2. Store original checksum on import
|
||||
3. Detect modifications on component save
|
||||
4. Mark forked components
|
||||
|
||||
### Phase 2: Diff Calculation
|
||||
1. Implement component diff algorithm
|
||||
2. Create human-readable change descriptions
|
||||
3. Calculate change counts
|
||||
|
||||
### Phase 3: UI - Fork Indicators
|
||||
1. Create ForkBadge component
|
||||
2. Add to component panel
|
||||
3. Add context menu items
|
||||
4. Show fork status
|
||||
|
||||
### Phase 4: UI - Diff View
|
||||
1. Create ComponentDiffModal
|
||||
2. Show changes list
|
||||
3. Add action buttons
|
||||
|
||||
### Phase 5: PR Workflow
|
||||
1. Implement branch creation
|
||||
2. Implement file commit
|
||||
3. Implement PR creation
|
||||
4. Create CreatePRModal
|
||||
|
||||
### Phase 6: Upstream Merge
|
||||
1. Implement three-way merge
|
||||
2. Create MergeUpstreamModal
|
||||
3. Handle conflicts
|
||||
4. Update metadata after merge
|
||||
|
||||
## Testing Checklist
|
||||
|
||||
- [ ] Modification detected correctly
|
||||
- [ ] Fork badge appears
|
||||
- [ ] Diff calculated accurately
|
||||
- [ ] Diff modal shows changes
|
||||
- [ ] PR creation works
|
||||
- [ ] PR opens in browser
|
||||
- [ ] PR status tracked
|
||||
- [ ] Upstream merge works (no conflicts)
|
||||
- [ ] Conflict detection works
|
||||
- [ ] Reset to upstream works
|
||||
- [ ] Multiple forks tracked
|
||||
- [ ] Works with org repos
|
||||
- [ ] Works with personal repos
|
||||
- [ ] Checksum stable across saves
|
||||
|
||||
## Dependencies
|
||||
|
||||
- COMP-003 (Component Export)
|
||||
- COMP-004 (Organization Components)
|
||||
- COMP-005 (Component Import Version Control)
|
||||
- GIT-001 (GitHub OAuth)
|
||||
|
||||
## Blocked By
|
||||
|
||||
- COMP-005
|
||||
|
||||
## Blocks
|
||||
|
||||
- None (final task in COMP series)
|
||||
|
||||
## Estimated Effort
|
||||
|
||||
- Fork detection & checksum: 4-5 hours
|
||||
- Diff calculation: 4-5 hours
|
||||
- Fork UI (badges, menus): 3-4 hours
|
||||
- Diff view modal: 3-4 hours
|
||||
- PR workflow: 5-6 hours
|
||||
- Upstream merge: 5-6 hours
|
||||
- Testing & polish: 4-5 hours
|
||||
- **Total: 28-35 hours**
|
||||
|
||||
## Success Criteria
|
||||
|
||||
1. Modified components detected as forks
|
||||
2. Fork badge visible in UI
|
||||
3. Diff view shows changes clearly
|
||||
4. PR creation works end-to-end
|
||||
5. PR status tracked
|
||||
6. Upstream merge works smoothly
|
||||
7. Conflict handling is clear
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
- Visual diff editor
|
||||
- Partial contribution (select changes for PR)
|
||||
- Auto-update after PR merged
|
||||
- Fork from fork (nested forks)
|
||||
- Component version branches
|
||||
- Conflict resolution UI
|
||||
- PR review integration
|
||||
@@ -0,0 +1,339 @@
|
||||
# COMP Series: Shared Component System
|
||||
|
||||
## Overview
|
||||
|
||||
The COMP series transforms Noodl's component sharing from manual zip file exchanges into a modern, Git-based collaborative ecosystem. Teams can share design systems via organization repositories, individuals can build personal component libraries, and improvements can flow back upstream via pull requests.
|
||||
|
||||
## Target Environment
|
||||
|
||||
- **Editor**: React 19 version only
|
||||
- **Runtime**: Not affected (components work in any runtime)
|
||||
- **Backwards Compatibility**: Existing prefabs continue to work
|
||||
|
||||
## Task Dependency Graph
|
||||
|
||||
```
|
||||
COMP-001 (Prefab System Refactoring)
|
||||
│
|
||||
├────────────────────────┬───────────────────────┐
|
||||
↓ ↓ ↓
|
||||
COMP-002 (Built-in) COMP-003 (Export) GIT-001 (OAuth)
|
||||
│ │ │
|
||||
↓ ↓ │
|
||||
│ COMP-004 (Org Components) ←───┘
|
||||
│ │
|
||||
└────────────┬───────────┘
|
||||
↓
|
||||
COMP-005 (Version Control)
|
||||
│
|
||||
↓
|
||||
COMP-006 (Forking & PR)
|
||||
```
|
||||
|
||||
## Task Summary
|
||||
|
||||
| Task ID | Name | Est. Hours | Priority |
|
||||
|---------|------|------------|----------|
|
||||
| COMP-001 | Prefab System Refactoring | 14-20 | Critical |
|
||||
| COMP-002 | Built-in Prefabs | 19-26 | High |
|
||||
| COMP-003 | Component Export to Repository | 19-25 | High |
|
||||
| COMP-004 | Organization Components Repository | 18-24 | High |
|
||||
| COMP-005 | Component Import with Version Control | 19-25 | Medium |
|
||||
| COMP-006 | Component Forking & PR Workflow | 28-35 | Medium |
|
||||
|
||||
**Total Estimated: 117-155 hours**
|
||||
|
||||
## Implementation Order
|
||||
|
||||
### Phase 1: Foundation (Weeks 1-2)
|
||||
1. **COMP-001** - Refactor prefab system for multiple sources
|
||||
|
||||
### Phase 2: Local & Built-in (Weeks 3-4)
|
||||
2. **COMP-002** - Bundle essential prefabs with editor
|
||||
|
||||
### Phase 3: Export & Organization (Weeks 5-7)
|
||||
3. **COMP-003** - Enable exporting to GitHub repositories
|
||||
4. **COMP-004** - Auto-detect and load organization repos
|
||||
|
||||
### Phase 4: Version Control & Collaboration (Weeks 8-10)
|
||||
5. **COMP-005** - Track imports, detect updates
|
||||
6. **COMP-006** - Fork detection, PR workflow
|
||||
|
||||
## Existing Infrastructure
|
||||
|
||||
### ModuleLibraryModel
|
||||
|
||||
```typescript
|
||||
// Current implementation
|
||||
class ModuleLibraryModel {
|
||||
modules: IModule[]; // External libraries
|
||||
prefabs: IModule[]; // Component bundles
|
||||
|
||||
fetchModules(type: 'modules' | 'prefabs'): Promise<IModule[]>;
|
||||
installModule(path: string): Promise<void>;
|
||||
installPrefab(path: string): Promise<void>;
|
||||
}
|
||||
```
|
||||
|
||||
### ProjectImporter
|
||||
|
||||
```typescript
|
||||
// Handles actual component import
|
||||
class ProjectImporter {
|
||||
listComponentsAndDependencies(dir, callback);
|
||||
checkForCollisions(imports, callback);
|
||||
import(dir, imports, callback);
|
||||
}
|
||||
```
|
||||
|
||||
### NodePicker
|
||||
|
||||
```
|
||||
packages/noodl-editor/src/editor/src/views/NodePicker/
|
||||
├── NodePicker.tsx # Main component
|
||||
├── NodePicker.context.tsx # State management
|
||||
├── tabs/
|
||||
│ ├── NodeLibrary/ # Built-in nodes
|
||||
│ ├── NodePickerSearchView/ # Prefabs & modules
|
||||
│ └── ImportFromProject/ # Import from other project
|
||||
└── components/
|
||||
└── ModuleCard/ # Prefab/module display card
|
||||
```
|
||||
|
||||
### Export Functionality
|
||||
|
||||
```typescript
|
||||
// exportProjectComponents.ts
|
||||
export function exportProjectComponents() {
|
||||
// Shows export popup
|
||||
// User selects components
|
||||
// Creates zip file with dependencies
|
||||
}
|
||||
```
|
||||
|
||||
## New Architecture
|
||||
|
||||
### PrefabRegistry (COMP-001)
|
||||
|
||||
Central hub for all prefab sources:
|
||||
|
||||
```typescript
|
||||
class PrefabRegistry {
|
||||
private sources: Map<string, PrefabSource>;
|
||||
|
||||
registerSource(source: PrefabSource): void;
|
||||
getAllPrefabs(): Promise<PrefabMetadata[]>;
|
||||
installPrefab(id: string): Promise<void>;
|
||||
}
|
||||
```
|
||||
|
||||
### Source Types
|
||||
|
||||
| Source | Priority | Description |
|
||||
|--------|----------|-------------|
|
||||
| BuiltInPrefabSource | 100 | Bundled with editor |
|
||||
| OrganizationPrefabSource | 80 | Team component repos |
|
||||
| PersonalPrefabSource | 70 | User's own repos |
|
||||
| DocsPrefabSource | 50 | Community prefabs |
|
||||
|
||||
### Component Tracking (COMP-005)
|
||||
|
||||
```typescript
|
||||
interface ImportedComponent {
|
||||
componentId: string;
|
||||
source: ComponentSource;
|
||||
importedVersion: string;
|
||||
isFork: boolean;
|
||||
updateAvailable?: string;
|
||||
activePR?: PRInfo;
|
||||
}
|
||||
```
|
||||
|
||||
## Repository Structure Convention
|
||||
|
||||
All component repositories follow this structure:
|
||||
|
||||
```
|
||||
noodl-components/
|
||||
├── index.json # Repository manifest
|
||||
├── README.md # Documentation
|
||||
├── LICENSE # License file
|
||||
└── components/
|
||||
├── component-name/
|
||||
│ ├── prefab.json # Component metadata
|
||||
│ ├── component.ndjson # Component data
|
||||
│ ├── dependencies/ # Styles, variants
|
||||
│ └── assets/ # Images, fonts
|
||||
└── ...
|
||||
```
|
||||
|
||||
### Manifest Format (index.json)
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "Acme Design System",
|
||||
"version": "2.1.0",
|
||||
"noodlVersion": ">=2.10.0",
|
||||
"components": [
|
||||
{
|
||||
"id": "acme-button",
|
||||
"name": "Acme Button",
|
||||
"version": "2.1.0",
|
||||
"path": "components/acme-button"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Key User Flows
|
||||
|
||||
### 1. Team Member Imports Org Component
|
||||
|
||||
```
|
||||
User opens NodePicker
|
||||
↓
|
||||
Sees "Acme Corp" section with org components
|
||||
↓
|
||||
Clicks "Clone" on AcmeButton
|
||||
↓
|
||||
Component imported, source tracked
|
||||
↓
|
||||
Later: notification "AcmeButton update available"
|
||||
```
|
||||
|
||||
### 2. Developer Shares Component
|
||||
|
||||
```
|
||||
User right-clicks component
|
||||
↓
|
||||
Selects "Export to Repository"
|
||||
↓
|
||||
Chooses personal repo or org repo
|
||||
↓
|
||||
Fills metadata (description, tags)
|
||||
↓
|
||||
Component committed and pushed
|
||||
```
|
||||
|
||||
### 3. Developer Improves Org Component
|
||||
|
||||
```
|
||||
User modifies imported AcmeButton
|
||||
↓
|
||||
System detects fork, shows badge
|
||||
↓
|
||||
User right-clicks → "Contribute Changes"
|
||||
↓
|
||||
PR created in org repo
|
||||
↓
|
||||
Team reviews and merges
|
||||
```
|
||||
|
||||
## Services to Create
|
||||
|
||||
| Service | Purpose |
|
||||
|---------|---------|
|
||||
| PrefabRegistry | Central source management |
|
||||
| ComponentTrackingService | Import/version tracking |
|
||||
| ComponentExportService | Export to repositories |
|
||||
| OrganizationService | Org detection & management |
|
||||
| ComponentForkService | Fork detection & PR workflow |
|
||||
|
||||
## UI Components to Create
|
||||
|
||||
| Component | Location | Purpose |
|
||||
|-----------|----------|---------|
|
||||
| ComponentSourceBadge | noodl-core-ui | Show source (Built-in, Org, etc.) |
|
||||
| ForkBadge | noodl-core-ui | Show fork status |
|
||||
| ExportToRepoModal | noodl-core-ui | Export workflow |
|
||||
| ComponentUpdatesModal | noodl-core-ui | Update selection |
|
||||
| ComponentDiffModal | noodl-core-ui | View changes |
|
||||
| CreatePRModal | noodl-core-ui | PR creation |
|
||||
| OrganizationSettings | noodl-core-ui | Org repo settings |
|
||||
|
||||
## Dependencies on Other Series
|
||||
|
||||
### Required from GIT Series
|
||||
- GIT-001 (GitHub OAuth) - Required for COMP-003, COMP-004
|
||||
|
||||
### Enables for Future
|
||||
- Community marketplace
|
||||
- Component ratings/reviews
|
||||
- Usage analytics
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
### Unit Tests
|
||||
- Source registration
|
||||
- Metadata parsing
|
||||
- Checksum calculation
|
||||
- Version comparison
|
||||
|
||||
### Integration Tests
|
||||
- Full import flow
|
||||
- Export to repo flow
|
||||
- Update detection
|
||||
- PR creation
|
||||
|
||||
### Manual Testing
|
||||
- Multiple organizations
|
||||
- Large component libraries
|
||||
- Offline scenarios
|
||||
- Permission edge cases
|
||||
|
||||
## Cline Usage Notes
|
||||
|
||||
### Before Starting Each Task
|
||||
|
||||
1. Read task document completely
|
||||
2. Review existing prefab system:
|
||||
- `modulelibrarymodel.ts`
|
||||
- `projectimporter.js`
|
||||
- `NodePicker/` views
|
||||
3. Understand export flow:
|
||||
- `exportProjectComponents.ts`
|
||||
|
||||
### Key Gotchas
|
||||
|
||||
1. **Singleton Pattern**: `ModuleLibraryModel.instance` is used everywhere
|
||||
2. **Async Import**: Import process is callback-based, not Promise
|
||||
3. **Collision Handling**: Existing collision detection must be preserved
|
||||
4. **File Paths**: Components use relative paths internally
|
||||
|
||||
### Testing Prefabs
|
||||
|
||||
```bash
|
||||
# Run editor tests
|
||||
npm run test:editor
|
||||
|
||||
# Manual: Open NodePicker, try importing prefab
|
||||
```
|
||||
|
||||
## Success Criteria (Series Complete)
|
||||
|
||||
1. ✅ Multiple prefab sources supported
|
||||
2. ✅ Built-in prefabs available offline
|
||||
3. ✅ Components exportable to GitHub
|
||||
4. ✅ Organization repos auto-detected
|
||||
5. ✅ Import source/version tracked
|
||||
6. ✅ Updates detected and installable
|
||||
7. ✅ Forks can create PRs upstream
|
||||
|
||||
## Future Work (Post-COMP)
|
||||
|
||||
The COMP series enables:
|
||||
- **Marketplace**: Paid/free component marketplace
|
||||
- **Analytics**: Usage tracking per component
|
||||
- **Ratings**: Community ratings and reviews
|
||||
- **Templates**: Project templates from components
|
||||
- **Subscriptions**: Organization component subscriptions
|
||||
|
||||
## Files in This Series
|
||||
|
||||
- `COMP-001-prefab-system-refactoring.md`
|
||||
- `COMP-002-builtin-prefabs.md`
|
||||
- `COMP-003-component-export.md`
|
||||
- `COMP-004-organization-components.md`
|
||||
- `COMP-005-component-import-version-control.md`
|
||||
- `COMP-006-forking-pr-workflow.md`
|
||||
- `COMP-OVERVIEW.md` (this file)
|
||||
@@ -0,0 +1,481 @@
|
||||
# AI-001: AI Project Scaffolding
|
||||
|
||||
## Overview
|
||||
|
||||
Enable users to describe their project idea in natural language and have AI generate a complete project scaffold with pages, components, data models, and basic styling. This transforms the "blank canvas" experience into an intelligent starting point.
|
||||
|
||||
## Context
|
||||
|
||||
Currently, project creation offers:
|
||||
- Blank "Hello World" template
|
||||
- Pre-built template gallery (limited selection)
|
||||
- Manual component-by-component building
|
||||
|
||||
New users face a steep learning curve:
|
||||
- Don't know where to start
|
||||
- Overwhelmed by node options
|
||||
- No guidance on structure
|
||||
|
||||
AI scaffolding provides:
|
||||
- Describe idea → Get working structure
|
||||
- Industry best practices baked in
|
||||
- Learning through example
|
||||
- Faster time-to-prototype
|
||||
|
||||
### Existing Infrastructure
|
||||
|
||||
From `AiAssistantModel.ts`:
|
||||
```typescript
|
||||
// Existing AI templates
|
||||
docsTemplates = [
|
||||
{ label: 'REST API', template: 'rest' },
|
||||
{ label: 'Form Validation', template: 'function-form-validation' },
|
||||
{ label: 'AI Function', template: 'function' },
|
||||
// ...
|
||||
]
|
||||
```
|
||||
|
||||
From `TemplateRegistry`:
|
||||
```typescript
|
||||
// Download and extract project templates
|
||||
templateRegistry.download({ templateUrl }) → zipPath
|
||||
```
|
||||
|
||||
From `LocalProjectsModel`:
|
||||
```typescript
|
||||
// Create new project from template
|
||||
newProject(callback, { name, path, projectTemplate })
|
||||
```
|
||||
|
||||
## Requirements
|
||||
|
||||
### Functional Requirements
|
||||
|
||||
1. **Natural Language Input**
|
||||
- Free-form text description of project
|
||||
- Example prompts for inspiration
|
||||
- Clarifying questions from AI
|
||||
- Refinement through conversation
|
||||
|
||||
2. **Project Analysis**
|
||||
- Identify project type (app, dashboard, form, etc.)
|
||||
- Extract features and functionality
|
||||
- Determine data models needed
|
||||
- Suggest appropriate structure
|
||||
|
||||
3. **Scaffold Generation**
|
||||
- Create page structure
|
||||
- Generate component hierarchy
|
||||
- Set up navigation flow
|
||||
- Create placeholder data models
|
||||
- Apply appropriate styling
|
||||
|
||||
4. **Preview & Refinement**
|
||||
- Preview generated structure before creation
|
||||
- Modify/refine via chat
|
||||
- Accept or regenerate parts
|
||||
- Explain what was generated
|
||||
|
||||
5. **Project Creation**
|
||||
- Create actual Noodl project
|
||||
- Import generated components
|
||||
- Set up routing/navigation
|
||||
- Open in editor
|
||||
|
||||
### Non-Functional Requirements
|
||||
|
||||
- Generation completes in < 30 seconds
|
||||
- Works with Claude API (Anthropic)
|
||||
- Graceful handling of API errors
|
||||
- Clear progress indication
|
||||
- Cost-effective token usage
|
||||
|
||||
## Technical Approach
|
||||
|
||||
### 1. AI Scaffolding Service
|
||||
|
||||
```typescript
|
||||
// packages/noodl-editor/src/editor/src/services/AiScaffoldingService.ts
|
||||
|
||||
interface ProjectDescription {
|
||||
rawText: string;
|
||||
clarifications?: Record<string, string>;
|
||||
}
|
||||
|
||||
interface ScaffoldResult {
|
||||
projectType: ProjectType;
|
||||
pages: PageDefinition[];
|
||||
components: ComponentDefinition[];
|
||||
dataModels: DataModelDefinition[];
|
||||
navigation: NavigationDefinition;
|
||||
styling: StylingDefinition;
|
||||
explanation: string;
|
||||
}
|
||||
|
||||
interface PageDefinition {
|
||||
name: string;
|
||||
route: string;
|
||||
description: string;
|
||||
components: string[]; // Component names used
|
||||
layout: 'stack' | 'grid' | 'sidebar' | 'tabs';
|
||||
}
|
||||
|
||||
interface ComponentDefinition {
|
||||
name: string;
|
||||
type: 'visual' | 'logic' | 'data';
|
||||
description: string;
|
||||
inputs: PortDefinition[];
|
||||
outputs: PortDefinition[];
|
||||
children?: ComponentDefinition[];
|
||||
prefab?: string; // Use existing prefab if available
|
||||
}
|
||||
|
||||
class AiScaffoldingService {
|
||||
private static instance: AiScaffoldingService;
|
||||
|
||||
// Main flow
|
||||
async analyzeDescription(description: string): Promise<AnalysisResult>;
|
||||
async generateScaffold(description: ProjectDescription): Promise<ScaffoldResult>;
|
||||
async refineScaffold(scaffold: ScaffoldResult, feedback: string): Promise<ScaffoldResult>;
|
||||
|
||||
// Project creation
|
||||
async createProject(scaffold: ScaffoldResult, name: string, path: string): Promise<ProjectModel>;
|
||||
|
||||
// Conversation
|
||||
async askClarification(description: string): Promise<ClarificationQuestion[]>;
|
||||
async chat(messages: ChatMessage[]): Promise<ChatResponse>;
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Prompt Engineering
|
||||
|
||||
```typescript
|
||||
// packages/noodl-editor/src/editor/src/services/ai/prompts/scaffolding.ts
|
||||
|
||||
const SYSTEM_PROMPT = `You are an expert Noodl application architect.
|
||||
Your task is to analyze project descriptions and generate detailed scaffolds
|
||||
for visual low-code applications.
|
||||
|
||||
Noodl is a visual programming platform with:
|
||||
- Pages (screens/routes)
|
||||
- Components (reusable UI elements)
|
||||
- Nodes (visual programming blocks)
|
||||
- Data models (objects, arrays, variables)
|
||||
- Logic nodes (conditions, loops, functions)
|
||||
|
||||
When generating scaffolds, consider:
|
||||
1. User experience and navigation flow
|
||||
2. Data management and state
|
||||
3. Reusability of components
|
||||
4. Mobile-first responsive design
|
||||
5. Performance and loading states
|
||||
|
||||
Output JSON following the ScaffoldResult schema.`;
|
||||
|
||||
const ANALYSIS_PROMPT = `Analyze this project description and identify:
|
||||
1. Project type (app, dashboard, form, e-commerce, etc.)
|
||||
2. Main features/functionality
|
||||
3. User roles/personas
|
||||
4. Data entities needed
|
||||
5. Key user flows
|
||||
6. Potential complexity areas
|
||||
|
||||
Description: {description}`;
|
||||
|
||||
const SCAFFOLD_PROMPT = `Generate a complete Noodl project scaffold for:
|
||||
|
||||
Project Type: {projectType}
|
||||
Features: {features}
|
||||
Data Models: {dataModels}
|
||||
|
||||
Create:
|
||||
1. Page structure with routes
|
||||
2. Component hierarchy
|
||||
3. Navigation flow
|
||||
4. Data model definitions
|
||||
5. Styling theme
|
||||
|
||||
Use these available prefabs when appropriate:
|
||||
{availablePrefabs}`;
|
||||
```
|
||||
|
||||
### 3. Scaffold to Project Converter
|
||||
|
||||
```typescript
|
||||
// packages/noodl-editor/src/editor/src/services/ai/ScaffoldConverter.ts
|
||||
|
||||
class ScaffoldConverter {
|
||||
// Convert scaffold definitions to actual Noodl components
|
||||
async convertToProject(scaffold: ScaffoldResult): Promise<ProjectFiles> {
|
||||
const project = new ProjectModel();
|
||||
|
||||
// Create pages
|
||||
for (const page of scaffold.pages) {
|
||||
const pageComponent = await this.createPage(page);
|
||||
project.addComponent(pageComponent);
|
||||
}
|
||||
|
||||
// Create reusable components
|
||||
for (const component of scaffold.components) {
|
||||
const comp = await this.createComponent(component);
|
||||
project.addComponent(comp);
|
||||
}
|
||||
|
||||
// Set up navigation
|
||||
await this.setupNavigation(project, scaffold.navigation);
|
||||
|
||||
// Apply styling
|
||||
await this.applyStyles(project, scaffold.styling);
|
||||
|
||||
return project;
|
||||
}
|
||||
|
||||
private async createPage(page: PageDefinition): Promise<ComponentModel> {
|
||||
// Create component with page layout
|
||||
const component = ComponentModel.create({
|
||||
name: page.name,
|
||||
type: 'page'
|
||||
});
|
||||
|
||||
// Add layout container based on page.layout
|
||||
const layout = this.createLayout(page.layout);
|
||||
component.addChild(layout);
|
||||
|
||||
// Add referenced components
|
||||
for (const compName of page.components) {
|
||||
const ref = this.createComponentReference(compName);
|
||||
layout.addChild(ref);
|
||||
}
|
||||
|
||||
return component;
|
||||
}
|
||||
|
||||
private async createComponent(def: ComponentDefinition): Promise<ComponentModel> {
|
||||
// Check if we can use a prefab
|
||||
if (def.prefab) {
|
||||
return await this.importPrefab(def.prefab, def);
|
||||
}
|
||||
|
||||
// Create custom component
|
||||
const component = ComponentModel.create({
|
||||
name: def.name,
|
||||
type: def.type
|
||||
});
|
||||
|
||||
// Add ports
|
||||
for (const input of def.inputs) {
|
||||
component.addInput(input);
|
||||
}
|
||||
for (const output of def.outputs) {
|
||||
component.addOutput(output);
|
||||
}
|
||||
|
||||
// Add children
|
||||
if (def.children) {
|
||||
for (const child of def.children) {
|
||||
const childComp = await this.createComponent(child);
|
||||
component.addChild(childComp);
|
||||
}
|
||||
}
|
||||
|
||||
return component;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. UI Flow
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ Create New Project [×] │
|
||||
├─────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ○ Start from scratch │
|
||||
│ ○ Use a template │
|
||||
│ ● Describe your project (AI) │
|
||||
│ │
|
||||
│ ┌─────────────────────────────────────────────────────────────────┐ │
|
||||
│ │ Describe what you want to build... │ │
|
||||
│ │ │ │
|
||||
│ │ I want to build a task management app where users can create │ │
|
||||
│ │ projects, add tasks with due dates, and track progress with │ │
|
||||
│ │ a kanban board view. │ │
|
||||
│ │ │ │
|
||||
│ └─────────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ 💡 Examples: │
|
||||
│ • "A recipe app with categories and favorites" │
|
||||
│ • "An e-commerce dashboard with sales charts" │
|
||||
│ • "A booking system for a salon" │
|
||||
│ │
|
||||
│ [Cancel] [Generate Project] │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 5. Preview & Refinement
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ Project Preview [×] │
|
||||
├─────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ STRUCTURE │ CHAT │
|
||||
│ ┌─────────────────────────────────┐ │ ┌─────────────────────────────┐│
|
||||
│ │ 📁 Pages │ │ │ 🤖 I've created a task ││
|
||||
│ │ 📄 HomePage │ │ │ management app with: ││
|
||||
│ │ 📄 ProjectsPage │ │ │ ││
|
||||
│ │ 📄 KanbanBoard │ │ │ • 4 pages for navigation ││
|
||||
│ │ 📄 TaskDetail │ │ │ • Kanban board component ││
|
||||
│ │ │ │ │ • Task and Project models ││
|
||||
│ │ 📁 Components │ │ │ • Drag-and-drop ready ││
|
||||
│ │ 🧩 TaskCard │ │ │ ││
|
||||
│ │ 🧩 KanbanColumn │ │ │ Want me to add anything? ││
|
||||
│ │ 🧩 ProjectCard │ │ ├─────────────────────────────┤│
|
||||
│ │ 🧩 NavBar │ │ │ Add a calendar view too ││
|
||||
│ │ │ │ │ [Send]││
|
||||
│ │ 📁 Data Models │ │ └─────────────────────────────┘│
|
||||
│ │ 📊 Task │ │ │
|
||||
│ │ 📊 Project │ │ │
|
||||
│ │ 📊 User │ │ │
|
||||
│ └─────────────────────────────────┘ │ │
|
||||
│ │
|
||||
│ [Regenerate] [Edit Manually] [Create] │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Files to Create
|
||||
|
||||
1. `packages/noodl-editor/src/editor/src/services/AiScaffoldingService.ts`
|
||||
2. `packages/noodl-editor/src/editor/src/services/ai/prompts/scaffolding.ts`
|
||||
3. `packages/noodl-editor/src/editor/src/services/ai/ScaffoldConverter.ts`
|
||||
4. `packages/noodl-editor/src/editor/src/services/ai/AnthropicClient.ts`
|
||||
5. `packages/noodl-core-ui/src/components/modals/AiProjectModal/AiProjectModal.tsx`
|
||||
6. `packages/noodl-core-ui/src/components/modals/AiProjectModal/ProjectDescriptionInput.tsx`
|
||||
7. `packages/noodl-core-ui/src/components/modals/AiProjectModal/ScaffoldPreview.tsx`
|
||||
8. `packages/noodl-core-ui/src/components/modals/AiProjectModal/RefinementChat.tsx`
|
||||
|
||||
## Files to Modify
|
||||
|
||||
1. `packages/noodl-editor/src/editor/src/views/projectsview.ts`
|
||||
- Add "Describe your project" option
|
||||
- Launch AiProjectModal
|
||||
|
||||
2. `packages/noodl-core-ui/src/preview/launcher/Launcher/Launcher.tsx`
|
||||
- Add AI project creation button
|
||||
|
||||
3. `packages/noodl-editor/src/editor/src/utils/LocalProjectsModel.ts`
|
||||
- Add `newProjectFromScaffold()` method
|
||||
|
||||
## Implementation Steps
|
||||
|
||||
### Phase 1: AI Infrastructure
|
||||
1. Create AnthropicClient wrapper
|
||||
2. Implement prompt templates
|
||||
3. Set up API key management
|
||||
4. Create scaffolding service skeleton
|
||||
|
||||
### Phase 2: Scaffold Generation
|
||||
1. Implement analyzeDescription
|
||||
2. Implement generateScaffold
|
||||
3. Test with various descriptions
|
||||
4. Refine prompts based on results
|
||||
|
||||
### Phase 3: Scaffold Converter
|
||||
1. Implement page creation
|
||||
2. Implement component creation
|
||||
3. Implement navigation setup
|
||||
4. Implement styling application
|
||||
|
||||
### Phase 4: UI - Input Phase
|
||||
1. Create AiProjectModal
|
||||
2. Create ProjectDescriptionInput
|
||||
3. Add example prompts
|
||||
4. Integrate with launcher
|
||||
|
||||
### Phase 5: UI - Preview Phase
|
||||
1. Create ScaffoldPreview
|
||||
2. Create structure tree view
|
||||
3. Create RefinementChat
|
||||
4. Add edit/regenerate actions
|
||||
|
||||
### Phase 6: Project Creation
|
||||
1. Implement createProject
|
||||
2. Handle prefab imports
|
||||
3. Open project in editor
|
||||
4. Show success/onboarding
|
||||
|
||||
## Testing Checklist
|
||||
|
||||
- [ ] Description analysis extracts features correctly
|
||||
- [ ] Scaffold generation produces valid structure
|
||||
- [ ] Prefabs used when appropriate
|
||||
- [ ] Converter creates valid components
|
||||
- [ ] Pages have correct routing
|
||||
- [ ] Navigation works between pages
|
||||
- [ ] Styling applied consistently
|
||||
- [ ] Refinement chat updates scaffold
|
||||
- [ ] Project opens in editor
|
||||
- [ ] Error handling for API failures
|
||||
- [ ] Rate limiting handled gracefully
|
||||
|
||||
## Dependencies
|
||||
|
||||
- Anthropic API access (Claude)
|
||||
- COMP-002 (Built-in Prefabs) - for scaffold components
|
||||
|
||||
## Blocked By
|
||||
|
||||
- None (can start immediately)
|
||||
|
||||
## Blocks
|
||||
|
||||
- AI-002 (Component Suggestions)
|
||||
- AI-003 (Natural Language Editing)
|
||||
|
||||
## Estimated Effort
|
||||
|
||||
- AI infrastructure: 4-5 hours
|
||||
- Scaffold generation: 6-8 hours
|
||||
- Scaffold converter: 6-8 hours
|
||||
- UI input phase: 4-5 hours
|
||||
- UI preview phase: 5-6 hours
|
||||
- Project creation: 3-4 hours
|
||||
- Testing & refinement: 4-5 hours
|
||||
- **Total: 32-41 hours**
|
||||
|
||||
## Success Criteria
|
||||
|
||||
1. Users can describe project in natural language
|
||||
2. AI generates appropriate structure
|
||||
3. Preview shows clear scaffold
|
||||
4. Refinement chat enables adjustments
|
||||
5. Created project is functional
|
||||
6. Time from idea to working scaffold < 2 minutes
|
||||
|
||||
## Example Prompts & Outputs
|
||||
|
||||
### Example 1: Task Manager
|
||||
|
||||
**Input:** "A task management app where users can create projects, add tasks with due dates, and track progress with a kanban board"
|
||||
|
||||
**Output:**
|
||||
- Pages: Home, Projects, Kanban, TaskDetail
|
||||
- Components: TaskCard, KanbanColumn, ProjectCard, NavBar
|
||||
- Data: Task (title, description, dueDate, status), Project (name, tasks[])
|
||||
|
||||
### Example 2: Recipe App
|
||||
|
||||
**Input:** "A recipe app with categories, favorites, and a shopping list generator"
|
||||
|
||||
**Output:**
|
||||
- Pages: Home, Categories, RecipeDetail, Favorites, ShoppingList
|
||||
- Components: RecipeCard, CategoryTile, IngredientList, AddToFavoritesButton
|
||||
- Data: Recipe, Category, Ingredient, ShoppingItem
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
- Voice input for description
|
||||
- Screenshot/mockup to scaffold
|
||||
- Integration with design systems
|
||||
- Multi-language support
|
||||
- Template learning from user projects
|
||||
@@ -0,0 +1,507 @@
|
||||
# AI-002: AI Component Suggestions
|
||||
|
||||
## Overview
|
||||
|
||||
Provide intelligent, context-aware component suggestions as users build their projects. When a user is working on a component, AI analyzes the context and suggests relevant nodes, connections, or entire sub-components that would complement what they're building.
|
||||
|
||||
## Context
|
||||
|
||||
Currently, users must:
|
||||
- Know what node they need
|
||||
- Search through the node picker
|
||||
- Understand which nodes work together
|
||||
- Manually create common patterns
|
||||
|
||||
This creates friction for:
|
||||
- New users learning the platform
|
||||
- Experienced users building repetitive patterns
|
||||
- Anyone implementing common UI patterns
|
||||
|
||||
AI suggestions provide:
|
||||
- "What you might need next" recommendations
|
||||
- Common pattern recognition
|
||||
- Learning through suggestion
|
||||
- Faster workflow for experts
|
||||
|
||||
### Integration with Existing AI
|
||||
|
||||
From `AiAssistantModel.ts`:
|
||||
```typescript
|
||||
// Existing AI node templates
|
||||
templates: AiTemplate[] = docsTemplates.map(...)
|
||||
|
||||
// Activity tracking
|
||||
addActivity({ id, type, title, prompt, node, graph })
|
||||
```
|
||||
|
||||
This task extends the AI capabilities to work alongside normal editing, not just through dedicated AI nodes.
|
||||
|
||||
## Requirements
|
||||
|
||||
### Functional Requirements
|
||||
|
||||
1. **Context Analysis**
|
||||
- Analyze current component structure
|
||||
- Identify incomplete patterns
|
||||
- Detect user intent from recent actions
|
||||
- Consider project-wide context
|
||||
|
||||
2. **Suggestion Types**
|
||||
- **Node suggestions**: "Add a Loading state?"
|
||||
- **Connection suggestions**: "Connect this to..."
|
||||
- **Pattern completion**: "Complete this form with validation?"
|
||||
- **Prefab suggestions**: "Use the Form Input prefab?"
|
||||
|
||||
3. **Suggestion Display**
|
||||
- Non-intrusive inline hints
|
||||
- Expandable detail panel
|
||||
- One-click insertion
|
||||
- Keyboard shortcuts
|
||||
|
||||
4. **Learning & Relevance**
|
||||
- Learn from user accepts/rejects
|
||||
- Improve relevance over time
|
||||
- Consider user skill level
|
||||
- Avoid repetitive suggestions
|
||||
|
||||
5. **Control & Settings**
|
||||
- Enable/disable suggestions
|
||||
- Suggestion frequency
|
||||
- Types of suggestions
|
||||
- Reset learned preferences
|
||||
|
||||
### Non-Functional Requirements
|
||||
|
||||
- Suggestions appear within 500ms
|
||||
- No blocking of user actions
|
||||
- Minimal API calls (batch/cache)
|
||||
- Works offline (basic patterns)
|
||||
|
||||
## Technical Approach
|
||||
|
||||
### 1. Suggestion Service
|
||||
|
||||
```typescript
|
||||
// packages/noodl-editor/src/editor/src/services/AiSuggestionService.ts
|
||||
|
||||
interface SuggestionContext {
|
||||
component: ComponentModel;
|
||||
selectedNodes: NodeGraphNode[];
|
||||
recentActions: EditorAction[];
|
||||
projectContext: ProjectContext;
|
||||
userPreferences: UserPreferences;
|
||||
}
|
||||
|
||||
interface Suggestion {
|
||||
id: string;
|
||||
type: 'node' | 'connection' | 'pattern' | 'prefab';
|
||||
confidence: number; // 0-1
|
||||
title: string;
|
||||
description: string;
|
||||
preview?: string; // Visual preview
|
||||
action: SuggestionAction;
|
||||
dismissable: boolean;
|
||||
}
|
||||
|
||||
interface SuggestionAction {
|
||||
type: 'insert_node' | 'create_connection' | 'insert_pattern' | 'import_prefab';
|
||||
payload: any;
|
||||
}
|
||||
|
||||
class AiSuggestionService {
|
||||
private static instance: AiSuggestionService;
|
||||
private suggestionCache: Map<string, Suggestion[]> = new Map();
|
||||
private userFeedback: UserFeedbackStore;
|
||||
|
||||
// Main API
|
||||
async getSuggestions(context: SuggestionContext): Promise<Suggestion[]>;
|
||||
async applySuggestion(suggestion: Suggestion): Promise<void>;
|
||||
async dismissSuggestion(suggestion: Suggestion): Promise<void>;
|
||||
|
||||
// Feedback
|
||||
recordAccept(suggestion: Suggestion): void;
|
||||
recordReject(suggestion: Suggestion): void;
|
||||
recordIgnore(suggestion: Suggestion): void;
|
||||
|
||||
// Settings
|
||||
setEnabled(enabled: boolean): void;
|
||||
setFrequency(frequency: SuggestionFrequency): void;
|
||||
getSuggestionSettings(): SuggestionSettings;
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Context Analyzer
|
||||
|
||||
```typescript
|
||||
// packages/noodl-editor/src/editor/src/services/ai/ContextAnalyzer.ts
|
||||
|
||||
interface AnalysisResult {
|
||||
componentType: ComponentType;
|
||||
currentPattern: Pattern | null;
|
||||
incompletePatterns: IncompletePattern[];
|
||||
missingConnections: MissingConnection[];
|
||||
suggestedEnhancements: Enhancement[];
|
||||
}
|
||||
|
||||
class ContextAnalyzer {
|
||||
// Pattern detection
|
||||
detectPatterns(component: ComponentModel): Pattern[];
|
||||
detectIncompletePatterns(component: ComponentModel): IncompletePattern[];
|
||||
|
||||
// Connection analysis
|
||||
findMissingConnections(nodes: NodeGraphNode[]): MissingConnection[];
|
||||
findOrphanedNodes(component: ComponentModel): NodeGraphNode[];
|
||||
|
||||
// Intent inference
|
||||
inferUserIntent(recentActions: EditorAction[]): UserIntent;
|
||||
|
||||
// Project context
|
||||
getRelatedComponents(component: ComponentModel): ComponentModel[];
|
||||
getDataModelContext(component: ComponentModel): DataModel[];
|
||||
}
|
||||
|
||||
// Common patterns to detect
|
||||
const PATTERNS = {
|
||||
FORM_INPUT: {
|
||||
nodes: ['TextInput', 'Label'],
|
||||
missing: ['Validation', 'ErrorDisplay'],
|
||||
suggestion: 'Add form validation?'
|
||||
},
|
||||
LIST_ITEM: {
|
||||
nodes: ['Repeater', 'Group'],
|
||||
missing: ['ItemClick', 'DeleteAction'],
|
||||
suggestion: 'Add item interactions?'
|
||||
},
|
||||
DATA_FETCH: {
|
||||
nodes: ['REST'],
|
||||
missing: ['LoadingState', 'ErrorState'],
|
||||
suggestion: 'Add loading and error states?'
|
||||
},
|
||||
// ... more patterns
|
||||
};
|
||||
```
|
||||
|
||||
### 3. Suggestion Engine
|
||||
|
||||
```typescript
|
||||
// packages/noodl-editor/src/editor/src/services/ai/SuggestionEngine.ts
|
||||
|
||||
class SuggestionEngine {
|
||||
private contextAnalyzer: ContextAnalyzer;
|
||||
private patternLibrary: PatternLibrary;
|
||||
private prefabMatcher: PrefabMatcher;
|
||||
|
||||
async generateSuggestions(context: SuggestionContext): Promise<Suggestion[]> {
|
||||
const suggestions: Suggestion[] = [];
|
||||
|
||||
// 1. Local pattern matching (no API)
|
||||
const localSuggestions = this.getLocalSuggestions(context);
|
||||
suggestions.push(...localSuggestions);
|
||||
|
||||
// 2. AI-powered suggestions (API call)
|
||||
if (this.shouldCallApi(context)) {
|
||||
const aiSuggestions = await this.getAiSuggestions(context);
|
||||
suggestions.push(...aiSuggestions);
|
||||
}
|
||||
|
||||
// 3. Prefab matching
|
||||
const prefabSuggestions = this.getPrefabSuggestions(context);
|
||||
suggestions.push(...prefabSuggestions);
|
||||
|
||||
// 4. Rank and filter
|
||||
return this.rankSuggestions(suggestions, context);
|
||||
}
|
||||
|
||||
private getLocalSuggestions(context: SuggestionContext): Suggestion[] {
|
||||
const analysis = this.contextAnalyzer.analyze(context.component);
|
||||
const suggestions: Suggestion[] = [];
|
||||
|
||||
// Pattern completion
|
||||
for (const incomplete of analysis.incompletePatterns) {
|
||||
suggestions.push({
|
||||
type: 'pattern',
|
||||
title: incomplete.completionTitle,
|
||||
description: incomplete.description,
|
||||
confidence: incomplete.confidence,
|
||||
action: {
|
||||
type: 'insert_pattern',
|
||||
payload: incomplete.completionNodes
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Missing connections
|
||||
for (const missing of analysis.missingConnections) {
|
||||
suggestions.push({
|
||||
type: 'connection',
|
||||
title: `Connect ${missing.from} to ${missing.to}`,
|
||||
confidence: missing.confidence,
|
||||
action: {
|
||||
type: 'create_connection',
|
||||
payload: missing
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return suggestions;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. UI Components
|
||||
|
||||
#### Inline Suggestion Hint
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ Canvas │
|
||||
│ │
|
||||
│ ┌─────────────┐ ┌─────────────┐ │
|
||||
│ │ TextInput │──────│ Variable │ │
|
||||
│ └─────────────┘ └─────────────┘ │
|
||||
│ │ │
|
||||
│ ▼ │
|
||||
│ ┌─────────────────────────────────────────┐ │
|
||||
│ │ 💡 Add form validation? [+ Add]│ │
|
||||
│ │ Validate input and show errors │ │
|
||||
│ └─────────────────────────────────────────┘ │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
#### Suggestion Panel
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ 💡 Suggestions [×] │
|
||||
├─────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ Based on your current component: │
|
||||
│ │
|
||||
│ ┌─────────────────────────────────────────────────────────────────┐ │
|
||||
│ │ 🧩 Complete Form Pattern [+ Apply] │ │
|
||||
│ │ Add validation, error states, and submit handling │ │
|
||||
│ │ Confidence: ████████░░ 85% │ │
|
||||
│ ├─────────────────────────────────────────────────────────────────┤ │
|
||||
│ │ 📦 Use "Form Input" Prefab [+ Apply] │ │
|
||||
│ │ Replace with pre-built form input component │ │
|
||||
│ │ Confidence: ███████░░░ 75% │ │
|
||||
│ ├─────────────────────────────────────────────────────────────────┤ │
|
||||
│ │ 🔗 Connect to Submit button [+ Apply] │ │
|
||||
│ │ Wire up the form submission flow │ │
|
||||
│ │ Confidence: ██████░░░░ 65% │ │
|
||||
│ └─────────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ [⚙️ Suggestion Settings] │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 5. Pattern Library
|
||||
|
||||
```typescript
|
||||
// packages/noodl-editor/src/editor/src/services/ai/PatternLibrary.ts
|
||||
|
||||
interface PatternDefinition {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
trigger: PatternTrigger;
|
||||
completion: PatternCompletion;
|
||||
examples: string[];
|
||||
}
|
||||
|
||||
const PATTERNS: PatternDefinition[] = [
|
||||
{
|
||||
id: 'form-validation',
|
||||
name: 'Form Validation',
|
||||
description: 'Add input validation with error display',
|
||||
trigger: {
|
||||
hasNodes: ['TextInput', 'Variable'],
|
||||
missingNodes: ['Function', 'Condition', 'Text'],
|
||||
nodeCount: { min: 2, max: 5 }
|
||||
},
|
||||
completion: {
|
||||
nodes: [
|
||||
{ type: 'Function', name: 'Validate' },
|
||||
{ type: 'Condition', name: 'IsValid' },
|
||||
{ type: 'Text', name: 'ErrorMessage' }
|
||||
],
|
||||
connections: [
|
||||
{ from: 'TextInput.value', to: 'Validate.input' },
|
||||
{ from: 'Validate.result', to: 'IsValid.condition' },
|
||||
{ from: 'IsValid.false', to: 'ErrorMessage.visible' }
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'loading-state',
|
||||
name: 'Loading State',
|
||||
description: 'Add loading indicator during async operations',
|
||||
trigger: {
|
||||
hasNodes: ['REST'],
|
||||
missingNodes: ['Condition', 'Group'],
|
||||
},
|
||||
completion: {
|
||||
nodes: [
|
||||
{ type: 'Variable', name: 'IsLoading' },
|
||||
{ type: 'Group', name: 'LoadingSpinner' },
|
||||
{ type: 'Condition', name: 'ShowContent' }
|
||||
],
|
||||
connections: [
|
||||
{ from: 'REST.fetch', to: 'IsLoading.set(true)' },
|
||||
{ from: 'REST.success', to: 'IsLoading.set(false)' },
|
||||
{ from: 'IsLoading.value', to: 'LoadingSpinner.visible' }
|
||||
]
|
||||
}
|
||||
},
|
||||
// ... more patterns
|
||||
];
|
||||
```
|
||||
|
||||
## Files to Create
|
||||
|
||||
1. `packages/noodl-editor/src/editor/src/services/AiSuggestionService.ts`
|
||||
2. `packages/noodl-editor/src/editor/src/services/ai/ContextAnalyzer.ts`
|
||||
3. `packages/noodl-editor/src/editor/src/services/ai/SuggestionEngine.ts`
|
||||
4. `packages/noodl-editor/src/editor/src/services/ai/PatternLibrary.ts`
|
||||
5. `packages/noodl-editor/src/editor/src/services/ai/PrefabMatcher.ts`
|
||||
6. `packages/noodl-core-ui/src/components/ai/SuggestionHint/SuggestionHint.tsx`
|
||||
7. `packages/noodl-core-ui/src/components/ai/SuggestionPanel/SuggestionPanel.tsx`
|
||||
8. `packages/noodl-core-ui/src/components/ai/SuggestionCard/SuggestionCard.tsx`
|
||||
|
||||
## Files to Modify
|
||||
|
||||
1. `packages/noodl-editor/src/editor/src/views/nodegrapheditor.js`
|
||||
- Hook into node selection/creation
|
||||
- Trigger suggestion generation
|
||||
- Display suggestion hints
|
||||
|
||||
2. `packages/noodl-editor/src/editor/src/pages/EditorPage/EditorPage.tsx`
|
||||
- Add suggestion panel toggle
|
||||
- Handle suggestion keybindings
|
||||
|
||||
3. `packages/noodl-editor/src/editor/src/models/AiAssistant/AiAssistantModel.ts`
|
||||
- Integrate suggestion service
|
||||
- Share context with AI nodes
|
||||
|
||||
4. `packages/noodl-editor/src/editor/src/stores/EditorSettings.ts`
|
||||
- Add suggestion settings
|
||||
|
||||
## Implementation Steps
|
||||
|
||||
### Phase 1: Context Analysis
|
||||
1. Create ContextAnalyzer
|
||||
2. Implement pattern detection
|
||||
3. Implement connection analysis
|
||||
4. Test with various components
|
||||
|
||||
### Phase 2: Pattern Library
|
||||
1. Define pattern schema
|
||||
2. Create initial patterns (10-15)
|
||||
3. Implement pattern matching
|
||||
4. Test pattern triggers
|
||||
|
||||
### Phase 3: Suggestion Engine
|
||||
1. Create SuggestionEngine
|
||||
2. Implement local suggestions
|
||||
3. Implement AI suggestions
|
||||
4. Add ranking/filtering
|
||||
|
||||
### Phase 4: UI - Inline Hints
|
||||
1. Create SuggestionHint component
|
||||
2. Position near relevant nodes
|
||||
3. Add apply/dismiss actions
|
||||
4. Animate appearance
|
||||
|
||||
### Phase 5: UI - Panel
|
||||
1. Create SuggestionPanel
|
||||
2. Create SuggestionCard
|
||||
3. Add settings access
|
||||
4. Handle keyboard shortcuts
|
||||
|
||||
### Phase 6: Feedback & Learning
|
||||
1. Track accept/reject
|
||||
2. Adjust confidence scores
|
||||
3. Improve relevance
|
||||
4. Add user settings
|
||||
|
||||
## Testing Checklist
|
||||
|
||||
- [ ] Patterns detected correctly
|
||||
- [ ] Suggestions appear at right time
|
||||
- [ ] Apply action works correctly
|
||||
- [ ] Dismiss removes suggestion
|
||||
- [ ] Inline hint positions correctly
|
||||
- [ ] Panel shows all suggestions
|
||||
- [ ] Settings persist
|
||||
- [ ] Works offline (local patterns)
|
||||
- [ ] API suggestions enhance local
|
||||
- [ ] Feedback recorded
|
||||
- [ ] Performance < 500ms
|
||||
|
||||
## Dependencies
|
||||
|
||||
- AI-001 (AI Project Scaffolding) - for AI infrastructure
|
||||
- COMP-002 (Built-in Prefabs) - for prefab matching
|
||||
|
||||
## Blocked By
|
||||
|
||||
- AI-001 (for AnthropicClient)
|
||||
|
||||
## Blocks
|
||||
|
||||
- AI-003 (Natural Language Editing)
|
||||
|
||||
## Estimated Effort
|
||||
|
||||
- Context analyzer: 4-5 hours
|
||||
- Pattern library: 4-5 hours
|
||||
- Suggestion engine: 5-6 hours
|
||||
- UI inline hints: 3-4 hours
|
||||
- UI panel: 4-5 hours
|
||||
- Feedback system: 3-4 hours
|
||||
- Testing & refinement: 4-5 hours
|
||||
- **Total: 27-34 hours**
|
||||
|
||||
## Success Criteria
|
||||
|
||||
1. Suggestions appear contextually
|
||||
2. Pattern completion works smoothly
|
||||
3. Prefab matching finds relevant prefabs
|
||||
4. Apply action inserts correctly
|
||||
5. Users can control suggestions
|
||||
6. Suggestions improve over time
|
||||
|
||||
## Pattern Categories
|
||||
|
||||
### Forms
|
||||
- Form validation
|
||||
- Form submission
|
||||
- Input formatting
|
||||
- Error display
|
||||
|
||||
### Data
|
||||
- Loading states
|
||||
- Error handling
|
||||
- Refresh/retry
|
||||
- Pagination
|
||||
|
||||
### Navigation
|
||||
- Page transitions
|
||||
- Breadcrumbs
|
||||
- Tab navigation
|
||||
- Modal flows
|
||||
|
||||
### Lists
|
||||
- Item selection
|
||||
- Delete/edit actions
|
||||
- Drag and drop
|
||||
- Filtering
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
- Real-time suggestions while typing
|
||||
- Team-shared patterns
|
||||
- Auto-apply for obvious patterns
|
||||
- Pattern creation from selection
|
||||
- AI-powered custom patterns
|
||||
@@ -0,0 +1,565 @@
|
||||
# AI-003: Natural Language Editing
|
||||
|
||||
## Overview
|
||||
|
||||
Enable users to modify their projects using natural language commands. Instead of manually finding and configuring nodes, users can say "make this button blue" or "add a loading spinner when fetching data" and have AI make the changes.
|
||||
|
||||
## Context
|
||||
|
||||
Current editing workflow:
|
||||
1. Select node in canvas
|
||||
2. Find property in sidebar
|
||||
3. Understand property options
|
||||
4. Make change
|
||||
5. Repeat for related nodes
|
||||
|
||||
Natural language editing:
|
||||
1. Select component or node
|
||||
2. Describe what you want
|
||||
3. AI makes the changes
|
||||
4. Review and accept/modify
|
||||
|
||||
This is especially powerful for:
|
||||
- Styling changes across multiple elements
|
||||
- Logic modifications that span nodes
|
||||
- Refactoring component structure
|
||||
- Complex multi-step changes
|
||||
|
||||
### Existing AI Foundation
|
||||
|
||||
From `AiAssistantModel.ts`:
|
||||
```typescript
|
||||
// Chat history for AI interactions
|
||||
class ChatHistory {
|
||||
messages: ChatMessage[];
|
||||
add(message: ChatMessage): void;
|
||||
}
|
||||
|
||||
// AI context per node
|
||||
class AiCopilotContext {
|
||||
template: AiTemplate;
|
||||
chatHistory: ChatHistory;
|
||||
node: NodeGraphNode;
|
||||
}
|
||||
```
|
||||
|
||||
## Requirements
|
||||
|
||||
### Functional Requirements
|
||||
|
||||
1. **Command Input**
|
||||
- Command palette (Cmd+K style)
|
||||
- Inline text input on selection
|
||||
- Voice input (optional)
|
||||
- Recent commands history
|
||||
|
||||
2. **Command Understanding**
|
||||
- Style changes: "make it red", "add shadow"
|
||||
- Structure changes: "add a header", "wrap in a card"
|
||||
- Logic changes: "show loading while fetching"
|
||||
- Data changes: "sort by date", "filter active items"
|
||||
|
||||
3. **Change Preview**
|
||||
- Show what will change before applying
|
||||
- Highlight affected nodes
|
||||
- Before/after comparison
|
||||
- Explanation of changes
|
||||
|
||||
4. **Change Application**
|
||||
- Apply changes atomically
|
||||
- Support undo/redo
|
||||
- Handle errors gracefully
|
||||
- Learn from corrections
|
||||
|
||||
5. **Scope Selection**
|
||||
- Selected node(s) only
|
||||
- Current component
|
||||
- Related components
|
||||
- Entire project
|
||||
|
||||
### Non-Functional Requirements
|
||||
|
||||
- Response time < 3 seconds
|
||||
- Changes are reversible
|
||||
- Works on any component type
|
||||
- Graceful degradation without API
|
||||
|
||||
## Technical Approach
|
||||
|
||||
### 1. Natural Language Command Service
|
||||
|
||||
```typescript
|
||||
// packages/noodl-editor/src/editor/src/services/NaturalLanguageService.ts
|
||||
|
||||
interface CommandContext {
|
||||
selection: NodeGraphNode[];
|
||||
component: ComponentModel;
|
||||
project: ProjectModel;
|
||||
recentCommands: Command[];
|
||||
}
|
||||
|
||||
interface Command {
|
||||
id: string;
|
||||
text: string;
|
||||
timestamp: Date;
|
||||
result: CommandResult;
|
||||
}
|
||||
|
||||
interface CommandResult {
|
||||
success: boolean;
|
||||
changes: Change[];
|
||||
explanation: string;
|
||||
undoAction?: () => void;
|
||||
}
|
||||
|
||||
interface Change {
|
||||
type: 'property' | 'node' | 'connection' | 'structure';
|
||||
target: string;
|
||||
before: any;
|
||||
after: any;
|
||||
description: string;
|
||||
}
|
||||
|
||||
class NaturalLanguageService {
|
||||
private static instance: NaturalLanguageService;
|
||||
|
||||
// Main API
|
||||
async parseCommand(text: string, context: CommandContext): Promise<ParsedCommand>;
|
||||
async previewChanges(command: ParsedCommand): Promise<ChangePreview>;
|
||||
async applyChanges(preview: ChangePreview): Promise<CommandResult>;
|
||||
async undoCommand(commandId: string): Promise<void>;
|
||||
|
||||
// Command history
|
||||
getRecentCommands(): Command[];
|
||||
searchCommands(query: string): Command[];
|
||||
|
||||
// Learning
|
||||
recordCorrection(commandId: string, correction: Change[]): void;
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Command Parser
|
||||
|
||||
```typescript
|
||||
// packages/noodl-editor/src/editor/src/services/ai/CommandParser.ts
|
||||
|
||||
interface ParsedCommand {
|
||||
intent: CommandIntent;
|
||||
targets: CommandTarget[];
|
||||
modifications: Modification[];
|
||||
confidence: number;
|
||||
}
|
||||
|
||||
enum CommandIntent {
|
||||
STYLE_CHANGE = 'style_change',
|
||||
STRUCTURE_CHANGE = 'structure_change',
|
||||
LOGIC_CHANGE = 'logic_change',
|
||||
DATA_CHANGE = 'data_change',
|
||||
CREATE = 'create',
|
||||
DELETE = 'delete',
|
||||
CONNECT = 'connect',
|
||||
UNKNOWN = 'unknown'
|
||||
}
|
||||
|
||||
interface CommandTarget {
|
||||
type: 'node' | 'component' | 'property' | 'connection';
|
||||
selector: string; // How to find it
|
||||
resolved?: any; // Actual reference
|
||||
}
|
||||
|
||||
class CommandParser {
|
||||
private patterns: CommandPattern[];
|
||||
|
||||
async parse(text: string, context: CommandContext): Promise<ParsedCommand> {
|
||||
// 1. Try local pattern matching first (fast)
|
||||
const localMatch = this.matchLocalPatterns(text);
|
||||
if (localMatch.confidence > 0.9) {
|
||||
return localMatch;
|
||||
}
|
||||
|
||||
// 2. Use AI for complex commands
|
||||
const aiParsed = await this.aiParse(text, context);
|
||||
|
||||
// 3. Merge and validate
|
||||
return this.mergeAndValidate(localMatch, aiParsed, context);
|
||||
}
|
||||
|
||||
private matchLocalPatterns(text: string): ParsedCommand {
|
||||
// Pattern: "make [target] [color]"
|
||||
// Pattern: "add [element] to [target]"
|
||||
// Pattern: "connect [source] to [destination]"
|
||||
// etc.
|
||||
}
|
||||
|
||||
private async aiParse(text: string, context: CommandContext): Promise<ParsedCommand> {
|
||||
const prompt = `Parse this Noodl editing command:
|
||||
Command: "${text}"
|
||||
|
||||
Current selection: ${context.selection.map(n => n.type.localName).join(', ')}
|
||||
Component: ${context.component.name}
|
||||
|
||||
Output JSON with: intent, targets, modifications`;
|
||||
|
||||
const response = await this.anthropicClient.complete(prompt);
|
||||
return JSON.parse(response);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Change Generator
|
||||
|
||||
```typescript
|
||||
// packages/noodl-editor/src/editor/src/services/ai/ChangeGenerator.ts
|
||||
|
||||
class ChangeGenerator {
|
||||
// Generate actual changes from parsed command
|
||||
async generateChanges(command: ParsedCommand, context: CommandContext): Promise<Change[]> {
|
||||
const changes: Change[] = [];
|
||||
|
||||
switch (command.intent) {
|
||||
case CommandIntent.STYLE_CHANGE:
|
||||
changes.push(...this.generateStyleChanges(command, context));
|
||||
break;
|
||||
case CommandIntent.STRUCTURE_CHANGE:
|
||||
changes.push(...await this.generateStructureChanges(command, context));
|
||||
break;
|
||||
case CommandIntent.LOGIC_CHANGE:
|
||||
changes.push(...await this.generateLogicChanges(command, context));
|
||||
break;
|
||||
// ...
|
||||
}
|
||||
|
||||
return changes;
|
||||
}
|
||||
|
||||
private generateStyleChanges(command: ParsedCommand, context: CommandContext): Change[] {
|
||||
const changes: Change[] = [];
|
||||
|
||||
for (const target of command.targets) {
|
||||
const node = this.resolveTarget(target, context);
|
||||
|
||||
for (const mod of command.modifications) {
|
||||
const propertyName = this.mapToNoodlProperty(mod.property);
|
||||
const newValue = this.parseValue(mod.value, propertyName);
|
||||
|
||||
changes.push({
|
||||
type: 'property',
|
||||
target: node.id,
|
||||
before: node.parameters[propertyName],
|
||||
after: newValue,
|
||||
description: `Change ${propertyName} to ${newValue}`
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return changes;
|
||||
}
|
||||
|
||||
private async generateStructureChanges(
|
||||
command: ParsedCommand,
|
||||
context: CommandContext
|
||||
): Promise<Change[]> {
|
||||
// Use AI to generate node structure
|
||||
const prompt = `Generate Noodl node structure for:
|
||||
"${command.modifications.map(m => m.description).join(', ')}"
|
||||
|
||||
Current context: ${JSON.stringify(context.selection.map(n => n.type.localName))}
|
||||
|
||||
Output JSON array of nodes to create and connections`;
|
||||
|
||||
const response = await this.anthropicClient.complete(prompt);
|
||||
return this.parseStructureResponse(response);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. UI Components
|
||||
|
||||
#### Command Palette
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ 🔮 What do you want to do? [×] │
|
||||
├─────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ┌─────────────────────────────────────────────────────────────────┐ │
|
||||
│ │ Make the button larger and add a hover effect │ │
|
||||
│ └─────────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ Selected: Button, Text │
|
||||
│ │
|
||||
│ Recent: │
|
||||
│ • "Add loading spinner to the form" │
|
||||
│ • "Make all headers blue" │
|
||||
│ • "Connect the submit button to the API" │
|
||||
│ │
|
||||
│ Examples: │
|
||||
│ • "Wrap this in a card with shadow" │
|
||||
│ • "Add validation to all inputs" │
|
||||
│ • "Show error message when API fails" │
|
||||
│ │
|
||||
│ [Preview Changes] │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
#### Change Preview
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ Preview Changes [×] │
|
||||
├─────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ Command: "Make the button larger and add a hover effect" │
|
||||
│ │
|
||||
│ Changes to apply: │
|
||||
│ │
|
||||
│ ┌─────────────────────────────────────────────────────────────────┐ │
|
||||
│ │ ✓ Button │ │
|
||||
│ │ • width: 100px → 150px │ │
|
||||
│ │ • height: 40px → 50px │ │
|
||||
│ │ • fontSize: 14px → 16px │ │
|
||||
│ ├─────────────────────────────────────────────────────────────────┤ │
|
||||
│ │ + New: HoverState │ │
|
||||
│ │ • scale: 1.05 │ │
|
||||
│ │ • transition: 200ms │ │
|
||||
│ └─────────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ 🤖 "I'll increase the button size by 50% and add a subtle scale │
|
||||
│ effect on hover with a smooth transition." │
|
||||
│ │
|
||||
│ [Cancel] [Modify] [Apply Changes] │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
#### Inline Command
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ Canvas │
|
||||
│ │
|
||||
│ ┌─────────────────┐ │
|
||||
│ │ [Button] │ ← Selected │
|
||||
│ │ Click me │ │
|
||||
│ └─────────────────┘ │
|
||||
│ │ │
|
||||
│ ┌──────┴──────────────────────────────────────────────┐ │
|
||||
│ │ 🔮 Make it red with rounded corners [Enter] │ │
|
||||
│ └─────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 5. Command Patterns
|
||||
|
||||
```typescript
|
||||
// packages/noodl-editor/src/editor/src/services/ai/CommandPatterns.ts
|
||||
|
||||
const COMMAND_PATTERNS: CommandPattern[] = [
|
||||
// Style patterns
|
||||
{
|
||||
pattern: /make (?:it |this |the )?(.+?) (red|blue|green|...)/i,
|
||||
intent: CommandIntent.STYLE_CHANGE,
|
||||
extract: (match) => ({
|
||||
target: match[1] || 'selection',
|
||||
property: 'backgroundColor',
|
||||
value: match[2]
|
||||
})
|
||||
},
|
||||
{
|
||||
pattern: /(?:set |change )?(?:the )?(.+?) (?:to |=) (.+)/i,
|
||||
intent: CommandIntent.STYLE_CHANGE,
|
||||
extract: (match) => ({
|
||||
property: match[1],
|
||||
value: match[2]
|
||||
})
|
||||
},
|
||||
|
||||
// Structure patterns
|
||||
{
|
||||
pattern: /add (?:a |an )?(.+?) (?:to |inside |in) (.+)/i,
|
||||
intent: CommandIntent.STRUCTURE_CHANGE,
|
||||
extract: (match) => ({
|
||||
nodeType: match[1],
|
||||
target: match[2]
|
||||
})
|
||||
},
|
||||
{
|
||||
pattern: /wrap (?:it |this |selection )?in (?:a |an )?(.+)/i,
|
||||
intent: CommandIntent.STRUCTURE_CHANGE,
|
||||
extract: (match) => ({
|
||||
action: 'wrap',
|
||||
wrapper: match[1]
|
||||
})
|
||||
},
|
||||
|
||||
// Logic patterns
|
||||
{
|
||||
pattern: /show (.+?) when (.+)/i,
|
||||
intent: CommandIntent.LOGIC_CHANGE,
|
||||
extract: (match) => ({
|
||||
action: 'conditional_show',
|
||||
target: match[1],
|
||||
condition: match[2]
|
||||
})
|
||||
},
|
||||
{
|
||||
pattern: /connect (.+?) to (.+)/i,
|
||||
intent: CommandIntent.CONNECT,
|
||||
extract: (match) => ({
|
||||
source: match[1],
|
||||
destination: match[2]
|
||||
})
|
||||
}
|
||||
];
|
||||
```
|
||||
|
||||
## Files to Create
|
||||
|
||||
1. `packages/noodl-editor/src/editor/src/services/NaturalLanguageService.ts`
|
||||
2. `packages/noodl-editor/src/editor/src/services/ai/CommandParser.ts`
|
||||
3. `packages/noodl-editor/src/editor/src/services/ai/ChangeGenerator.ts`
|
||||
4. `packages/noodl-editor/src/editor/src/services/ai/CommandPatterns.ts`
|
||||
5. `packages/noodl-core-ui/src/components/ai/CommandPalette/CommandPalette.tsx`
|
||||
6. `packages/noodl-core-ui/src/components/ai/ChangePreview/ChangePreview.tsx`
|
||||
7. `packages/noodl-core-ui/src/components/ai/InlineCommand/InlineCommand.tsx`
|
||||
|
||||
## Files to Modify
|
||||
|
||||
1. `packages/noodl-editor/src/editor/src/views/nodegrapheditor.js`
|
||||
- Add command palette trigger (Cmd+K)
|
||||
- Add inline command on selection
|
||||
- Handle change application
|
||||
|
||||
2. `packages/noodl-editor/src/editor/src/pages/EditorPage/EditorPage.tsx`
|
||||
- Add keyboard shortcut handler
|
||||
- Mount command palette
|
||||
|
||||
3. `packages/noodl-editor/src/editor/src/models/NodeGraphModel.ts`
|
||||
- Add atomic change application
|
||||
- Support undo for AI changes
|
||||
|
||||
## Implementation Steps
|
||||
|
||||
### Phase 1: Command Infrastructure
|
||||
1. Create NaturalLanguageService
|
||||
2. Implement command history
|
||||
3. Set up undo/redo support
|
||||
|
||||
### Phase 2: Command Parser
|
||||
1. Create CommandParser
|
||||
2. Define local patterns
|
||||
3. Implement AI parsing
|
||||
4. Test parsing accuracy
|
||||
|
||||
### Phase 3: Change Generator
|
||||
1. Create ChangeGenerator
|
||||
2. Implement style changes
|
||||
3. Implement structure changes
|
||||
4. Implement logic changes
|
||||
|
||||
### Phase 4: UI - Command Palette
|
||||
1. Create CommandPalette component
|
||||
2. Add keyboard shortcut
|
||||
3. Show recent/examples
|
||||
4. Handle input
|
||||
|
||||
### Phase 5: UI - Change Preview
|
||||
1. Create ChangePreview component
|
||||
2. Show before/after
|
||||
3. Add explanation
|
||||
4. Handle apply/cancel
|
||||
|
||||
### Phase 6: UI - Inline Command
|
||||
1. Create InlineCommand component
|
||||
2. Position near selection
|
||||
3. Handle quick commands
|
||||
|
||||
### Phase 7: Learning & Improvement
|
||||
1. Track command success
|
||||
2. Record corrections
|
||||
3. Improve pattern matching
|
||||
|
||||
## Testing Checklist
|
||||
|
||||
- [ ] Style commands work correctly
|
||||
- [ ] Structure commands create nodes
|
||||
- [ ] Logic commands set up conditions
|
||||
- [ ] Preview shows accurate changes
|
||||
- [ ] Apply actually makes changes
|
||||
- [ ] Undo reverts changes
|
||||
- [ ] Keyboard shortcuts work
|
||||
- [ ] Recent commands saved
|
||||
- [ ] Error handling graceful
|
||||
- [ ] Complex commands work
|
||||
- [ ] Multi-target commands work
|
||||
|
||||
## Dependencies
|
||||
|
||||
- AI-001 (AI Project Scaffolding) - for AnthropicClient
|
||||
- AI-002 (Component Suggestions) - for context analysis
|
||||
|
||||
## Blocked By
|
||||
|
||||
- AI-001
|
||||
|
||||
## Blocks
|
||||
|
||||
- AI-004 (AI Design Assistance)
|
||||
|
||||
## Estimated Effort
|
||||
|
||||
- Command service: 4-5 hours
|
||||
- Command parser: 5-6 hours
|
||||
- Change generator: 6-8 hours
|
||||
- UI command palette: 4-5 hours
|
||||
- UI change preview: 4-5 hours
|
||||
- UI inline command: 3-4 hours
|
||||
- Testing & refinement: 4-5 hours
|
||||
- **Total: 30-38 hours**
|
||||
|
||||
## Success Criteria
|
||||
|
||||
1. Natural language commands understood
|
||||
2. Preview shows accurate changes
|
||||
3. Changes applied correctly
|
||||
4. Undo/redo works
|
||||
5. < 3 second response time
|
||||
6. 80%+ command success rate
|
||||
|
||||
## Command Examples
|
||||
|
||||
### Style Changes
|
||||
- "Make it red"
|
||||
- "Add a shadow"
|
||||
- "Increase font size to 18"
|
||||
- "Round the corners"
|
||||
- "Make all buttons blue"
|
||||
|
||||
### Structure Changes
|
||||
- "Add a header"
|
||||
- "Wrap in a card"
|
||||
- "Add a loading spinner"
|
||||
- "Create a sidebar"
|
||||
- "Split into two columns"
|
||||
|
||||
### Logic Changes
|
||||
- "Show loading while fetching"
|
||||
- "Hide when empty"
|
||||
- "Disable until form is valid"
|
||||
- "Navigate to home on click"
|
||||
- "Show error message on failure"
|
||||
|
||||
### Data Changes
|
||||
- "Sort by date"
|
||||
- "Filter completed items"
|
||||
- "Group by category"
|
||||
- "Limit to 10 items"
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
- Voice input
|
||||
- Multi-language support
|
||||
- Command macros
|
||||
- Batch changes
|
||||
- Command sharing
|
||||
- Context-aware autocomplete
|
||||
@@ -0,0 +1,681 @@
|
||||
# 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
|
||||
|
||||
1. **Design Analysis**
|
||||
- Scan component/page for issues
|
||||
- Categorize by severity (error, warning, info)
|
||||
- Group by type (spacing, color, typography, etc.)
|
||||
- Provide explanations
|
||||
|
||||
2. **Issue Categories**
|
||||
- **Accessibility**: Contrast, touch targets, labels
|
||||
- **Consistency**: Spacing, colors, typography
|
||||
- **Layout**: Alignment, balance, whitespace
|
||||
- **Interaction**: Hover, focus, active states
|
||||
- **Responsiveness**: Breakpoint issues
|
||||
|
||||
3. **Fix Application**
|
||||
- One-click fix for individual issues
|
||||
- "Fix all" for category
|
||||
- Preview before applying
|
||||
- Explain what was fixed
|
||||
|
||||
4. **Design Improvement**
|
||||
- "Polish this" command
|
||||
- Transform rough layouts
|
||||
- Suggest design alternatives
|
||||
- Apply consistent styling
|
||||
|
||||
5. **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
|
||||
|
||||
```typescript
|
||||
// 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
|
||||
|
||||
```typescript
|
||||
// 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
|
||||
|
||||
```typescript
|
||||
// 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
|
||||
|
||||
```typescript
|
||||
// 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
|
||||
|
||||
1. `packages/noodl-editor/src/editor/src/services/DesignAnalysisService.ts`
|
||||
2. `packages/noodl-editor/src/editor/src/services/ai/analyzers/AccessibilityAnalyzer.ts`
|
||||
3. `packages/noodl-editor/src/editor/src/services/ai/analyzers/ConsistencyAnalyzer.ts`
|
||||
4. `packages/noodl-editor/src/editor/src/services/ai/analyzers/LayoutAnalyzer.ts`
|
||||
5. `packages/noodl-editor/src/editor/src/services/ai/analyzers/InteractionAnalyzer.ts`
|
||||
6. `packages/noodl-editor/src/editor/src/services/ai/PolishEngine.ts`
|
||||
7. `packages/noodl-editor/src/editor/src/services/ai/DesignRules.ts`
|
||||
8. `packages/noodl-core-ui/src/components/ai/DesignReviewPanel/DesignReviewPanel.tsx`
|
||||
9. `packages/noodl-core-ui/src/components/ai/DesignIssueCard/DesignIssueCard.tsx`
|
||||
10. `packages/noodl-core-ui/src/components/ai/PolishPreview/PolishPreview.tsx`
|
||||
|
||||
## Files to Modify
|
||||
|
||||
1. `packages/noodl-editor/src/editor/src/pages/EditorPage/EditorPage.tsx`
|
||||
- Add Design Review panel toggle
|
||||
- Add menu option
|
||||
|
||||
2. `packages/noodl-editor/src/editor/src/views/panels/propertiespanel/`
|
||||
- Add issue indicators on properties
|
||||
- Quick fix buttons
|
||||
|
||||
3. `packages/noodl-editor/src/editor/src/views/nodegrapheditor.js`
|
||||
- Highlight nodes with issues
|
||||
- Add "Polish" context menu
|
||||
|
||||
## Implementation Steps
|
||||
|
||||
### Phase 1: Analysis Infrastructure
|
||||
1. Create DesignAnalysisService
|
||||
2. Define issue types and categories
|
||||
3. Create analyzer base class
|
||||
4. Implement fix application
|
||||
|
||||
### Phase 2: Core Analyzers
|
||||
1. Implement AccessibilityAnalyzer
|
||||
2. Implement ConsistencyAnalyzer
|
||||
3. Implement LayoutAnalyzer
|
||||
4. Implement InteractionAnalyzer
|
||||
|
||||
### Phase 3: Polish Engine
|
||||
1. Create PolishEngine
|
||||
2. Implement auto-fix application
|
||||
3. Add AI improvement suggestions
|
||||
4. Generate explanations
|
||||
|
||||
### Phase 4: UI - Review Panel
|
||||
1. Create DesignReviewPanel
|
||||
2. Create DesignIssueCard
|
||||
3. Group issues by category
|
||||
4. Add fix buttons
|
||||
|
||||
### Phase 5: UI - Polish Preview
|
||||
1. Create PolishPreview
|
||||
2. Show before/after
|
||||
3. List changes
|
||||
4. Apply/revert actions
|
||||
|
||||
### Phase 6: Integration
|
||||
1. Add to editor menus
|
||||
2. Highlight issues on canvas
|
||||
3. 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
|
||||
|
||||
1. Issues detected accurately
|
||||
2. Fixes don't break functionality
|
||||
3. Polish improves design quality
|
||||
4. Accessibility issues caught
|
||||
5. One-click fixes work
|
||||
6. 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
|
||||
@@ -0,0 +1,425 @@
|
||||
# AI Series: AI-Powered Development
|
||||
|
||||
## Overview
|
||||
|
||||
The AI series transforms OpenNoodl from a visual development tool into an intelligent development partner. Users can describe what they want to build, receive contextual suggestions, edit with natural language, and get automatic design feedback—all powered by Claude AI.
|
||||
|
||||
## Target Environment
|
||||
|
||||
- **Editor**: React 19 version only
|
||||
- **Runtime**: Not affected
|
||||
- **API**: Anthropic Claude API
|
||||
- **Fallback**: Graceful degradation without API
|
||||
|
||||
## Task Dependency Graph
|
||||
|
||||
```
|
||||
AI-001 (Project Scaffolding)
|
||||
│
|
||||
├──────────────────────────┐
|
||||
↓ ↓
|
||||
AI-002 (Suggestions) AI-003 (NL Editing)
|
||||
│ │
|
||||
└──────────┬───────────────┘
|
||||
↓
|
||||
AI-004 (Design Assistance)
|
||||
```
|
||||
|
||||
## Task Summary
|
||||
|
||||
| Task ID | Name | Est. Hours | Priority |
|
||||
|---------|------|------------|----------|
|
||||
| AI-001 | AI Project Scaffolding | 32-41 | Critical |
|
||||
| AI-002 | AI Component Suggestions | 27-34 | High |
|
||||
| AI-003 | Natural Language Editing | 30-38 | High |
|
||||
| AI-004 | AI Design Assistance | 32-41 | Medium |
|
||||
|
||||
**Total Estimated: 121-154 hours**
|
||||
|
||||
## Implementation Order
|
||||
|
||||
### Phase 1: Foundation (Weeks 1-3)
|
||||
1. **AI-001** - Project scaffolding with AI
|
||||
- Establishes Anthropic API integration
|
||||
- Creates core AI services
|
||||
- Delivers immediate user value
|
||||
|
||||
### Phase 2: In-Editor Intelligence (Weeks 4-6)
|
||||
2. **AI-002** - Component suggestions
|
||||
- Context-aware recommendations
|
||||
- Pattern library foundation
|
||||
3. **AI-003** - Natural language editing
|
||||
- Command palette for AI edits
|
||||
- Change preview and application
|
||||
|
||||
### Phase 3: Design Quality (Weeks 7-8)
|
||||
4. **AI-004** - Design assistance
|
||||
- Automated design review
|
||||
- Polish and improvements
|
||||
|
||||
## Existing Infrastructure
|
||||
|
||||
### AiAssistantModel
|
||||
|
||||
```typescript
|
||||
// Current AI node system
|
||||
class AiAssistantModel {
|
||||
templates: AiTemplate[]; // REST, Function, Form Validation, etc.
|
||||
|
||||
createNode(templateId, parentModel, pos);
|
||||
createContext(node);
|
||||
send(context);
|
||||
}
|
||||
```
|
||||
|
||||
### AI Templates
|
||||
|
||||
```typescript
|
||||
docsTemplates = [
|
||||
{ label: 'REST API', template: 'rest' },
|
||||
{ label: 'Form Validation', template: 'function-form-validation' },
|
||||
{ label: 'AI Function', template: 'function' },
|
||||
{ label: 'Write to database', template: 'function-crud' }
|
||||
];
|
||||
```
|
||||
|
||||
### Template Registry
|
||||
|
||||
```typescript
|
||||
// Project template system
|
||||
templateRegistry.list({}); // List available templates
|
||||
templateRegistry.download({ templateUrl }); // Download template
|
||||
```
|
||||
|
||||
### LocalProjectsModel
|
||||
|
||||
```typescript
|
||||
// Project creation
|
||||
LocalProjectsModel.newProject(callback, {
|
||||
name,
|
||||
path,
|
||||
projectTemplate
|
||||
});
|
||||
```
|
||||
|
||||
## New Architecture
|
||||
|
||||
### Core AI Services
|
||||
|
||||
```
|
||||
packages/noodl-editor/src/editor/src/services/
|
||||
├── ai/
|
||||
│ ├── AnthropicClient.ts # Claude API wrapper
|
||||
│ ├── prompts/ # Prompt templates
|
||||
│ │ ├── scaffolding.ts
|
||||
│ │ ├── suggestions.ts
|
||||
│ │ └── editing.ts
|
||||
│ ├── ContextAnalyzer.ts # Component analysis
|
||||
│ ├── PatternLibrary.ts # Known patterns
|
||||
│ ├── CommandParser.ts # NL command parsing
|
||||
│ ├── ChangeGenerator.ts # Generate changes
|
||||
│ └── analyzers/ # Design analyzers
|
||||
│ ├── AccessibilityAnalyzer.ts
|
||||
│ ├── ConsistencyAnalyzer.ts
|
||||
│ └── ...
|
||||
├── AiScaffoldingService.ts
|
||||
├── AiSuggestionService.ts
|
||||
├── NaturalLanguageService.ts
|
||||
└── DesignAnalysisService.ts
|
||||
```
|
||||
|
||||
### Service Hierarchy
|
||||
|
||||
| Service | Purpose |
|
||||
|---------|---------|
|
||||
| AnthropicClient | Claude API communication |
|
||||
| AiScaffoldingService | Project generation |
|
||||
| AiSuggestionService | Context-aware suggestions |
|
||||
| NaturalLanguageService | Command parsing & execution |
|
||||
| DesignAnalysisService | Design review & fixes |
|
||||
|
||||
## API Integration
|
||||
|
||||
### Anthropic Client
|
||||
|
||||
```typescript
|
||||
class AnthropicClient {
|
||||
private apiKey: string;
|
||||
private model = 'claude-sonnet-4-20250514';
|
||||
|
||||
async complete(prompt: string, options?: CompletionOptions): Promise<string>;
|
||||
async chat(messages: Message[]): Promise<Message>;
|
||||
async stream(prompt: string, onChunk: (chunk: string) => void): Promise<void>;
|
||||
}
|
||||
```
|
||||
|
||||
### API Key Management
|
||||
|
||||
```typescript
|
||||
// Settings storage
|
||||
interface AiSettings {
|
||||
apiKey: string; // Stored securely
|
||||
enabled: boolean;
|
||||
features: {
|
||||
scaffolding: boolean;
|
||||
suggestions: boolean;
|
||||
naturalLanguage: boolean;
|
||||
designAssistance: boolean;
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
## Key User Flows
|
||||
|
||||
### 1. Create Project from Description
|
||||
|
||||
```
|
||||
User opens "New Project"
|
||||
↓
|
||||
Selects "Describe your project"
|
||||
↓
|
||||
Types: "A task management app with kanban board"
|
||||
↓
|
||||
AI generates scaffold
|
||||
↓
|
||||
User previews & refines via chat
|
||||
↓
|
||||
Creates actual project
|
||||
```
|
||||
|
||||
### 2. Get Suggestions While Building
|
||||
|
||||
```
|
||||
User adds TextInput node
|
||||
↓
|
||||
System detects incomplete form pattern
|
||||
↓
|
||||
Shows suggestion: "Add form validation?"
|
||||
↓
|
||||
User clicks "Apply"
|
||||
↓
|
||||
Validation nodes added automatically
|
||||
```
|
||||
|
||||
### 3. Edit with Natural Language
|
||||
|
||||
```
|
||||
User selects Button node
|
||||
↓
|
||||
Presses Cmd+K
|
||||
↓
|
||||
Types: "Make it larger with a hover effect"
|
||||
↓
|
||||
Preview shows changes
|
||||
↓
|
||||
User clicks "Apply"
|
||||
```
|
||||
|
||||
### 4. Design Review & Polish
|
||||
|
||||
```
|
||||
User opens Design Review panel
|
||||
↓
|
||||
AI analyzes component
|
||||
↓
|
||||
Shows: "2 accessibility issues, 3 warnings"
|
||||
↓
|
||||
User clicks "Fix All" or "Polish"
|
||||
↓
|
||||
Changes applied automatically
|
||||
```
|
||||
|
||||
## UI Components to Create
|
||||
|
||||
| Component | Package | Purpose |
|
||||
|-----------|---------|---------|
|
||||
| AiProjectModal | noodl-core-ui | Project scaffolding UI |
|
||||
| ScaffoldPreview | noodl-core-ui | Preview generated structure |
|
||||
| SuggestionHint | noodl-core-ui | Inline suggestion display |
|
||||
| SuggestionPanel | noodl-core-ui | Full suggestions list |
|
||||
| CommandPalette | noodl-core-ui | NL command input |
|
||||
| ChangePreview | noodl-core-ui | Show pending changes |
|
||||
| DesignReviewPanel | noodl-core-ui | Design issues list |
|
||||
| PolishPreview | noodl-core-ui | Before/after comparison |
|
||||
|
||||
## Prompt Engineering
|
||||
|
||||
### System Prompts
|
||||
|
||||
```typescript
|
||||
// Scaffolding
|
||||
const SCAFFOLD_SYSTEM = `You are an expert Noodl application architect.
|
||||
Generate detailed project scaffolds for visual low-code applications.
|
||||
Consider: UX flow, data management, reusability, performance.`;
|
||||
|
||||
// Suggestions
|
||||
const SUGGESTION_SYSTEM = `You analyze Noodl components and suggest
|
||||
improvements. Focus on: pattern completion, best practices,
|
||||
common UI patterns, data handling.`;
|
||||
|
||||
// Natural Language
|
||||
const NL_SYSTEM = `You parse natural language commands for editing
|
||||
Noodl visual components. Output structured changes that can be
|
||||
applied to the node graph.`;
|
||||
|
||||
// Design
|
||||
const DESIGN_SYSTEM = `You are a design expert analyzing Noodl
|
||||
components for accessibility, consistency, and visual quality.
|
||||
Suggest concrete property changes.`;
|
||||
```
|
||||
|
||||
### Context Serialization
|
||||
|
||||
```typescript
|
||||
// Serialize component for AI context
|
||||
function serializeForAi(component: ComponentModel): string {
|
||||
return JSON.stringify({
|
||||
name: component.name,
|
||||
nodes: component.nodes.map(n => ({
|
||||
type: n.type.localName,
|
||||
id: n.id,
|
||||
parameters: n.parameters,
|
||||
children: n.children?.map(c => c.id)
|
||||
})),
|
||||
connections: component.connections.map(c => ({
|
||||
from: `${c.sourceNode.id}.${c.sourcePort}`,
|
||||
to: `${c.targetNode.id}.${c.targetPort}`
|
||||
}))
|
||||
}, null, 2);
|
||||
}
|
||||
```
|
||||
|
||||
## Performance Considerations
|
||||
|
||||
### Token Management
|
||||
- Keep prompts concise
|
||||
- Truncate large components
|
||||
- Cache common patterns locally
|
||||
- Batch similar requests
|
||||
|
||||
### Response Times
|
||||
- Scaffold generation: < 30 seconds
|
||||
- Suggestions: < 500ms (local), < 3s (AI)
|
||||
- NL parsing: < 3 seconds
|
||||
- Design analysis: < 5 seconds
|
||||
|
||||
### Offline Support
|
||||
- Local pattern library for suggestions
|
||||
- Cached design rules
|
||||
- Basic NL patterns
|
||||
- Graceful degradation
|
||||
|
||||
## Settings & Configuration
|
||||
|
||||
```typescript
|
||||
interface AiConfiguration {
|
||||
// API
|
||||
apiKey: string;
|
||||
apiEndpoint: string; // For custom/proxy
|
||||
model: string;
|
||||
|
||||
// Features
|
||||
features: {
|
||||
scaffolding: boolean;
|
||||
suggestions: boolean;
|
||||
naturalLanguage: boolean;
|
||||
designAssistance: boolean;
|
||||
};
|
||||
|
||||
// Suggestions
|
||||
suggestions: {
|
||||
enabled: boolean;
|
||||
frequency: 'always' | 'sometimes' | 'manual';
|
||||
showInline: boolean;
|
||||
showPanel: boolean;
|
||||
};
|
||||
|
||||
// Design
|
||||
design: {
|
||||
autoAnalyze: boolean;
|
||||
showInCanvas: boolean;
|
||||
strictAccessibility: boolean;
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
### Unit Tests
|
||||
- Prompt generation
|
||||
- Response parsing
|
||||
- Pattern matching
|
||||
- Change generation
|
||||
|
||||
### Integration Tests
|
||||
- Full scaffold flow
|
||||
- Suggestion pipeline
|
||||
- NL command execution
|
||||
- Design analysis
|
||||
|
||||
### Manual Testing
|
||||
- Various project descriptions
|
||||
- Edge case components
|
||||
- Complex NL commands
|
||||
- Accessibility scenarios
|
||||
|
||||
## Cline Usage Notes
|
||||
|
||||
### Before Starting Each Task
|
||||
|
||||
1. Read existing AI infrastructure:
|
||||
- `AiAssistantModel.ts`
|
||||
- Related AI components in `noodl-core-ui`
|
||||
2. Understand prompt patterns from existing templates
|
||||
3. Review how changes are applied to node graph
|
||||
|
||||
### Key Integration Points
|
||||
|
||||
1. **Node Graph**: All changes go through `NodeGraphModel`
|
||||
2. **Undo/Redo**: Must integrate with `UndoManager`
|
||||
3. **Project Model**: Scaffolds create full project structure
|
||||
4. **Settings**: Store in `EditorSettings`
|
||||
|
||||
### API Key Handling
|
||||
|
||||
- Never log API keys
|
||||
- Store securely (electron safeStorage)
|
||||
- Clear from memory after use
|
||||
- Support environment variable override
|
||||
|
||||
## Success Criteria (Series Complete)
|
||||
|
||||
1. ✅ Users can create projects from descriptions
|
||||
2. ✅ Contextual suggestions appear while building
|
||||
3. ✅ Natural language commands modify components
|
||||
4. ✅ Design issues automatically detected
|
||||
5. ✅ One-click fixes for common issues
|
||||
6. ✅ Works offline with reduced functionality
|
||||
|
||||
## Future Work (Post-AI Series)
|
||||
|
||||
The AI series enables:
|
||||
- **Voice Control**: Voice input for commands
|
||||
- **Image to Project**: Screenshot to scaffold
|
||||
- **Code Generation**: Export to React/Vue
|
||||
- **AI Debugging**: Debug logic issues
|
||||
- **Performance Optimization**: AI-suggested optimizations
|
||||
|
||||
## Files in This Series
|
||||
|
||||
- `AI-001-ai-project-scaffolding.md`
|
||||
- `AI-002-ai-component-suggestions.md`
|
||||
- `AI-003-natural-language-editing.md`
|
||||
- `AI-004-ai-design-assistance.md`
|
||||
- `AI-OVERVIEW.md` (this file)
|
||||
|
||||
## External Dependencies
|
||||
|
||||
### Anthropic API
|
||||
- Model: claude-sonnet-4-20250514 (default)
|
||||
- Rate limits: Handle gracefully
|
||||
- Costs: Optimize token usage
|
||||
|
||||
### No Additional Packages Required
|
||||
- Uses existing HTTP infrastructure
|
||||
- No additional AI libraries needed
|
||||
@@ -0,0 +1,579 @@
|
||||
# DEPLOY-001: One-Click Deploy Integrations
|
||||
|
||||
## Overview
|
||||
|
||||
Add one-click deployment to popular hosting platforms (Netlify, Vercel, GitHub Pages). Users can deploy their frontend directly from the editor without manual file handling or CLI tools.
|
||||
|
||||
## Context
|
||||
|
||||
Currently, deployment requires:
|
||||
1. Deploy to local folder
|
||||
2. Manually upload to hosting platform
|
||||
3. Configure hosting settings separately
|
||||
4. Repeat for every deployment
|
||||
|
||||
This friction discourages frequent deployments and makes it harder for non-technical users to share their work.
|
||||
|
||||
### Existing Infrastructure
|
||||
|
||||
From `deployer.ts`:
|
||||
```typescript
|
||||
export async function deployToFolder({
|
||||
project,
|
||||
direntry,
|
||||
environment,
|
||||
baseUrl,
|
||||
envVariables,
|
||||
runtimeType = 'deploy'
|
||||
}: DeployToFolderOptions)
|
||||
```
|
||||
|
||||
From `compilation.ts`:
|
||||
```typescript
|
||||
class Compilation {
|
||||
deployToFolder(direntry, options): Promise<void>;
|
||||
// Build scripts for pre/post deploy
|
||||
}
|
||||
```
|
||||
|
||||
From `DeployToFolderTab.tsx`:
|
||||
- Current UI for folder selection
|
||||
- Environment selection dropdown
|
||||
|
||||
## Requirements
|
||||
|
||||
### Functional Requirements
|
||||
|
||||
1. **Platform Integrations**
|
||||
- Netlify (OAuth + API)
|
||||
- Vercel (OAuth + API)
|
||||
- GitHub Pages (via GitHub API)
|
||||
- Cloudflare Pages (OAuth + API)
|
||||
|
||||
2. **Deploy Flow**
|
||||
- One-click deploy from editor
|
||||
- Platform selection dropdown
|
||||
- Site/project selection or creation
|
||||
- Environment variables configuration
|
||||
- Deploy progress indication
|
||||
|
||||
3. **Site Management**
|
||||
- List user's sites on each platform
|
||||
- Create new site from editor
|
||||
- Link project to existing site
|
||||
- View deployment history
|
||||
|
||||
4. **Configuration**
|
||||
- Environment variables per platform
|
||||
- Custom domain display
|
||||
- Build settings (if needed)
|
||||
- Deploy hooks
|
||||
|
||||
5. **Status & History**
|
||||
- Deploy status in editor
|
||||
- Link to live site
|
||||
- Deployment history
|
||||
- Rollback option (if supported)
|
||||
|
||||
### Non-Functional Requirements
|
||||
|
||||
- Deploy completes in < 2 minutes
|
||||
- Works with existing deploy-to-folder logic
|
||||
- Secure token storage
|
||||
- Clear error messages
|
||||
|
||||
## Technical Approach
|
||||
|
||||
### 1. Deploy Service Architecture
|
||||
|
||||
```typescript
|
||||
// packages/noodl-editor/src/editor/src/services/DeployService.ts
|
||||
|
||||
interface DeployTarget {
|
||||
id: string;
|
||||
name: string;
|
||||
platform: DeployPlatform;
|
||||
siteId: string;
|
||||
siteName: string;
|
||||
url: string;
|
||||
customDomain?: string;
|
||||
lastDeployedAt?: string;
|
||||
envVariables?: Record<string, string>;
|
||||
}
|
||||
|
||||
enum DeployPlatform {
|
||||
NETLIFY = 'netlify',
|
||||
VERCEL = 'vercel',
|
||||
GITHUB_PAGES = 'github_pages',
|
||||
CLOUDFLARE = 'cloudflare',
|
||||
LOCAL_FOLDER = 'local_folder'
|
||||
}
|
||||
|
||||
interface DeployResult {
|
||||
success: boolean;
|
||||
deployId: string;
|
||||
url: string;
|
||||
buildTime: number;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
class DeployService {
|
||||
private static instance: DeployService;
|
||||
private providers: Map<DeployPlatform, DeployProvider> = new Map();
|
||||
|
||||
// Provider management
|
||||
registerProvider(provider: DeployProvider): void;
|
||||
getProvider(platform: DeployPlatform): DeployProvider;
|
||||
|
||||
// Authentication
|
||||
async authenticate(platform: DeployPlatform): Promise<void>;
|
||||
async disconnect(platform: DeployPlatform): Promise<void>;
|
||||
isAuthenticated(platform: DeployPlatform): boolean;
|
||||
|
||||
// Site management
|
||||
async listSites(platform: DeployPlatform): Promise<Site[]>;
|
||||
async createSite(platform: DeployPlatform, name: string): Promise<Site>;
|
||||
async linkSite(project: ProjectModel, target: DeployTarget): Promise<void>;
|
||||
|
||||
// Deployment
|
||||
async deploy(project: ProjectModel, target: DeployTarget): Promise<DeployResult>;
|
||||
async getDeployStatus(deployId: string): Promise<DeployStatus>;
|
||||
async getDeployHistory(target: DeployTarget): Promise<Deployment[]>;
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Deploy Provider Interface
|
||||
|
||||
```typescript
|
||||
// packages/noodl-editor/src/editor/src/services/deploy/DeployProvider.ts
|
||||
|
||||
interface DeployProvider {
|
||||
readonly platform: DeployPlatform;
|
||||
readonly name: string;
|
||||
readonly icon: string;
|
||||
|
||||
// Authentication
|
||||
authenticate(): Promise<AuthResult>;
|
||||
disconnect(): Promise<void>;
|
||||
isAuthenticated(): boolean;
|
||||
getUser(): Promise<User | null>;
|
||||
|
||||
// Sites
|
||||
listSites(): Promise<Site[]>;
|
||||
createSite(name: string, options?: CreateSiteOptions): Promise<Site>;
|
||||
deleteSite(siteId: string): Promise<void>;
|
||||
|
||||
// Deployment
|
||||
deploy(siteId: string, files: DeployFiles): Promise<DeployResult>;
|
||||
getDeployStatus(deployId: string): Promise<DeployStatus>;
|
||||
getDeployHistory(siteId: string): Promise<Deployment[]>;
|
||||
cancelDeploy(deployId: string): Promise<void>;
|
||||
|
||||
// Configuration
|
||||
getEnvVariables(siteId: string): Promise<Record<string, string>>;
|
||||
setEnvVariables(siteId: string, vars: Record<string, string>): Promise<void>;
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Netlify Provider
|
||||
|
||||
```typescript
|
||||
// packages/noodl-editor/src/editor/src/services/deploy/providers/NetlifyProvider.ts
|
||||
|
||||
class NetlifyProvider implements DeployProvider {
|
||||
platform = DeployPlatform.NETLIFY;
|
||||
name = 'Netlify';
|
||||
icon = 'netlify-icon.svg';
|
||||
|
||||
private clientId = 'YOUR_NETLIFY_CLIENT_ID';
|
||||
private redirectUri = 'noodl://netlify-callback';
|
||||
private token: string | null = null;
|
||||
|
||||
async authenticate(): Promise<AuthResult> {
|
||||
// OAuth flow
|
||||
const authUrl = `https://app.netlify.com/authorize?` +
|
||||
`client_id=${this.clientId}&` +
|
||||
`response_type=token&` +
|
||||
`redirect_uri=${encodeURIComponent(this.redirectUri)}`;
|
||||
|
||||
// Open in browser, handle callback via deep link
|
||||
const token = await this.handleOAuthCallback(authUrl);
|
||||
this.token = token;
|
||||
|
||||
await this.storeToken(token);
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
async listSites(): Promise<Site[]> {
|
||||
const response = await fetch('https://api.netlify.com/api/v1/sites', {
|
||||
headers: { Authorization: `Bearer ${this.token}` }
|
||||
});
|
||||
|
||||
const sites = await response.json();
|
||||
return sites.map(s => ({
|
||||
id: s.id,
|
||||
name: s.name,
|
||||
url: s.ssl_url || s.url,
|
||||
customDomain: s.custom_domain,
|
||||
updatedAt: s.updated_at
|
||||
}));
|
||||
}
|
||||
|
||||
async createSite(name: string): Promise<Site> {
|
||||
const response = await fetch('https://api.netlify.com/api/v1/sites', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: `Bearer ${this.token}`,
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ name })
|
||||
});
|
||||
|
||||
const site = await response.json();
|
||||
return {
|
||||
id: site.id,
|
||||
name: site.name,
|
||||
url: site.ssl_url || site.url
|
||||
};
|
||||
}
|
||||
|
||||
async deploy(siteId: string, files: DeployFiles): Promise<DeployResult> {
|
||||
// Create deploy
|
||||
const deploy = await this.createDeploy(siteId);
|
||||
|
||||
// Upload files using Netlify's digest-based upload
|
||||
const fileHashes = await this.calculateHashes(files);
|
||||
const required = await this.getRequiredFiles(deploy.id, fileHashes);
|
||||
|
||||
for (const file of required) {
|
||||
await this.uploadFile(deploy.id, file);
|
||||
}
|
||||
|
||||
// Finalize deploy
|
||||
return await this.finalizeDeploy(deploy.id);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Vercel Provider
|
||||
|
||||
```typescript
|
||||
// packages/noodl-editor/src/editor/src/services/deploy/providers/VercelProvider.ts
|
||||
|
||||
class VercelProvider implements DeployProvider {
|
||||
platform = DeployPlatform.VERCEL;
|
||||
name = 'Vercel';
|
||||
icon = 'vercel-icon.svg';
|
||||
|
||||
private clientId = 'YOUR_VERCEL_CLIENT_ID';
|
||||
private token: string | null = null;
|
||||
|
||||
async authenticate(): Promise<AuthResult> {
|
||||
// Vercel uses OAuth 2.0
|
||||
const state = this.generateState();
|
||||
const authUrl = `https://vercel.com/integrations/noodl/new?` +
|
||||
`state=${state}`;
|
||||
|
||||
const token = await this.handleOAuthCallback(authUrl);
|
||||
this.token = token;
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
async deploy(projectId: string, files: DeployFiles): Promise<DeployResult> {
|
||||
// Vercel deployment API
|
||||
const deployment = await fetch('https://api.vercel.com/v13/deployments', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: `Bearer ${this.token}`,
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
name: projectId,
|
||||
files: await this.prepareFiles(files),
|
||||
projectSettings: {
|
||||
framework: null // Static site
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
const result = await deployment.json();
|
||||
return {
|
||||
success: true,
|
||||
deployId: result.id,
|
||||
url: `https://${result.url}`,
|
||||
buildTime: 0
|
||||
};
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 5. GitHub Pages Provider
|
||||
|
||||
```typescript
|
||||
// packages/noodl-editor/src/editor/src/services/deploy/providers/GitHubPagesProvider.ts
|
||||
|
||||
class GitHubPagesProvider implements DeployProvider {
|
||||
platform = DeployPlatform.GITHUB_PAGES;
|
||||
name = 'GitHub Pages';
|
||||
icon = 'github-icon.svg';
|
||||
|
||||
async authenticate(): Promise<AuthResult> {
|
||||
// Reuse GitHub OAuth from GIT-001
|
||||
const githubService = GitHubOAuthService.instance;
|
||||
if (!githubService.isAuthenticated()) {
|
||||
await githubService.authenticate();
|
||||
}
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
async listSites(): Promise<Site[]> {
|
||||
// List repos with GitHub Pages enabled
|
||||
const repos = await this.githubApi.listRepos();
|
||||
const pagesRepos = repos.filter(r => r.has_pages);
|
||||
|
||||
return pagesRepos.map(r => ({
|
||||
id: r.full_name,
|
||||
name: r.name,
|
||||
url: `https://${r.owner.login}.github.io/${r.name}`,
|
||||
repo: r.full_name
|
||||
}));
|
||||
}
|
||||
|
||||
async deploy(repoFullName: string, files: DeployFiles): Promise<DeployResult> {
|
||||
const [owner, repo] = repoFullName.split('/');
|
||||
|
||||
// Create/update gh-pages branch
|
||||
const branch = 'gh-pages';
|
||||
|
||||
// Get current tree (if exists)
|
||||
let baseTree: string | null = null;
|
||||
try {
|
||||
const ref = await this.githubApi.getRef(owner, repo, `heads/${branch}`);
|
||||
const commit = await this.githubApi.getCommit(owner, repo, ref.object.sha);
|
||||
baseTree = commit.tree.sha;
|
||||
} catch {
|
||||
// Branch doesn't exist yet
|
||||
}
|
||||
|
||||
// Create blobs for all files
|
||||
const tree = await this.createTree(owner, repo, files, baseTree);
|
||||
|
||||
// Create commit
|
||||
const commit = await this.githubApi.createCommit(owner, repo, {
|
||||
message: 'Deploy from Noodl',
|
||||
tree: tree.sha,
|
||||
parents: baseTree ? [baseTree] : []
|
||||
});
|
||||
|
||||
// Update branch reference
|
||||
await this.githubApi.updateRef(owner, repo, `heads/${branch}`, commit.sha);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
deployId: commit.sha,
|
||||
url: `https://${owner}.github.io/${repo}`,
|
||||
buildTime: 0
|
||||
};
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 6. UI Components
|
||||
|
||||
#### Deploy Panel
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ Deploy [×] │
|
||||
├─────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ DEPLOY TARGET │
|
||||
│ ┌─────────────────────────────────────────────────────────────────┐ │
|
||||
│ │ 🌐 Netlify [▾] │ │
|
||||
│ └─────────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ SITE │
|
||||
│ ┌─────────────────────────────────────────────────────────────────┐ │
|
||||
│ │ my-noodl-app [▾] │ │
|
||||
│ │ https://my-noodl-app.netlify.app │ │
|
||||
│ └─────────────────────────────────────────────────────────────────┘ │
|
||||
│ [+ Create New Site] │
|
||||
│ │
|
||||
│ ENVIRONMENT │
|
||||
│ ┌─────────────────────────────────────────────────────────────────┐ │
|
||||
│ │ Production [▾] │ │
|
||||
│ └─────────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ┌─────────────────────────────────────────────────────────────────┐ │
|
||||
│ │ Last deployed: 2 hours ago │ │
|
||||
│ │ Deploy time: 45 seconds │ │
|
||||
│ │ [View Site ↗] [View Deploy History] │ │
|
||||
│ └─────────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ [🚀 Deploy Now] │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
#### Deploy Progress
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ Deploying to Netlify... │
|
||||
├─────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ████████████░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 35% │
|
||||
│ │
|
||||
│ ✓ Building project │
|
||||
│ ✓ Exporting files (127 files) │
|
||||
│ ◐ Uploading to Netlify... │
|
||||
│ ○ Finalizing deploy │
|
||||
│ │
|
||||
│ [Cancel] │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Files to Create
|
||||
|
||||
1. `packages/noodl-editor/src/editor/src/services/DeployService.ts`
|
||||
2. `packages/noodl-editor/src/editor/src/services/deploy/DeployProvider.ts`
|
||||
3. `packages/noodl-editor/src/editor/src/services/deploy/providers/NetlifyProvider.ts`
|
||||
4. `packages/noodl-editor/src/editor/src/services/deploy/providers/VercelProvider.ts`
|
||||
5. `packages/noodl-editor/src/editor/src/services/deploy/providers/GitHubPagesProvider.ts`
|
||||
6. `packages/noodl-editor/src/editor/src/services/deploy/providers/CloudflareProvider.ts`
|
||||
7. `packages/noodl-core-ui/src/components/deploy/DeployPanel/DeployPanel.tsx`
|
||||
8. `packages/noodl-core-ui/src/components/deploy/DeployProgress/DeployProgress.tsx`
|
||||
9. `packages/noodl-core-ui/src/components/deploy/SiteSelector/SiteSelector.tsx`
|
||||
10. `packages/noodl-core-ui/src/components/deploy/PlatformSelector/PlatformSelector.tsx`
|
||||
|
||||
## Files to Modify
|
||||
|
||||
1. `packages/noodl-editor/src/editor/src/views/DeployPopup/DeployPopup.tsx`
|
||||
- Add platform tabs
|
||||
- Integrate new deploy flow
|
||||
|
||||
2. `packages/noodl-editor/src/editor/src/utils/compilation/compilation.ts`
|
||||
- Add deploy to platform method
|
||||
- Hook into build scripts
|
||||
|
||||
3. `packages/noodl-editor/src/main/src/main.js`
|
||||
- Add deep link handlers for OAuth callbacks
|
||||
|
||||
4. `packages/noodl-editor/src/editor/src/models/projectmodel.ts`
|
||||
- Store deploy target configuration
|
||||
|
||||
## Implementation Steps
|
||||
|
||||
### Phase 1: Service Architecture
|
||||
1. Create DeployService
|
||||
2. Define DeployProvider interface
|
||||
3. Implement provider registration
|
||||
4. Set up token storage
|
||||
|
||||
### Phase 2: Netlify Integration
|
||||
1. Implement NetlifyProvider
|
||||
2. Add OAuth flow
|
||||
3. Implement site listing
|
||||
4. Implement deployment
|
||||
|
||||
### Phase 3: Vercel Integration
|
||||
1. Implement VercelProvider
|
||||
2. Add OAuth flow
|
||||
3. Implement deployment
|
||||
|
||||
### Phase 4: GitHub Pages Integration
|
||||
1. Implement GitHubPagesProvider
|
||||
2. Reuse GitHub OAuth
|
||||
3. Implement gh-pages deployment
|
||||
|
||||
### Phase 5: UI Components
|
||||
1. Create DeployPanel
|
||||
2. Create platform/site selectors
|
||||
3. Create progress indicator
|
||||
4. Integrate with existing popup
|
||||
|
||||
### Phase 6: Testing & Polish
|
||||
1. Test each provider
|
||||
2. Error handling
|
||||
3. Progress accuracy
|
||||
4. Deploy history
|
||||
|
||||
## Testing Checklist
|
||||
|
||||
- [ ] Netlify OAuth works
|
||||
- [ ] Netlify site listing works
|
||||
- [ ] Netlify deployment succeeds
|
||||
- [ ] Vercel OAuth works
|
||||
- [ ] Vercel deployment succeeds
|
||||
- [ ] GitHub Pages deployment works
|
||||
- [ ] Progress indicator accurate
|
||||
- [ ] Error messages helpful
|
||||
- [ ] Deploy history shows
|
||||
- [ ] Site links work
|
||||
- [ ] Token storage secure
|
||||
- [ ] Disconnect works
|
||||
|
||||
## Dependencies
|
||||
|
||||
- GIT-001 (GitHub OAuth) - for GitHub Pages
|
||||
|
||||
## Blocked By
|
||||
|
||||
- None (can start immediately)
|
||||
|
||||
## Blocks
|
||||
|
||||
- DEPLOY-002 (Preview Deployments)
|
||||
- DEPLOY-003 (Deploy Settings)
|
||||
|
||||
## Estimated Effort
|
||||
|
||||
- Service architecture: 4-5 hours
|
||||
- Netlify provider: 5-6 hours
|
||||
- Vercel provider: 4-5 hours
|
||||
- GitHub Pages provider: 4-5 hours
|
||||
- Cloudflare provider: 4-5 hours
|
||||
- UI components: 5-6 hours
|
||||
- Testing & polish: 4-5 hours
|
||||
- **Total: 30-37 hours**
|
||||
|
||||
## Success Criteria
|
||||
|
||||
1. One-click deploy to Netlify works
|
||||
2. One-click deploy to Vercel works
|
||||
3. One-click deploy to GitHub Pages works
|
||||
4. Site creation from editor works
|
||||
5. Deploy progress visible
|
||||
6. Deploy history accessible
|
||||
|
||||
## Platform-Specific Notes
|
||||
|
||||
### Netlify
|
||||
- Uses digest-based uploads (efficient)
|
||||
- Supports deploy previews (branch deploys)
|
||||
- Has good API documentation
|
||||
- Free tier: 100GB bandwidth/month
|
||||
|
||||
### Vercel
|
||||
- File-based deployment API
|
||||
- Automatic HTTPS
|
||||
- Edge functions support
|
||||
- Free tier: 100GB bandwidth/month
|
||||
|
||||
### GitHub Pages
|
||||
- No OAuth app needed (reuse GitHub)
|
||||
- Limited to public repos on free tier
|
||||
- Jekyll processing (can disable with .nojekyll)
|
||||
- Free for public repos
|
||||
|
||||
### Cloudflare Pages
|
||||
- Similar to Netlify/Vercel
|
||||
- Global CDN
|
||||
- Free tier generous
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
- AWS S3 + CloudFront
|
||||
- Firebase Hosting
|
||||
- Surge.sh
|
||||
- Custom server deployment (SFTP/SSH)
|
||||
- Docker container deployment
|
||||
@@ -0,0 +1,510 @@
|
||||
# DEPLOY-002: Preview Deployments
|
||||
|
||||
## Overview
|
||||
|
||||
Enable automatic preview deployments for each git branch or commit. When users push changes, a preview URL is automatically generated so stakeholders can review before merging to production.
|
||||
|
||||
## Context
|
||||
|
||||
Currently, sharing work-in-progress requires:
|
||||
1. Manual deploy to a staging site
|
||||
2. Share URL with stakeholders
|
||||
3. Remember which deploy corresponds to which version
|
||||
4. Manually clean up old deploys
|
||||
|
||||
Preview deployments provide:
|
||||
- Automatic URL per branch/PR
|
||||
- Easy sharing with stakeholders
|
||||
- Visual history of changes
|
||||
- Automatic cleanup
|
||||
|
||||
This is especially valuable for:
|
||||
- Design reviews
|
||||
- QA testing
|
||||
- Client approvals
|
||||
- Team collaboration
|
||||
|
||||
### Integration with GIT Series
|
||||
|
||||
From GIT-002:
|
||||
- Git status tracking per project
|
||||
- Branch awareness
|
||||
- Commit detection
|
||||
|
||||
This task leverages that infrastructure to trigger preview deploys.
|
||||
|
||||
## Requirements
|
||||
|
||||
### Functional Requirements
|
||||
|
||||
1. **Automatic Previews**
|
||||
- Deploy preview on branch push
|
||||
- Unique URL per branch
|
||||
- Update preview on new commits
|
||||
- Delete preview on branch delete
|
||||
|
||||
2. **Manual Previews**
|
||||
- "Deploy Preview" button in editor
|
||||
- Generate shareable URL
|
||||
- Named previews (optional)
|
||||
- Expiration settings
|
||||
|
||||
3. **Preview Management**
|
||||
- List all active previews
|
||||
- View preview URL
|
||||
- Delete individual previews
|
||||
- Set auto-cleanup rules
|
||||
|
||||
4. **Sharing**
|
||||
- Copy preview URL
|
||||
- QR code for mobile
|
||||
- Optional password protection
|
||||
- Expiration timer
|
||||
|
||||
5. **Integration with PRs**
|
||||
- Comment preview URL on PR
|
||||
- Update comment on new commits
|
||||
- Status check integration
|
||||
|
||||
### Non-Functional Requirements
|
||||
|
||||
- Preview available within 2 minutes
|
||||
- Support 10+ concurrent previews
|
||||
- Auto-cleanup after configurable period
|
||||
- Works with all deploy providers
|
||||
|
||||
## Technical Approach
|
||||
|
||||
### 1. Preview Service
|
||||
|
||||
```typescript
|
||||
// packages/noodl-editor/src/editor/src/services/PreviewDeployService.ts
|
||||
|
||||
interface PreviewDeployment {
|
||||
id: string;
|
||||
projectId: string;
|
||||
branch: string;
|
||||
commitSha: string;
|
||||
url: string;
|
||||
platform: DeployPlatform;
|
||||
siteId: string;
|
||||
status: PreviewStatus;
|
||||
createdAt: string;
|
||||
expiresAt?: string;
|
||||
password?: string;
|
||||
name?: string;
|
||||
}
|
||||
|
||||
enum PreviewStatus {
|
||||
PENDING = 'pending',
|
||||
BUILDING = 'building',
|
||||
READY = 'ready',
|
||||
FAILED = 'failed',
|
||||
EXPIRED = 'expired'
|
||||
}
|
||||
|
||||
interface PreviewConfig {
|
||||
enabled: boolean;
|
||||
autoDeployBranches: boolean;
|
||||
excludeBranches: string[]; // e.g., ['main', 'master']
|
||||
expirationDays: number;
|
||||
maxPreviews: number;
|
||||
passwordProtect: boolean;
|
||||
commentOnPR: boolean;
|
||||
}
|
||||
|
||||
class PreviewDeployService {
|
||||
private static instance: PreviewDeployService;
|
||||
|
||||
// Preview management
|
||||
async createPreview(options: CreatePreviewOptions): Promise<PreviewDeployment>;
|
||||
async updatePreview(previewId: string): Promise<PreviewDeployment>;
|
||||
async deletePreview(previewId: string): Promise<void>;
|
||||
async listPreviews(projectId: string): Promise<PreviewDeployment[]>;
|
||||
|
||||
// Auto-deployment
|
||||
async onBranchPush(projectId: string, branch: string, commitSha: string): Promise<void>;
|
||||
async onBranchDelete(projectId: string, branch: string): Promise<void>;
|
||||
|
||||
// PR integration
|
||||
async commentOnPR(preview: PreviewDeployment): Promise<void>;
|
||||
async updatePRComment(preview: PreviewDeployment): Promise<void>;
|
||||
|
||||
// Cleanup
|
||||
async cleanupExpiredPreviews(): Promise<void>;
|
||||
async enforceMaxPreviews(projectId: string): Promise<void>;
|
||||
|
||||
// Configuration
|
||||
getConfig(projectId: string): PreviewConfig;
|
||||
setConfig(projectId: string, config: Partial<PreviewConfig>): void;
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Branch-Based Preview Naming
|
||||
|
||||
```typescript
|
||||
// Generate preview URLs based on branch
|
||||
function generatePreviewUrl(platform: DeployPlatform, branch: string, projectName: string): string {
|
||||
const sanitizedBranch = sanitizeBranchName(branch);
|
||||
|
||||
switch (platform) {
|
||||
case DeployPlatform.NETLIFY:
|
||||
// Netlify: branch--sitename.netlify.app
|
||||
return `https://${sanitizedBranch}--${projectName}.netlify.app`;
|
||||
|
||||
case DeployPlatform.VERCEL:
|
||||
// Vercel: project-branch-hash.vercel.app
|
||||
return `https://${projectName}-${sanitizedBranch}.vercel.app`;
|
||||
|
||||
case DeployPlatform.GITHUB_PAGES:
|
||||
// GitHub Pages: use subdirectory or separate branch
|
||||
return `https://${owner}.github.io/${repo}/preview/${sanitizedBranch}`;
|
||||
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
function sanitizeBranchName(branch: string): string {
|
||||
return branch
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z0-9-]/g, '-')
|
||||
.replace(/-+/g, '-')
|
||||
.substring(0, 50);
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Git Integration Hook
|
||||
|
||||
```typescript
|
||||
// packages/noodl-editor/src/editor/src/services/PreviewDeployService.ts
|
||||
|
||||
// Hook into git operations
|
||||
class PreviewDeployService {
|
||||
constructor() {
|
||||
// Listen for git events
|
||||
EventDispatcher.instance.on('git.push.success', this.handlePush.bind(this));
|
||||
EventDispatcher.instance.on('git.branch.delete', this.handleBranchDelete.bind(this));
|
||||
}
|
||||
|
||||
private async handlePush(event: GitPushEvent): Promise<void> {
|
||||
const config = this.getConfig(event.projectId);
|
||||
|
||||
if (!config.enabled || !config.autoDeployBranches) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if branch is excluded
|
||||
if (config.excludeBranches.includes(event.branch)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if we already have a preview for this branch
|
||||
const existing = await this.findPreviewByBranch(event.projectId, event.branch);
|
||||
|
||||
if (existing) {
|
||||
// Update existing preview
|
||||
await this.updatePreview(existing.id);
|
||||
} else {
|
||||
// Create new preview
|
||||
await this.createPreview({
|
||||
projectId: event.projectId,
|
||||
branch: event.branch,
|
||||
commitSha: event.commitSha
|
||||
});
|
||||
}
|
||||
|
||||
// Comment on PR if enabled
|
||||
if (config.commentOnPR) {
|
||||
const pr = await this.findPRForBranch(event.projectId, event.branch);
|
||||
if (pr) {
|
||||
await this.commentOnPR(existing || await this.getLatestPreview(event.projectId, event.branch));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async handleBranchDelete(event: GitBranchDeleteEvent): Promise<void> {
|
||||
const preview = await this.findPreviewByBranch(event.projectId, event.branch);
|
||||
if (preview) {
|
||||
await this.deletePreview(preview.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. PR Comment Integration
|
||||
|
||||
```typescript
|
||||
// packages/noodl-editor/src/editor/src/services/PreviewDeployService.ts
|
||||
|
||||
async commentOnPR(preview: PreviewDeployment): Promise<void> {
|
||||
const github = GitHubApiClient.instance;
|
||||
const project = ProjectModel.instance;
|
||||
const remote = project.getRemoteUrl();
|
||||
|
||||
if (!remote || !remote.includes('github.com')) {
|
||||
return; // Only GitHub PRs supported
|
||||
}
|
||||
|
||||
const { owner, repo } = parseGitHubUrl(remote);
|
||||
const pr = await github.findPRByBranch(owner, repo, preview.branch);
|
||||
|
||||
if (!pr) {
|
||||
return;
|
||||
}
|
||||
|
||||
const commentBody = this.generatePRComment(preview);
|
||||
|
||||
// Check for existing Noodl comment
|
||||
const existingComment = await github.findComment(owner, repo, pr.number, '<!-- noodl-preview -->');
|
||||
|
||||
if (existingComment) {
|
||||
await github.updateComment(owner, repo, existingComment.id, commentBody);
|
||||
} else {
|
||||
await github.createComment(owner, repo, pr.number, commentBody);
|
||||
}
|
||||
}
|
||||
|
||||
private generatePRComment(preview: PreviewDeployment): string {
|
||||
return `<!-- noodl-preview -->
|
||||
## 🚀 Noodl Preview Deployment
|
||||
|
||||
| Status | URL |
|
||||
|--------|-----|
|
||||
| ${this.getStatusEmoji(preview.status)} ${preview.status} | [${preview.url}](${preview.url}) |
|
||||
|
||||
**Branch:** \`${preview.branch}\`
|
||||
**Commit:** \`${preview.commitSha.substring(0, 7)}\`
|
||||
**Updated:** ${new Date(preview.createdAt).toLocaleString()}
|
||||
|
||||
---
|
||||
<sub>Deployed automatically by Noodl</sub>`;
|
||||
}
|
||||
```
|
||||
|
||||
### 5. UI Components
|
||||
|
||||
#### Preview Manager Panel
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ Preview Deployments [×] │
|
||||
├─────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ACTIVE PREVIEWS (3) │
|
||||
│ │
|
||||
│ ┌─────────────────────────────────────────────────────────────────┐ │
|
||||
│ │ 🌿 feature/new-dashboard [Copy URL] │ │
|
||||
│ │ https://feature-new-dashboard--myapp.netlify.app │ │
|
||||
│ │ Updated 10 minutes ago • Commit abc1234 │ │
|
||||
│ │ [Open ↗] [QR Code] [Delete] │ │
|
||||
│ ├─────────────────────────────────────────────────────────────────┤ │
|
||||
│ │ 🌿 feature/login-redesign [Copy URL] │ │
|
||||
│ │ https://feature-login-redesign--myapp.netlify.app │ │
|
||||
│ │ Updated 2 hours ago • Commit def5678 │ │
|
||||
│ │ [Open ↗] [QR Code] [Delete] │ │
|
||||
│ ├─────────────────────────────────────────────────────────────────┤ │
|
||||
│ │ 🌿 bugfix/form-validation [Copy URL] │ │
|
||||
│ │ https://bugfix-form-validation--myapp.netlify.app │ │
|
||||
│ │ Updated yesterday • Commit ghi9012 │ │
|
||||
│ │ [Open ↗] [QR Code] [Delete] │ │
|
||||
│ └─────────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ SETTINGS │
|
||||
│ ☑ Auto-deploy branches │
|
||||
│ ☑ Comment preview URL on PRs │
|
||||
│ Exclude branches: main, master │
|
||||
│ Auto-delete after: [7 days ▾] │
|
||||
│ │
|
||||
│ [+ Create Manual Preview] │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
#### QR Code Modal
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ Share Preview [×] │
|
||||
├─────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ┌─────────────────┐ │
|
||||
│ │ ▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄│ │
|
||||
│ │ █ █ █ █│ │
|
||||
│ │ █ ███ █ █ ███ █│ Scan to open │
|
||||
│ │ █ █ █ █│ on mobile │
|
||||
│ │ ▀▀▀▀▀▀▀ ▀▀▀▀▀▀▀│ │
|
||||
│ └─────────────────┘ │
|
||||
│ │
|
||||
│ feature/new-dashboard │
|
||||
│ https://feature-new-dashboard--myapp.netlify.app │
|
||||
│ │
|
||||
│ [Copy URL] [Download QR] [Close] │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
#### Create Manual Preview
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ Create Preview [×] │
|
||||
├─────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ Preview Name (optional): │
|
||||
│ ┌─────────────────────────────────────────────────────────────────┐ │
|
||||
│ │ client-review-dec-15 │ │
|
||||
│ └─────────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ Expires in: │
|
||||
│ ┌─────────────────────────────────────────────────────────────────┐ │
|
||||
│ │ 7 days [▾] │ │
|
||||
│ └─────────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ☐ Password protect │
|
||||
│ Password: [•••••••• ] │
|
||||
│ │
|
||||
│ Deploy from: │
|
||||
│ ○ Current state (uncommitted changes included) │
|
||||
│ ● Current branch (feature/new-dashboard) │
|
||||
│ ○ Specific commit: [abc1234 ▾] │
|
||||
│ │
|
||||
│ [Cancel] [Create Preview] │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Files to Create
|
||||
|
||||
1. `packages/noodl-editor/src/editor/src/services/PreviewDeployService.ts`
|
||||
2. `packages/noodl-core-ui/src/components/deploy/PreviewManager/PreviewManager.tsx`
|
||||
3. `packages/noodl-core-ui/src/components/deploy/PreviewCard/PreviewCard.tsx`
|
||||
4. `packages/noodl-core-ui/src/components/deploy/CreatePreviewModal/CreatePreviewModal.tsx`
|
||||
5. `packages/noodl-core-ui/src/components/deploy/QRCodeModal/QRCodeModal.tsx`
|
||||
6. `packages/noodl-core-ui/src/components/deploy/PreviewSettings/PreviewSettings.tsx`
|
||||
|
||||
## Files to Modify
|
||||
|
||||
1. `packages/noodl-editor/src/editor/src/services/DeployService.ts`
|
||||
- Add preview deployment methods
|
||||
- Integrate with deploy providers
|
||||
|
||||
2. `packages/noodl-editor/src/editor/src/views/DeployPopup/DeployPopup.tsx`
|
||||
- Add "Previews" tab
|
||||
- Integrate PreviewManager
|
||||
|
||||
3. `packages/noodl-editor/src/editor/src/models/projectmodel.ts`
|
||||
- Store preview configuration
|
||||
- Track active previews
|
||||
|
||||
4. `packages/noodl-editor/src/editor/src/services/GitHubApiClient.ts`
|
||||
- Add PR comment methods
|
||||
- Add PR lookup by branch
|
||||
|
||||
## Implementation Steps
|
||||
|
||||
### Phase 1: Preview Service
|
||||
1. Create PreviewDeployService
|
||||
2. Implement preview creation
|
||||
3. Implement preview deletion
|
||||
4. Add configuration storage
|
||||
|
||||
### Phase 2: Git Integration
|
||||
1. Hook into push events
|
||||
2. Hook into branch delete events
|
||||
3. Implement auto-deployment
|
||||
4. Test with branches
|
||||
|
||||
### Phase 3: PR Integration
|
||||
1. Implement PR comment creation
|
||||
2. Implement comment updating
|
||||
3. Add status emoji handling
|
||||
4. Test with GitHub PRs
|
||||
|
||||
### Phase 4: UI - Preview Manager
|
||||
1. Create PreviewManager component
|
||||
2. Create PreviewCard component
|
||||
3. Add copy/share functionality
|
||||
4. Implement delete action
|
||||
|
||||
### Phase 5: UI - Create Preview
|
||||
1. Create CreatePreviewModal
|
||||
2. Add expiration options
|
||||
3. Add password protection
|
||||
4. Add source selection
|
||||
|
||||
### Phase 6: UI - QR & Sharing
|
||||
1. Create QRCodeModal
|
||||
2. Add QR code generation
|
||||
3. Add download option
|
||||
4. Polish sharing UX
|
||||
|
||||
## Testing Checklist
|
||||
|
||||
- [ ] Auto-preview on push works
|
||||
- [ ] Preview URL is correct
|
||||
- [ ] PR comment created
|
||||
- [ ] PR comment updated on new commit
|
||||
- [ ] Manual preview creation works
|
||||
- [ ] Preview deletion works
|
||||
- [ ] Auto-cleanup works
|
||||
- [ ] QR code generates correctly
|
||||
- [ ] Password protection works
|
||||
- [ ] Expiration works
|
||||
- [ ] Multiple previews supported
|
||||
|
||||
## Dependencies
|
||||
|
||||
- DEPLOY-001 (One-Click Deploy) - for deploy providers
|
||||
- GIT-001 (GitHub OAuth) - for PR comments
|
||||
- GIT-002 (Git Status) - for branch awareness
|
||||
|
||||
## Blocked By
|
||||
|
||||
- DEPLOY-001
|
||||
|
||||
## Blocks
|
||||
|
||||
- None
|
||||
|
||||
## Estimated Effort
|
||||
|
||||
- Preview service: 5-6 hours
|
||||
- Git integration: 4-5 hours
|
||||
- PR integration: 3-4 hours
|
||||
- UI preview manager: 4-5 hours
|
||||
- UI create preview: 3-4 hours
|
||||
- UI QR/sharing: 2-3 hours
|
||||
- Testing & polish: 3-4 hours
|
||||
- **Total: 24-31 hours**
|
||||
|
||||
## Success Criteria
|
||||
|
||||
1. Auto-preview deploys on branch push
|
||||
2. Preview URL unique per branch
|
||||
3. PR comments posted automatically
|
||||
4. Manual previews can be created
|
||||
5. QR codes work for mobile testing
|
||||
6. Expired previews auto-cleaned
|
||||
|
||||
## Platform-Specific Implementation
|
||||
|
||||
### Netlify
|
||||
- Branch deploys built-in
|
||||
- URL pattern: `branch--site.netlify.app`
|
||||
- Easy configuration
|
||||
|
||||
### Vercel
|
||||
- Preview deployments automatic
|
||||
- URL pattern: `project-branch-hash.vercel.app`
|
||||
- Good GitHub integration
|
||||
|
||||
### GitHub Pages
|
||||
- Need separate approach (subdirectory or deploy to different branch)
|
||||
- Less native support for branch previews
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
- Visual regression testing
|
||||
- Screenshot comparison
|
||||
- Performance metrics per preview
|
||||
- A/B testing setup
|
||||
- Preview environments (staging, QA)
|
||||
- Slack/Teams notifications
|
||||
@@ -0,0 +1,533 @@
|
||||
# DEPLOY-003: Deploy Settings & Environment Variables
|
||||
|
||||
## Overview
|
||||
|
||||
Provide comprehensive deployment configuration including environment variables, build settings, custom domains, and deployment rules. Users can manage different environments (development, staging, production) with different configurations.
|
||||
|
||||
## Context
|
||||
|
||||
Currently:
|
||||
- Environment variables set per deploy manually
|
||||
- No persistent environment configuration
|
||||
- No distinction between environments
|
||||
- Custom domain setup requires external configuration
|
||||
|
||||
This task adds:
|
||||
- Persistent environment variable management
|
||||
- Multiple environment profiles
|
||||
- Custom domain configuration
|
||||
- Build optimization settings
|
||||
- Deploy rules and triggers
|
||||
|
||||
## Requirements
|
||||
|
||||
### Functional Requirements
|
||||
|
||||
1. **Environment Variables**
|
||||
- Add/edit/delete variables
|
||||
- Sensitive variable masking
|
||||
- Import from .env file
|
||||
- Export to .env file
|
||||
- Variable validation
|
||||
|
||||
2. **Environment Profiles**
|
||||
- Development, Staging, Production presets
|
||||
- Custom profiles
|
||||
- Variables per profile
|
||||
- Easy switching
|
||||
|
||||
3. **Custom Domains**
|
||||
- View current domains
|
||||
- Add custom domain
|
||||
- SSL certificate status
|
||||
- DNS configuration help
|
||||
|
||||
4. **Build Settings**
|
||||
- Output directory
|
||||
- Base URL configuration
|
||||
- Asset optimization
|
||||
- Source maps (dev only)
|
||||
|
||||
5. **Deploy Rules**
|
||||
- Auto-deploy on push
|
||||
- Branch-based rules
|
||||
- Deploy schedule
|
||||
- Deploy hooks/webhooks
|
||||
|
||||
### Non-Functional Requirements
|
||||
|
||||
- Variables encrypted at rest
|
||||
- Sensitive values never logged
|
||||
- Sync with platform settings
|
||||
- Works offline (cached)
|
||||
|
||||
## Technical Approach
|
||||
|
||||
### 1. Environment Configuration Service
|
||||
|
||||
```typescript
|
||||
// packages/noodl-editor/src/editor/src/services/EnvironmentConfigService.ts
|
||||
|
||||
interface EnvironmentVariable {
|
||||
key: string;
|
||||
value: string;
|
||||
sensitive: boolean; // Masked in UI
|
||||
scope: VariableScope;
|
||||
}
|
||||
|
||||
enum VariableScope {
|
||||
BUILD = 'build', // Available during build
|
||||
RUNTIME = 'runtime', // Injected into app
|
||||
BOTH = 'both'
|
||||
}
|
||||
|
||||
interface EnvironmentProfile {
|
||||
id: string;
|
||||
name: string;
|
||||
description?: string;
|
||||
variables: EnvironmentVariable[];
|
||||
isDefault: boolean;
|
||||
platform?: DeployPlatform; // If linked to a platform
|
||||
}
|
||||
|
||||
interface DeploySettings {
|
||||
outputDirectory: string;
|
||||
baseUrl: string;
|
||||
assetOptimization: boolean;
|
||||
sourceMaps: boolean;
|
||||
cleanUrls: boolean;
|
||||
trailingSlash: boolean;
|
||||
}
|
||||
|
||||
class EnvironmentConfigService {
|
||||
private static instance: EnvironmentConfigService;
|
||||
|
||||
// Profiles
|
||||
async getProfiles(projectId: string): Promise<EnvironmentProfile[]>;
|
||||
async createProfile(projectId: string, profile: Omit<EnvironmentProfile, 'id'>): Promise<EnvironmentProfile>;
|
||||
async updateProfile(projectId: string, profileId: string, updates: Partial<EnvironmentProfile>): Promise<void>;
|
||||
async deleteProfile(projectId: string, profileId: string): Promise<void>;
|
||||
|
||||
// Variables
|
||||
async getVariables(projectId: string, profileId: string): Promise<EnvironmentVariable[]>;
|
||||
async setVariable(projectId: string, profileId: string, variable: EnvironmentVariable): Promise<void>;
|
||||
async deleteVariable(projectId: string, profileId: string, key: string): Promise<void>;
|
||||
async importFromEnvFile(projectId: string, profileId: string, content: string): Promise<void>;
|
||||
async exportToEnvFile(projectId: string, profileId: string): Promise<string>;
|
||||
|
||||
// Build settings
|
||||
async getDeploySettings(projectId: string): Promise<DeploySettings>;
|
||||
async updateDeploySettings(projectId: string, settings: Partial<DeploySettings>): Promise<void>;
|
||||
|
||||
// Platform sync
|
||||
async syncWithPlatform(projectId: string, profileId: string, platform: DeployPlatform): Promise<void>;
|
||||
async pullFromPlatform(projectId: string, platform: DeployPlatform): Promise<EnvironmentVariable[]>;
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Environment Storage
|
||||
|
||||
```typescript
|
||||
// Store in project metadata, encrypted
|
||||
interface ProjectDeployConfig {
|
||||
profiles: EnvironmentProfile[];
|
||||
activeProfileId: string;
|
||||
deploySettings: DeploySettings;
|
||||
domains: CustomDomain[];
|
||||
deployRules: DeployRule[];
|
||||
}
|
||||
|
||||
// Encryption for sensitive values
|
||||
class SecureStorage {
|
||||
async encrypt(value: string): Promise<string>;
|
||||
async decrypt(value: string): Promise<string>;
|
||||
}
|
||||
|
||||
// Store encrypted in project.json
|
||||
{
|
||||
"metadata": {
|
||||
"deployConfig": {
|
||||
"profiles": [
|
||||
{
|
||||
"id": "prod",
|
||||
"name": "Production",
|
||||
"variables": [
|
||||
{
|
||||
"key": "API_URL",
|
||||
"value": "https://api.example.com", // Plain text
|
||||
"sensitive": false
|
||||
},
|
||||
{
|
||||
"key": "API_KEY",
|
||||
"value": "encrypted:abc123...", // Encrypted
|
||||
"sensitive": true
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Domain Configuration
|
||||
|
||||
```typescript
|
||||
interface CustomDomain {
|
||||
domain: string;
|
||||
platform: DeployPlatform;
|
||||
siteId: string;
|
||||
status: DomainStatus;
|
||||
sslStatus: SSLStatus;
|
||||
dnsRecords?: DNSRecord[];
|
||||
}
|
||||
|
||||
enum DomainStatus {
|
||||
PENDING = 'pending',
|
||||
ACTIVE = 'active',
|
||||
FAILED = 'failed'
|
||||
}
|
||||
|
||||
enum SSLStatus {
|
||||
PENDING = 'pending',
|
||||
ACTIVE = 'active',
|
||||
EXPIRED = 'expired',
|
||||
FAILED = 'failed'
|
||||
}
|
||||
|
||||
interface DNSRecord {
|
||||
type: 'A' | 'CNAME' | 'TXT';
|
||||
name: string;
|
||||
value: string;
|
||||
required: boolean;
|
||||
}
|
||||
|
||||
class DomainService {
|
||||
async addDomain(siteId: string, domain: string): Promise<CustomDomain>;
|
||||
async verifyDomain(domainId: string): Promise<DomainStatus>;
|
||||
async getDNSInstructions(domain: string): Promise<DNSRecord[]>;
|
||||
async checkSSL(domainId: string): Promise<SSLStatus>;
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Deploy Rules
|
||||
|
||||
```typescript
|
||||
interface DeployRule {
|
||||
id: string;
|
||||
name: string;
|
||||
enabled: boolean;
|
||||
trigger: DeployTrigger;
|
||||
conditions: DeployCondition[];
|
||||
actions: DeployAction[];
|
||||
}
|
||||
|
||||
interface DeployTrigger {
|
||||
type: 'push' | 'schedule' | 'manual' | 'webhook';
|
||||
config: PushConfig | ScheduleConfig | WebhookConfig;
|
||||
}
|
||||
|
||||
interface PushConfig {
|
||||
branches: string[]; // Glob patterns
|
||||
paths?: string[]; // Only deploy if these paths changed
|
||||
}
|
||||
|
||||
interface ScheduleConfig {
|
||||
cron: string; // Cron expression
|
||||
timezone: string;
|
||||
}
|
||||
|
||||
interface DeployCondition {
|
||||
type: 'branch' | 'tag' | 'path' | 'message';
|
||||
operator: 'equals' | 'contains' | 'matches';
|
||||
value: string;
|
||||
}
|
||||
|
||||
interface DeployAction {
|
||||
type: 'deploy' | 'notify' | 'webhook';
|
||||
config: DeployActionConfig | NotifyConfig | WebhookConfig;
|
||||
}
|
||||
```
|
||||
|
||||
### 5. UI Components
|
||||
|
||||
#### Environment Variables Panel
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ Environment Variables [×] │
|
||||
├─────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ Profile: [Production ▾] [+ New Profile] │
|
||||
│ │
|
||||
│ VARIABLES [Import .env] │
|
||||
│ ┌─────────────────────────────────────────────────────────────────┐ │
|
||||
│ │ Key Value Scope [⋯] │ │
|
||||
│ ├─────────────────────────────────────────────────────────────────┤ │
|
||||
│ │ API_URL https://api.example.com Runtime [✎🗑] │ │
|
||||
│ │ API_KEY •••••••••••••••••••••• Runtime [✎🗑] │ │
|
||||
│ │ ANALYTICS_ID UA-12345678-1 Runtime [✎🗑] │ │
|
||||
│ │ DEBUG false Build [✎🗑] │ │
|
||||
│ └─────────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ [+ Add Variable] [Export .env] │
|
||||
│ │
|
||||
│ ☑ Sync with Netlify │
|
||||
│ Last synced: 5 minutes ago [Sync Now] │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
#### Build Settings Panel
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ Build Settings [×] │
|
||||
├─────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ OUTPUT │
|
||||
│ Output Directory: [dist ] │
|
||||
│ Base URL: [/ ] │
|
||||
│ │
|
||||
│ OPTIMIZATION │
|
||||
│ ☑ Optimize assets (minify JS/CSS) │
|
||||
│ ☐ Generate source maps (increases build size) │
|
||||
│ ☑ Clean URLs (remove .html extension) │
|
||||
│ ☐ Trailing slash on URLs │
|
||||
│ │
|
||||
│ ADVANCED │
|
||||
│ Build Command: [npm run build ] │
|
||||
│ Publish Directory: [build ] │
|
||||
│ │
|
||||
│ NODE VERSION │
|
||||
│ ┌─────────────────────────────────────────────────────────────────┐ │
|
||||
│ │ 18 (LTS) [▾] │ │
|
||||
│ └─────────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ [Save Settings] │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
#### Custom Domains Panel
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ Custom Domains [×] │
|
||||
├─────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ CONNECTED DOMAINS │
|
||||
│ ┌─────────────────────────────────────────────────────────────────┐ │
|
||||
│ │ 🌐 myapp.com │ │
|
||||
│ │ ✓ DNS Configured ✓ SSL Active │ │
|
||||
│ │ Primary domain [Remove] │ │
|
||||
│ ├─────────────────────────────────────────────────────────────────┤ │
|
||||
│ │ 🌐 www.myapp.com │ │
|
||||
│ │ ✓ DNS Configured ✓ SSL Active │ │
|
||||
│ │ Redirects to myapp.com [Remove] │ │
|
||||
│ └─────────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ [+ Add Custom Domain] │
|
||||
│ │
|
||||
│ DEFAULT DOMAIN │
|
||||
│ https://myapp.netlify.app [Copy] │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
#### Add Domain Modal
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ Add Custom Domain [×] │
|
||||
├─────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ Domain: │
|
||||
│ ┌─────────────────────────────────────────────────────────────────┐ │
|
||||
│ │ app.example.com │ │
|
||||
│ └─────────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ DNS CONFIGURATION REQUIRED │
|
||||
│ │
|
||||
│ Add these records to your DNS provider: │
|
||||
│ │
|
||||
│ ┌─────────────────────────────────────────────────────────────────┐ │
|
||||
│ │ Type Name Value [Copy] │ │
|
||||
│ ├─────────────────────────────────────────────────────────────────┤ │
|
||||
│ │ CNAME app myapp.netlify.app [Copy] │ │
|
||||
│ └─────────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ⚠️ DNS changes can take up to 48 hours to propagate │
|
||||
│ │
|
||||
│ Status: ⏳ Waiting for DNS verification... │
|
||||
│ │
|
||||
│ [Cancel] [Verify Domain] [Done] │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
#### Deploy Rules Panel
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ Deploy Rules [×] │
|
||||
├─────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ RULES [+ Add Rule] │
|
||||
│ ┌─────────────────────────────────────────────────────────────────┐ │
|
||||
│ │ ☑ Auto-deploy production │ │
|
||||
│ │ When: Push to main │ │
|
||||
│ │ Deploy to: Production [Edit 🗑] │ │
|
||||
│ ├─────────────────────────────────────────────────────────────────┤ │
|
||||
│ │ ☑ Preview branches │ │
|
||||
│ │ When: Push to feature/* │ │
|
||||
│ │ Deploy to: Preview [Edit 🗑] │ │
|
||||
│ ├─────────────────────────────────────────────────────────────────┤ │
|
||||
│ │ ☐ Scheduled deploy │ │
|
||||
│ │ When: Daily at 2:00 AM UTC │ │
|
||||
│ │ Deploy to: Production [Edit 🗑] │ │
|
||||
│ └─────────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ WEBHOOKS [+ Add Webhook] │
|
||||
│ ┌─────────────────────────────────────────────────────────────────┐ │
|
||||
│ │ Build hook URL: │ │
|
||||
│ │ https://api.netlify.com/build_hooks/abc123 [Copy] [🔄] │ │
|
||||
│ │ Trigger: POST request to this URL │ │
|
||||
│ └─────────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Files to Create
|
||||
|
||||
1. `packages/noodl-editor/src/editor/src/services/EnvironmentConfigService.ts`
|
||||
2. `packages/noodl-editor/src/editor/src/services/DomainService.ts`
|
||||
3. `packages/noodl-editor/src/editor/src/services/DeployRulesService.ts`
|
||||
4. `packages/noodl-core-ui/src/components/deploy/EnvironmentVariables/EnvironmentVariables.tsx`
|
||||
5. `packages/noodl-core-ui/src/components/deploy/EnvironmentVariables/VariableRow.tsx`
|
||||
6. `packages/noodl-core-ui/src/components/deploy/EnvironmentVariables/ProfileSelector.tsx`
|
||||
7. `packages/noodl-core-ui/src/components/deploy/BuildSettings/BuildSettings.tsx`
|
||||
8. `packages/noodl-core-ui/src/components/deploy/CustomDomains/CustomDomains.tsx`
|
||||
9. `packages/noodl-core-ui/src/components/deploy/CustomDomains/AddDomainModal.tsx`
|
||||
10. `packages/noodl-core-ui/src/components/deploy/DeployRules/DeployRules.tsx`
|
||||
11. `packages/noodl-core-ui/src/components/deploy/DeployRules/RuleEditor.tsx`
|
||||
|
||||
## Files to Modify
|
||||
|
||||
1. `packages/noodl-editor/src/editor/src/views/DeployPopup/DeployPopup.tsx`
|
||||
- Add settings tabs
|
||||
- Integrate new panels
|
||||
|
||||
2. `packages/noodl-editor/src/editor/src/utils/compilation/compilation.ts`
|
||||
- Use environment variables from config
|
||||
- Apply build settings
|
||||
|
||||
3. `packages/noodl-editor/src/editor/src/models/projectmodel.ts`
|
||||
- Store deploy configuration
|
||||
- Load/save config
|
||||
|
||||
4. `packages/noodl-editor/src/editor/src/services/DeployService.ts`
|
||||
- Apply environment variables to deploy
|
||||
- Handle domain configuration
|
||||
|
||||
## Implementation Steps
|
||||
|
||||
### Phase 1: Environment Variables
|
||||
1. Create EnvironmentConfigService
|
||||
2. Implement variable storage
|
||||
3. Implement encryption for sensitive values
|
||||
4. Add import/export .env
|
||||
|
||||
### Phase 2: Environment Profiles
|
||||
1. Add profile management
|
||||
2. Implement profile switching
|
||||
3. Add default profiles (dev/staging/prod)
|
||||
4. UI for profile management
|
||||
|
||||
### Phase 3: UI - Variables Panel
|
||||
1. Create EnvironmentVariables component
|
||||
2. Create VariableRow component
|
||||
3. Add add/edit/delete functionality
|
||||
4. Add import/export buttons
|
||||
|
||||
### Phase 4: Build Settings
|
||||
1. Create build settings storage
|
||||
2. Create BuildSettings component
|
||||
3. Integrate with compilation
|
||||
4. Test with deployments
|
||||
|
||||
### Phase 5: Custom Domains
|
||||
1. Create DomainService
|
||||
2. Implement platform-specific domain APIs
|
||||
3. Create CustomDomains component
|
||||
4. Create AddDomainModal
|
||||
|
||||
### Phase 6: Deploy Rules
|
||||
1. Create DeployRulesService
|
||||
2. Implement rule evaluation
|
||||
3. Create DeployRules component
|
||||
4. Create RuleEditor
|
||||
|
||||
## Testing Checklist
|
||||
|
||||
- [ ] Variables saved correctly
|
||||
- [ ] Sensitive values encrypted
|
||||
- [ ] Variables applied to deploy
|
||||
- [ ] Import .env works
|
||||
- [ ] Export .env works
|
||||
- [ ] Profile switching works
|
||||
- [ ] Build settings applied
|
||||
- [ ] Custom domain setup works
|
||||
- [ ] DNS verification works
|
||||
- [ ] Deploy rules trigger correctly
|
||||
- [ ] Webhooks work
|
||||
- [ ] Platform sync works
|
||||
|
||||
## Dependencies
|
||||
|
||||
- DEPLOY-001 (One-Click Deploy) - for platform integration
|
||||
|
||||
## Blocked By
|
||||
|
||||
- DEPLOY-001
|
||||
|
||||
## Blocks
|
||||
|
||||
- None (final DEPLOY task)
|
||||
|
||||
## Estimated Effort
|
||||
|
||||
- Environment config service: 4-5 hours
|
||||
- Variable storage/encryption: 3-4 hours
|
||||
- Environment profiles: 3-4 hours
|
||||
- UI variables panel: 4-5 hours
|
||||
- Build settings: 3-4 hours
|
||||
- Custom domains: 4-5 hours
|
||||
- Deploy rules: 4-5 hours
|
||||
- Testing & polish: 3-4 hours
|
||||
- **Total: 28-36 hours**
|
||||
|
||||
## Success Criteria
|
||||
|
||||
1. Environment variables persist across deploys
|
||||
2. Sensitive values properly secured
|
||||
3. Multiple profiles supported
|
||||
4. Import/export .env works
|
||||
5. Custom domains configurable
|
||||
6. Deploy rules automate deployments
|
||||
7. Settings sync with platforms
|
||||
|
||||
## Security Considerations
|
||||
|
||||
- Sensitive values encrypted at rest
|
||||
- Never log sensitive values
|
||||
- Use platform-native secret storage where available
|
||||
- Clear memory after use
|
||||
- Validate input to prevent injection
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
- Environment variable inheritance
|
||||
- Secret rotation reminders
|
||||
- Integration with secret managers (Vault, AWS Secrets)
|
||||
- A/B testing configuration
|
||||
- Feature flags integration
|
||||
- Monitoring/alerting integration
|
||||
@@ -0,0 +1,385 @@
|
||||
# DEPLOY Series: Deployment Automation
|
||||
|
||||
## Overview
|
||||
|
||||
The DEPLOY series transforms Noodl's deployment from manual folder exports into a modern, automated deployment pipeline. Users can deploy to popular hosting platforms with one click, get automatic preview URLs for each branch, and manage environment variables and domains directly from the editor.
|
||||
|
||||
## Target Environment
|
||||
|
||||
- **Editor**: React 19 version only
|
||||
- **Platforms**: Netlify, Vercel, GitHub Pages, Cloudflare Pages
|
||||
- **Backwards Compatibility**: Existing deploy-to-folder continues to work
|
||||
|
||||
## Task Dependency Graph
|
||||
|
||||
```
|
||||
DEPLOY-001 (One-Click Deploy)
|
||||
│
|
||||
├─────────────────────┐
|
||||
↓ ↓
|
||||
DEPLOY-002 (Previews) DEPLOY-003 (Settings)
|
||||
```
|
||||
|
||||
## Task Summary
|
||||
|
||||
| Task ID | Name | Est. Hours | Priority |
|
||||
|---------|------|------------|----------|
|
||||
| DEPLOY-001 | One-Click Deploy Integrations | 30-37 | Critical |
|
||||
| DEPLOY-002 | Preview Deployments | 24-31 | High |
|
||||
| DEPLOY-003 | Deploy Settings & Environment Variables | 28-36 | High |
|
||||
|
||||
**Total Estimated: 82-104 hours**
|
||||
|
||||
## Implementation Order
|
||||
|
||||
### Phase 1: Core Deployment (Weeks 1-2)
|
||||
1. **DEPLOY-001** - One-click deploy to platforms
|
||||
- Establishes provider architecture
|
||||
- OAuth flows for each platform
|
||||
- Core deployment functionality
|
||||
|
||||
### Phase 2: Preview & Settings (Weeks 3-4)
|
||||
2. **DEPLOY-002** - Preview deployments
|
||||
- Branch-based previews
|
||||
- PR integration
|
||||
- Sharing features
|
||||
|
||||
3. **DEPLOY-003** - Deploy settings
|
||||
- Environment variables
|
||||
- Custom domains
|
||||
- Deploy rules
|
||||
|
||||
## Existing Infrastructure
|
||||
|
||||
### Deployer
|
||||
|
||||
```typescript
|
||||
// packages/noodl-editor/src/editor/src/utils/compilation/build/deployer.ts
|
||||
export async function deployToFolder({
|
||||
project,
|
||||
direntry,
|
||||
environment,
|
||||
baseUrl,
|
||||
envVariables,
|
||||
runtimeType = 'deploy'
|
||||
}: DeployToFolderOptions)
|
||||
```
|
||||
|
||||
### Compilation
|
||||
|
||||
```typescript
|
||||
// packages/noodl-editor/src/editor/src/utils/compilation/compilation.ts
|
||||
class Compilation {
|
||||
deployToFolder(direntry, options): Promise<void>;
|
||||
// Build scripts system for pre/post deploy
|
||||
}
|
||||
```
|
||||
|
||||
### Deploy UI
|
||||
|
||||
```typescript
|
||||
// Current deploy popup with folder selection
|
||||
DeployToFolderTab.tsx
|
||||
DeployPopup.tsx
|
||||
```
|
||||
|
||||
### Cloud Services
|
||||
|
||||
```typescript
|
||||
// packages/noodl-editor/src/editor/src/models/CloudServices.ts
|
||||
interface Environment {
|
||||
id: string;
|
||||
name: string;
|
||||
url: string;
|
||||
appId: string;
|
||||
masterKey?: string;
|
||||
}
|
||||
```
|
||||
|
||||
## New Architecture
|
||||
|
||||
### Service Layer
|
||||
|
||||
```
|
||||
packages/noodl-editor/src/editor/src/services/
|
||||
├── DeployService.ts # Central deployment service
|
||||
├── PreviewDeployService.ts # Preview management
|
||||
├── EnvironmentConfigService.ts # Env vars & profiles
|
||||
├── DomainService.ts # Custom domain management
|
||||
├── DeployRulesService.ts # Automation rules
|
||||
└── deploy/
|
||||
├── DeployProvider.ts # Provider interface
|
||||
└── providers/
|
||||
├── NetlifyProvider.ts
|
||||
├── VercelProvider.ts
|
||||
├── GitHubPagesProvider.ts
|
||||
└── CloudflareProvider.ts
|
||||
```
|
||||
|
||||
### Provider Interface
|
||||
|
||||
```typescript
|
||||
interface DeployProvider {
|
||||
readonly platform: DeployPlatform;
|
||||
readonly name: string;
|
||||
|
||||
// Authentication
|
||||
authenticate(): Promise<AuthResult>;
|
||||
isAuthenticated(): boolean;
|
||||
|
||||
// Sites
|
||||
listSites(): Promise<Site[]>;
|
||||
createSite(name: string): Promise<Site>;
|
||||
|
||||
// Deployment
|
||||
deploy(siteId: string, files: DeployFiles): Promise<DeployResult>;
|
||||
getDeployStatus(deployId: string): Promise<DeployStatus>;
|
||||
|
||||
// Configuration
|
||||
getEnvVariables(siteId: string): Promise<Record<string, string>>;
|
||||
setEnvVariables(siteId: string, vars: Record<string, string>): Promise<void>;
|
||||
}
|
||||
```
|
||||
|
||||
## Platform Comparison
|
||||
|
||||
| Feature | Netlify | Vercel | GitHub Pages | Cloudflare |
|
||||
|---------|---------|--------|--------------|------------|
|
||||
| OAuth | ✓ | ✓ | Via GitHub | ✓ |
|
||||
| Preview Deploys | ✓ | ✓ | Manual | ✓ |
|
||||
| Custom Domains | ✓ | ✓ | ✓ | ✓ |
|
||||
| Env Variables | ✓ | ✓ | Secrets only | ✓ |
|
||||
| Deploy Hooks | ✓ | ✓ | Actions | ✓ |
|
||||
| Free Tier | 100GB/mo | 100GB/mo | Unlimited* | 100K/day |
|
||||
|
||||
*GitHub Pages: Free for public repos, requires Pro for private
|
||||
|
||||
## Key User Flows
|
||||
|
||||
### 1. First-Time Deploy
|
||||
|
||||
```
|
||||
User clicks "Deploy"
|
||||
↓
|
||||
Select platform (Netlify, Vercel, etc.)
|
||||
↓
|
||||
Authenticate with platform (OAuth)
|
||||
↓
|
||||
Create new site or select existing
|
||||
↓
|
||||
Configure environment variables
|
||||
↓
|
||||
Deploy → Get live URL
|
||||
```
|
||||
|
||||
### 2. Subsequent Deploys
|
||||
|
||||
```
|
||||
User clicks "Deploy"
|
||||
↓
|
||||
Site already linked
|
||||
↓
|
||||
One-click deploy
|
||||
↓
|
||||
Progress indicator
|
||||
↓
|
||||
Success → Link to site
|
||||
```
|
||||
|
||||
### 3. Preview Workflow
|
||||
|
||||
```
|
||||
User pushes feature branch
|
||||
↓
|
||||
Auto-deploy preview
|
||||
↓
|
||||
Comment on PR with preview URL
|
||||
↓
|
||||
Stakeholder reviews
|
||||
↓
|
||||
Merge → Production deploy
|
||||
↓
|
||||
Preview auto-cleaned
|
||||
```
|
||||
|
||||
## UI Components to Create
|
||||
|
||||
| Component | Package | Purpose |
|
||||
|-----------|---------|---------|
|
||||
| DeployPanel | noodl-core-ui | Main deploy interface |
|
||||
| PlatformSelector | noodl-core-ui | Platform choice |
|
||||
| SiteSelector | noodl-core-ui | Site choice |
|
||||
| DeployProgress | noodl-core-ui | Progress indicator |
|
||||
| PreviewManager | noodl-core-ui | Preview list |
|
||||
| EnvironmentVariables | noodl-core-ui | Var management |
|
||||
| CustomDomains | noodl-core-ui | Domain setup |
|
||||
| DeployRules | noodl-core-ui | Automation rules |
|
||||
|
||||
## Data Models
|
||||
|
||||
### Deploy Target
|
||||
|
||||
```typescript
|
||||
interface DeployTarget {
|
||||
id: string;
|
||||
name: string;
|
||||
platform: DeployPlatform;
|
||||
siteId: string;
|
||||
siteName: string;
|
||||
url: string;
|
||||
customDomain?: string;
|
||||
lastDeployedAt?: string;
|
||||
}
|
||||
```
|
||||
|
||||
### Preview Deployment
|
||||
|
||||
```typescript
|
||||
interface PreviewDeployment {
|
||||
id: string;
|
||||
projectId: string;
|
||||
branch: string;
|
||||
commitSha: string;
|
||||
url: string;
|
||||
status: PreviewStatus;
|
||||
createdAt: string;
|
||||
expiresAt?: string;
|
||||
}
|
||||
```
|
||||
|
||||
### Environment Profile
|
||||
|
||||
```typescript
|
||||
interface EnvironmentProfile {
|
||||
id: string;
|
||||
name: string;
|
||||
variables: EnvironmentVariable[];
|
||||
isDefault: boolean;
|
||||
}
|
||||
|
||||
interface EnvironmentVariable {
|
||||
key: string;
|
||||
value: string;
|
||||
sensitive: boolean;
|
||||
}
|
||||
```
|
||||
|
||||
## Security Considerations
|
||||
|
||||
### Token Storage
|
||||
- OAuth tokens stored in electron safeStorage
|
||||
- Never logged or displayed
|
||||
- Cleared on disconnect
|
||||
|
||||
### Sensitive Variables
|
||||
- Encrypted at rest
|
||||
- Masked in UI (•••••)
|
||||
- Never exported to .env without warning
|
||||
|
||||
### Platform Security
|
||||
- Minimum OAuth scopes
|
||||
- Token refresh handling
|
||||
- Secure redirect handling
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
### Unit Tests
|
||||
- Provider method isolation
|
||||
- Token handling
|
||||
- File preparation
|
||||
|
||||
### Integration Tests
|
||||
- OAuth flow mocking
|
||||
- Deploy API mocking
|
||||
- Full deploy cycle
|
||||
|
||||
### Manual Testing
|
||||
- Real deployments to each platform
|
||||
- Custom domain setup
|
||||
- Preview workflow
|
||||
|
||||
## Cline Usage Notes
|
||||
|
||||
### Before Starting Each Task
|
||||
|
||||
1. Review existing deployment infrastructure:
|
||||
- `deployer.ts`
|
||||
- `compilation.ts`
|
||||
- `DeployPopup/`
|
||||
|
||||
2. Understand build output:
|
||||
- How project.json is exported
|
||||
- How bundles are created
|
||||
- How index.html is generated
|
||||
|
||||
### Key Integration Points
|
||||
|
||||
1. **Compilation**: Use existing `deployToFolder` for file preparation
|
||||
2. **Cloud Services**: Existing environment model can inform design
|
||||
3. **Git Integration**: Leverage GIT series for branch awareness
|
||||
|
||||
### Platform API Notes
|
||||
|
||||
- **Netlify**: Uses digest-based upload (SHA1 hashes)
|
||||
- **Vercel**: File-based deployment API
|
||||
- **GitHub Pages**: Git-based via GitHub API
|
||||
- **Cloudflare**: Similar to Netlify/Vercel
|
||||
|
||||
## Success Criteria (Series Complete)
|
||||
|
||||
1. ✅ One-click deploy to 4 platforms
|
||||
2. ✅ OAuth authentication flow works
|
||||
3. ✅ Site creation from editor works
|
||||
4. ✅ Preview deploys auto-generated
|
||||
5. ✅ PR comments posted automatically
|
||||
6. ✅ Environment variables managed
|
||||
7. ✅ Custom domains configurable
|
||||
8. ✅ Deploy rules automate workflow
|
||||
|
||||
## Future Work (Post-DEPLOY)
|
||||
|
||||
The DEPLOY series enables:
|
||||
- **CI/CD Integration**: Connect to GitHub Actions, GitLab CI
|
||||
- **Performance Monitoring**: Lighthouse scores per deploy
|
||||
- **A/B Testing**: Deploy variants to subsets
|
||||
- **Rollback**: One-click rollback to previous deploy
|
||||
- **Multi-Region**: Deploy to multiple regions
|
||||
|
||||
## Files in This Series
|
||||
|
||||
- `DEPLOY-001-one-click-deploy.md`
|
||||
- `DEPLOY-002-preview-deployments.md`
|
||||
- `DEPLOY-003-deploy-settings.md`
|
||||
- `DEPLOY-OVERVIEW.md` (this file)
|
||||
|
||||
## External Dependencies
|
||||
|
||||
### Platform OAuth
|
||||
|
||||
| Platform | OAuth Type | Client ID Required |
|
||||
|----------|------------|-------------------|
|
||||
| Netlify | OAuth 2.0 | Yes |
|
||||
| Vercel | OAuth 2.0 | Yes |
|
||||
| GitHub | OAuth 2.0 | From GIT-001 |
|
||||
| Cloudflare | OAuth 2.0 | Yes |
|
||||
|
||||
### API Endpoints
|
||||
|
||||
- Netlify: `api.netlify.com/api/v1`
|
||||
- Vercel: `api.vercel.com/v13`
|
||||
- GitHub: `api.github.com`
|
||||
- Cloudflare: `api.cloudflare.com/client/v4`
|
||||
|
||||
## Complete Roadmap Summary
|
||||
|
||||
With the DEPLOY series complete, the full OpenNoodl modernization roadmap includes:
|
||||
|
||||
| Series | Tasks | Hours | Focus |
|
||||
|--------|-------|-------|-------|
|
||||
| DASH | 4 tasks | 43-63 | Dashboard UX |
|
||||
| GIT | 5 tasks | 68-96 | Git integration |
|
||||
| COMP | 6 tasks | 117-155 | Shared components |
|
||||
| AI | 4 tasks | 121-154 | AI assistance |
|
||||
| DEPLOY | 3 tasks | 82-104 | Deployment |
|
||||
|
||||
**Grand Total: 22 tasks, 431-572 hours**
|
||||
Reference in New Issue
Block a user