Started tasks to migrate runtime to React 19. Added phase 3 projects

This commit is contained in:
Richard Osborne
2025-12-13 22:37:44 +01:00
parent 8dd4f395c0
commit 1477a29ff7
55 changed files with 49205 additions and 281 deletions

View 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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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)