mirror of
https://github.com/The-Low-Code-Foundation/OpenNoodl.git
synced 2026-01-12 07:12:54 +01:00
Started tasks to migrate runtime to React 19. Added phase 3 projects
This commit is contained in:
@@ -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)
|
||||
Reference in New Issue
Block a user