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

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

View File

@@ -0,0 +1,335 @@
# GIT-001: GitHub OAuth Integration
## Overview
Add GitHub OAuth as an authentication method alongside the existing Personal Access Token (PAT) approach. This provides a smoother onboarding experience and enables access to GitHub's API for advanced features like repository browsing and organization access.
## Context
Currently, Noodl uses Personal Access Tokens for GitHub authentication:
- Stored per-project in `GitStore` (encrypted locally)
- Prompted via `GitProviderPopout` component
- Used by `trampoline-askpass-handler` for git operations
OAuth provides advantages:
- No need to manually create and copy PATs
- Automatic token refresh
- Access to GitHub API (not just git operations)
- Org/repo scope selection
## Current State
### Existing Authentication Flow
```
User → GitProviderPopout → Enter PAT → GitStore.set() → Git operations use PAT
```
### Key Files
- `packages/noodl-editor/src/editor/src/views/panels/VersionControlPanel/components/GitProviderPopout/`
- `packages/noodl-store/src/GitStore.ts` (assumed location)
- `packages/noodl-git/src/core/trampoline/trampoline-askpass-handler.ts`
## Requirements
### Functional Requirements
1. **OAuth Flow**
- "Connect with GitHub" button in settings/dashboard
- Opens GitHub OAuth in system browser
- Handles callback via custom protocol (`noodl://github-callback`)
- Exchanges code for access token
- Stores token securely
2. **Scope Selection**
- Request appropriate scopes: `repo`, `read:org`, `read:user`
- Display what permissions are being requested
- Option to request additional scopes later
3. **Account Management**
- Show connected GitHub account (avatar, username)
- "Disconnect" option
- Support multiple accounts (stretch goal)
4. **Organization Access**
- List user's organizations
- Allow selecting which orgs to access
- Remember org selection
5. **Token Management**
- Secure storage using electron's safeStorage or keytar
- Automatic token refresh (GitHub OAuth tokens don't expire but can be revoked)
- Handle token revocation gracefully
6. **Fallback to PAT**
- Keep existing PAT flow as alternative
- "Use Personal Access Token instead" option
- Clear migration path from PAT to OAuth
### Non-Functional Requirements
- OAuth flow completes in <30 seconds
- Token stored securely (encrypted at rest)
- Works behind corporate proxies
- Graceful offline handling
## Technical Approach
### 1. GitHub OAuth App Setup
Register OAuth App in GitHub:
- Application name: "OpenNoodl"
- Homepage URL: `https://opennoodl.net`
- Callback URL: `noodl://github-callback`
Store Client ID in app (Client Secret not needed for public clients using PKCE).
### 2. OAuth Flow Implementation
```typescript
// packages/noodl-editor/src/editor/src/services/GitHubOAuthService.ts
class GitHubOAuthService {
private static instance: GitHubOAuthService;
// OAuth flow
async initiateOAuth(): Promise<void>;
async handleCallback(code: string, state: string): Promise<GitHubToken>;
// Token management
async getToken(): Promise<string | null>;
async refreshToken(): Promise<string>;
async revokeToken(): Promise<void>;
// Account info
async getCurrentUser(): Promise<GitHubUser>;
async getOrganizations(): Promise<GitHubOrg[]>;
// State
isAuthenticated(): boolean;
onAuthStateChanged(callback: (authenticated: boolean) => void): void;
}
```
### 3. PKCE Flow (Recommended for Desktop Apps)
```typescript
// Generate PKCE challenge
function generatePKCE(): { verifier: string; challenge: string } {
const verifier = crypto.randomBytes(32).toString('base64url');
const challenge = crypto
.createHash('sha256')
.update(verifier)
.digest('base64url');
return { verifier, challenge };
}
// OAuth URL
function getAuthorizationUrl(state: string, challenge: string): string {
const params = new URLSearchParams({
client_id: GITHUB_CLIENT_ID,
redirect_uri: 'noodl://github-callback',
scope: 'repo read:org read:user',
state,
code_challenge: challenge,
code_challenge_method: 'S256'
});
return `https://github.com/login/oauth/authorize?${params}`;
}
```
### 4. Deep Link Handler
```typescript
// packages/noodl-editor/src/main/main.js
// Register protocol handler
app.setAsDefaultProtocolClient('noodl');
// Handle deep links
app.on('open-url', (event, url) => {
event.preventDefault();
if (url.startsWith('noodl://github-callback')) {
const params = new URL(url).searchParams;
const code = params.get('code');
const state = params.get('state');
handleGitHubCallback(code, state);
}
});
```
### 5. Secure Token Storage
```typescript
// Use electron's safeStorage API
import { safeStorage } from 'electron';
async function storeToken(token: string): Promise<void> {
const encrypted = safeStorage.encryptString(token);
await store.set('github.token', encrypted.toString('base64'));
}
async function getToken(): Promise<string | null> {
const encrypted = await store.get('github.token');
if (!encrypted) return null;
return safeStorage.decryptString(Buffer.from(encrypted, 'base64'));
}
```
### 6. Integration with Existing Git Auth
```typescript
// packages/noodl-utils/LocalProjectsModel.ts
setCurrentGlobalGitAuth(projectId: string) {
const func = async (endpoint: string) => {
if (endpoint.includes('github.com')) {
// Try OAuth token first
const oauthToken = await GitHubOAuthService.instance.getToken();
if (oauthToken) {
return {
username: 'oauth2',
password: oauthToken
};
}
// Fall back to PAT
const config = await GitStore.get('github', projectId);
return {
username: 'noodl',
password: config?.password
};
}
// ... rest of existing logic
};
setRequestGitAccount(func);
}
```
## Files to Create
1. `packages/noodl-editor/src/editor/src/services/GitHubOAuthService.ts`
2. `packages/noodl-editor/src/editor/src/services/GitHubApiClient.ts`
3. `packages/noodl-core-ui/src/preview/launcher/Launcher/components/GitHubAccountCard/GitHubAccountCard.tsx`
4. `packages/noodl-core-ui/src/preview/launcher/Launcher/components/GitHubConnectButton/GitHubConnectButton.tsx`
5. `packages/noodl-core-ui/src/preview/launcher/Launcher/components/OrgSelector/OrgSelector.tsx`
6. `packages/noodl-editor/src/editor/src/views/panels/VersionControlPanel/components/GitProviderPopout/sections/OAuthSection.tsx`
## Files to Modify
1. `packages/noodl-editor/src/main/main.js`
- Add deep link protocol handler for `noodl://`
2. `packages/noodl-utils/LocalProjectsModel.ts`
- Update `setCurrentGlobalGitAuth` to prefer OAuth token
3. `packages/noodl-editor/src/editor/src/views/panels/VersionControlPanel/components/GitProviderPopout/GitProviderPopout.tsx`
- Add OAuth option alongside PAT
4. `packages/noodl-core-ui/src/preview/launcher/Launcher/components/LauncherSidebar/LauncherSidebar.tsx`
- Add GitHub account display/connect button
## Implementation Steps
### Phase 1: OAuth Service Foundation
1. Create GitHubOAuthService class
2. Implement PKCE flow
3. Set up deep link handler in main process
4. Implement secure token storage
### Phase 2: UI Components
1. Create GitHubConnectButton
2. Create GitHubAccountCard
3. Add OAuth section to GitProviderPopout
4. Add account display to launcher sidebar
### Phase 3: API Integration
1. Create GitHubApiClient for REST API calls
2. Implement user info fetching
3. Implement organization listing
4. Create OrgSelector component
### Phase 4: Git Integration
1. Update LocalProjectsModel auth function
2. Test with git operations
3. Handle token expiry/revocation
4. Add fallback to PAT
### Phase 5: Polish
1. Error handling and messages
2. Offline handling
3. Loading states
4. Settings persistence
## Security Considerations
1. **PKCE**: Use PKCE flow instead of client secret (more secure for desktop apps)
2. **Token Storage**: Use electron's safeStorage API (OS-level encryption)
3. **State Parameter**: Verify state to prevent CSRF attacks
4. **Scope Limitation**: Request minimum required scopes
5. **Token Exposure**: Never log tokens, clear from memory when not needed
## Testing Checklist
- [ ] OAuth flow completes successfully
- [ ] Token stored securely
- [ ] Token retrieved correctly for git operations
- [ ] Clone works with OAuth token
- [ ] Push works with OAuth token
- [ ] Pull works with OAuth token
- [ ] Disconnect clears token
- [ ] Fallback to PAT works
- [ ] Organizations listed correctly
- [ ] Deep link works on macOS
- [ ] Deep link works on Windows
- [ ] Handles network errors gracefully
- [ ] Handles token revocation gracefully
## Dependencies
- DASH-001 (for launcher context to display account)
### External Dependencies
May need to add:
```json
{
"keytar": "^7.9.0" // Alternative to safeStorage for older Electron
}
```
## Blocked By
- DASH-001 (Tabbed Navigation) - for launcher UI placement
## Blocks
- GIT-003 (Repository Cloning) - needs auth for private repos
- COMP-004 (Organization Components) - needs org access
## Estimated Effort
- OAuth service: 4-6 hours
- Deep link handler: 2-3 hours
- UI components: 3-4 hours
- Git integration: 2-3 hours
- Testing & polish: 3-4 hours
- **Total: 14-20 hours**
## Success Criteria
1. Users can authenticate with GitHub via OAuth
2. OAuth tokens are stored securely
3. Git operations work with OAuth tokens
4. Users can see their connected account
5. Users can disconnect and reconnect
6. PAT remains available as fallback
7. Flow works on both macOS and Windows
## Future Enhancements
- Multiple GitHub account support
- GitLab OAuth
- Bitbucket OAuth
- GitHub Enterprise support
- Fine-grained personal access tokens

View File

@@ -0,0 +1,426 @@
# GIT-002: Git Status Dashboard Visibility
## Overview
Surface git status information directly in the project list on the dashboard, allowing users to see at a glance which projects need attention (uncommitted changes, unpushed commits, available updates) without opening each project.
## Context
Currently, git status is only visible inside the VersionControlPanel after opening a project. Users with many projects have no way to know which ones have uncommitted changes or need syncing.
The new launcher already has mock data for git sync status in `LauncherProjectCard`, but it's not connected to real data.
### Existing Infrastructure
From `LauncherProjectCard.tsx`:
```typescript
export enum CloudSyncType {
None = 'none',
Git = 'git'
}
export interface LauncherProjectData {
cloudSyncMeta: {
type: CloudSyncType;
source?: string; // Remote URL
};
pullAmount?: number;
pushAmount?: number;
uncommittedChangesAmount?: number;
}
```
From `VersionControlPanel/context/fetch.context.ts`:
```typescript
// Already calculates:
localCommitCount // Commits ahead of remote
remoteCommitCount // Commits behind remote
workingDirectoryStatus // Uncommitted files
```
## Requirements
### Functional Requirements
1. **Status Indicators in Project List**
- Not Initialized: Gray indicator, no version control
- Local Only: Yellow indicator, git but no remote
- Synced: Green checkmark, up to date
- Has Uncommitted Changes: Yellow dot, local modifications
- Ahead: Blue up arrow, local commits to push
- Behind: Orange down arrow, remote commits to pull
- Diverged: Red warning, both ahead and behind
2. **Status Details**
- Tooltip showing details on hover
- "3 commits to push, 2 to pull"
- "5 uncommitted files"
- Last sync time
3. **Quick Actions**
- Quick sync button (fetch + show status)
- Link to open Version Control panel
4. **Background Refresh**
- Check status on dashboard load
- Periodic refresh (every 5 minutes)
- Manual refresh button
- Status cached to avoid repeated git operations
5. **Performance**
- Parallel status checks for multiple projects
- Debounced/throttled to avoid overwhelming git
- Cached results with TTL
### Non-Functional Requirements
- Status check per project: <500ms
- Dashboard load not blocked by status checks
- Works offline (shows cached/stale data)
## Data Model
### Git Status Types
```typescript
enum ProjectGitStatus {
Unknown = 'unknown', // Haven't checked yet
NotInitialized = 'not-init', // Not a git repo
LocalOnly = 'local-only', // Git but no remote
Synced = 'synced', // Up to date with remote
Uncommitted = 'uncommitted', // Has local changes
Ahead = 'ahead', // Has commits to push
Behind = 'behind', // Has commits to pull
Diverged = 'diverged', // Both ahead and behind
Error = 'error' // Failed to check
}
interface ProjectGitStatusDetails {
status: ProjectGitStatus;
aheadCount?: number;
behindCount?: number;
uncommittedCount?: number;
lastFetchTime?: number;
remoteUrl?: string;
currentBranch?: string;
error?: string;
}
```
### Cache Structure
```typescript
interface GitStatusCache {
[projectPath: string]: {
status: ProjectGitStatusDetails;
checkedAt: number;
isStale: boolean;
};
}
```
## Technical Approach
### 1. Git Status Service
```typescript
// packages/noodl-editor/src/editor/src/services/ProjectGitStatusService.ts
class ProjectGitStatusService {
private static instance: ProjectGitStatusService;
private cache: GitStatusCache = {};
private checkQueue: Set<string> = new Set();
private isChecking = false;
// Check single project
async checkStatus(projectPath: string): Promise<ProjectGitStatusDetails>;
// Check multiple projects (batched)
async checkStatusBatch(projectPaths: string[]): Promise<Map<string, ProjectGitStatusDetails>>;
// Get cached status
getCachedStatus(projectPath: string): ProjectGitStatusDetails | null;
// Clear cache
invalidateCache(projectPath?: string): void;
// Subscribe to status changes
onStatusChanged(callback: (path: string, status: ProjectGitStatusDetails) => void): () => void;
}
```
### 2. Status Check Implementation
```typescript
async checkStatus(projectPath: string): Promise<ProjectGitStatusDetails> {
const git = new Git(mergeProject);
try {
// Check if it's a git repo
const gitPath = await getTopLevelWorkingDirectory(projectPath);
if (!gitPath) {
return { status: ProjectGitStatus.NotInitialized };
}
await git.openRepository(projectPath);
// Check for remote
const remoteName = await git.getRemoteName();
if (!remoteName) {
return { status: ProjectGitStatus.LocalOnly };
}
// Get working directory status
const workingStatus = await git.status();
const uncommittedCount = workingStatus.length;
// Get commit counts (requires fetch for accuracy)
const commits = await git.getCommitsCurrentBranch();
const aheadCount = commits.filter(c => c.isLocalAhead).length;
const behindCount = commits.filter(c => c.isRemoteAhead).length;
// Determine status
let status: ProjectGitStatus;
if (uncommittedCount > 0) {
status = ProjectGitStatus.Uncommitted;
} else if (aheadCount > 0 && behindCount > 0) {
status = ProjectGitStatus.Diverged;
} else if (aheadCount > 0) {
status = ProjectGitStatus.Ahead;
} else if (behindCount > 0) {
status = ProjectGitStatus.Behind;
} else {
status = ProjectGitStatus.Synced;
}
return {
status,
aheadCount,
behindCount,
uncommittedCount,
lastFetchTime: Date.now(),
remoteUrl: git.OriginUrl,
currentBranch: await git.getCurrentBranchName()
};
} catch (error) {
return {
status: ProjectGitStatus.Error,
error: error.message
};
}
}
```
### 3. Dashboard Integration Hook
```typescript
// packages/noodl-core-ui/src/preview/launcher/Launcher/hooks/useProjectGitStatus.ts
function useProjectGitStatus(projectPaths: string[]) {
const [statuses, setStatuses] = useState<Map<string, ProjectGitStatusDetails>>(new Map());
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
// Initial check
ProjectGitStatusService.instance
.checkStatusBatch(projectPaths)
.then(setStatuses)
.finally(() => setIsLoading(false));
// Subscribe to updates
const unsubscribe = ProjectGitStatusService.instance.onStatusChanged((path, status) => {
setStatuses(prev => new Map(prev).set(path, status));
});
return unsubscribe;
}, [projectPaths]);
const refresh = useCallback(() => {
ProjectGitStatusService.instance.invalidateCache();
// Re-trigger check
}, []);
return { statuses, isLoading, refresh };
}
```
### 4. Visual Status Badge
Already started in DASH-002 as `GitStatusBadge`, but needs real data connection:
```typescript
// Enhanced GitStatusBadge props
interface GitStatusBadgeProps {
status: ProjectGitStatus;
details: ProjectGitStatusDetails;
showTooltip?: boolean;
size?: 'small' | 'medium';
}
```
## Files to Create
1. `packages/noodl-editor/src/editor/src/services/ProjectGitStatusService.ts`
2. `packages/noodl-core-ui/src/preview/launcher/Launcher/hooks/useProjectGitStatus.ts`
3. `packages/noodl-core-ui/src/preview/launcher/Launcher/components/GitStatusBadge/GitStatusBadge.tsx` (if not created in DASH-002)
4. `packages/noodl-core-ui/src/preview/launcher/Launcher/components/GitStatusBadge/GitStatusBadge.module.scss`
5. `packages/noodl-core-ui/src/preview/launcher/Launcher/components/GitStatusTooltip/GitStatusTooltip.tsx`
## Files to Modify
1. `packages/noodl-core-ui/src/preview/launcher/Launcher/views/Projects.tsx`
- Use `useProjectGitStatus` hook
- Pass status to project cards/rows
2. `packages/noodl-core-ui/src/preview/launcher/Launcher/components/ProjectList/ProjectListRow.tsx`
- Display GitStatusBadge with real data
3. `packages/noodl-core-ui/src/preview/launcher/Launcher/components/LauncherProjectCard/LauncherProjectCard.tsx`
- Update to use real status data (for grid view)
4. `packages/noodl-core-ui/src/preview/launcher/Launcher/Launcher.tsx`
- Replace mock project data with real data connection
## Visual Specifications
### Status Badge Icons & Colors
| Status | Icon | Color | Background |
|--------|------|-------|------------|
| Unknown | ◌ (spinner) | Gray | Transparent |
| Not Initialized | ⊘ | Gray (#6B7280) | Transparent |
| Local Only | 💾 | Yellow (#EAB308) | Yellow/10 |
| Synced | ✓ | Green (#22C55E) | Green/10 |
| Uncommitted | ● | Yellow (#EAB308) | Yellow/10 |
| Ahead | ↑ | Blue (#3B82F6) | Blue/10 |
| Behind | ↓ | Orange (#F97316) | Orange/10 |
| Diverged | ⚠ | Red (#EF4444) | Red/10 |
| Error | ✕ | Red (#EF4444) | Red/10 |
### Tooltip Content
```
┌─────────────────────────────────┐
│ main branch │
│ ↑ 3 commits to push │
│ ↓ 2 commits to pull │
│ ● 5 uncommitted files │
│ │
│ Last synced: 10 minutes ago │
│ Remote: github.com/user/repo │
└─────────────────────────────────┘
```
## Implementation Steps
### Phase 1: Service Foundation
1. Create ProjectGitStatusService
2. Implement single project status check
3. Add caching logic
4. Create batch checking with parallelization
### Phase 2: Hook & Data Flow
1. Create useProjectGitStatus hook
2. Connect to Projects view
3. Replace mock data with real data
4. Add loading states
### Phase 3: Visual Components
1. Create/update GitStatusBadge
2. Create GitStatusTooltip
3. Integrate into ProjectListRow
4. Integrate into LauncherProjectCard
### Phase 4: Refresh & Background
1. Add manual refresh button
2. Implement periodic background refresh
3. Add refresh on window focus
4. Handle offline state
### Phase 5: Polish
1. Performance optimization
2. Error handling
3. Stale data indicators
4. Animation on status change
## Performance Considerations
1. **Parallel Checking**: Check up to 5 projects simultaneously
2. **Debouncing**: Don't re-check same project within 10 seconds
3. **Cache TTL**: Status valid for 5 minutes, stale after
4. **Lazy Loading**: Only check visible projects first
5. **Background Priority**: Use requestIdleCallback for non-visible
```typescript
// Throttled batch check
async checkStatusBatch(projectPaths: string[]): Promise<Map<string, ProjectGitStatusDetails>> {
const CONCURRENCY = 5;
const results = new Map();
for (let i = 0; i < projectPaths.length; i += CONCURRENCY) {
const batch = projectPaths.slice(i, i + CONCURRENCY);
const batchResults = await Promise.all(
batch.map(path => this.checkStatus(path))
);
batch.forEach((path, idx) => results.set(path, batchResults[idx]));
}
return results;
}
```
## Testing Checklist
- [ ] Detects non-git project correctly
- [ ] Detects git project without remote
- [ ] Shows synced status when up to date
- [ ] Shows uncommitted when local changes exist
- [ ] Shows ahead when local commits exist
- [ ] Shows behind when remote commits exist
- [ ] Shows diverged when both ahead and behind
- [ ] Tooltip shows correct details
- [ ] Refresh updates status
- [ ] Status persists across dashboard navigation
- [ ] Handles deleted projects gracefully
- [ ] Handles network errors gracefully
- [ ] Performance acceptable with 20+ projects
## Dependencies
- DASH-002 (Project List Redesign) - for UI integration
## Blocked By
- DASH-002
## Blocks
- GIT-004 (Auto-initialization) - needs status detection
- GIT-005 (Enhanced Push/Pull) - shares status infrastructure
## Estimated Effort
- Status service: 3-4 hours
- Hook & data flow: 2-3 hours
- Visual components: 2-3 hours
- Background refresh: 2-3 hours
- Polish & testing: 2-3 hours
- **Total: 11-16 hours**
## Success Criteria
1. Git status visible at a glance in project list
2. Status updates without manual refresh
3. Tooltip provides actionable details
4. Performance acceptable with many projects
5. Works offline with cached data
6. Handles edge cases gracefully
## Future Enhancements
- Quick commit from dashboard
- Quick push/pull buttons per project
- Bulk sync all projects
- Branch indicator
- Last commit message preview
- Contributor avatars (from git log)

View File

@@ -0,0 +1,346 @@
# GIT-003: Repository Cloning
## Overview
Add the ability to clone GitHub repositories directly from the Noodl dashboard, similar to how VS Code handles cloning. Users can browse their repositories, select one, choose a local folder, and have the project cloned and opened automatically.
## Context
Currently, to work with an existing Noodl project from GitHub, users must:
1. Clone the repo manually using git CLI or another tool
2. Open Noodl
3. Use "Open folder" to navigate to the cloned project
This task streamlines that to:
1. Click "Clone from GitHub"
2. Select repository
3. Choose folder
4. Project opens automatically
### Existing Infrastructure
The `noodl-git` package already has clone functionality:
```typescript
// From git.ts
async clone({ url, directory, singleBranch, onProgress }: GitCloneOptions): Promise<void>
```
And clone tests show it working:
```typescript
await git.clone({
url: 'https://github.com/github/testrepo.git',
directory: tempDir,
onProgress: (progress) => { result.push(progress); }
});
```
## Requirements
### Functional Requirements
1. **Clone Entry Points**
- "Clone Repository" button in dashboard toolbar
- "Clone from GitHub" option in "Create Project" menu
- Right-click empty area → "Clone Repository"
2. **Repository Browser**
- List user's repositories (requires OAuth from GIT-001)
- List organization repositories
- Search/filter repositories
- Show repo details: name, description, visibility, last updated
- "Clone URL" input for direct URL entry
3. **Folder Selection**
- Native folder picker dialog
- Remember last used parent folder
- Validate folder is empty or doesn't exist
- Show full path before cloning
4. **Clone Process**
- Progress indicator with stages
- Cancel button
- Error handling with clear messages
- Retry option on failure
5. **Post-Clone Actions**
- Automatically open project in editor
- Add to recent projects
- Show success notification
6. **Branch Selection (Optional)**
- Default to main/master
- Option to select different branch
- Shallow clone option for large repos
### Non-Functional Requirements
- Clone progress updates smoothly
- Cancellation works immediately
- Handles large repositories
- Works with private repositories (with auth)
- Clear error messages for common failures
## Technical Approach
### 1. Clone Service
```typescript
// packages/noodl-editor/src/editor/src/services/CloneService.ts
interface CloneOptions {
url: string;
directory: string;
branch?: string;
shallow?: boolean;
onProgress?: (progress: CloneProgress) => void;
}
interface CloneProgress {
phase: 'counting' | 'compressing' | 'receiving' | 'resolving' | 'checking-out';
percent: number;
message: string;
}
interface CloneResult {
success: boolean;
projectPath?: string;
error?: string;
}
class CloneService {
private static instance: CloneService;
private activeClone: AbortController | null = null;
async clone(options: CloneOptions): Promise<CloneResult>;
cancel(): void;
// GitHub API integration
async listUserRepos(): Promise<GitHubRepo[]>;
async listOrgRepos(orgName: string): Promise<GitHubRepo[]>;
async searchRepos(query: string): Promise<GitHubRepo[]>;
}
```
### 2. Repository Browser Component
```typescript
// RepoBrowser.tsx
interface RepoBrowserProps {
onSelect: (repo: GitHubRepo) => void;
onUrlSubmit: (url: string) => void;
}
interface GitHubRepo {
id: number;
name: string;
fullName: string;
description: string;
private: boolean;
htmlUrl: string;
cloneUrl: string;
sshUrl: string;
defaultBranch: string;
updatedAt: string;
owner: {
login: string;
avatarUrl: string;
};
}
```
### 3. Clone Modal Flow
```
┌─────────────────────────────────────────────────────────────────────┐
│ Clone Repository [×] │
├─────────────────────────────────────────────────────────────────────┤
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ [🔍 Search repositories... ] │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ [Your Repositories ▾] [Organizations: acme-corp ▾] │
│ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ 📁 noodl-project-template ★ 12 2 days ago │ │
│ │ A starter template for Noodl projects [Private 🔒] │ │
│ ├─────────────────────────────────────────────────────────────────┤ │
│ │ 📁 my-awesome-app ★ 5 1 week ago │ │
│ │ An awesome application built with Noodl [Public 🌍] │ │
│ ├─────────────────────────────────────────────────────────────────┤ │
│ │ 📁 client-dashboard ★ 0 3 weeks ago │ │
│ │ Dashboard for client project [Private 🔒] │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ ─── OR enter repository URL ───────────────────────────────────── │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ https://github.com/user/repo.git │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ [Cancel] [Next →] │
└─────────────────────────────────────────────────────────────────────┘
```
### 4. Folder Selection Step
```
┌─────────────────────────────────────────────────────────────────────┐
│ Clone Repository [×] │
├─────────────────────────────────────────────────────────────────────┤
│ Repository: github.com/user/my-awesome-app │
│ │
│ Clone to: │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ /Users/richard/Projects/my-awesome-app [Browse...] │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ ☐ Clone only the default branch (faster) │
│ │
│ [← Back] [Cancel] [Clone]│
└─────────────────────────────────────────────────────────────────────┘
```
### 5. Progress Step
```
┌─────────────────────────────────────────────────────────────────────┐
│ Cloning Repository [×] │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ Cloning my-awesome-app... │
│ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │████████████████████████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░│ 42% │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ Receiving objects: 1,234 of 2,891 │
│ │
│ [Cancel] │
└─────────────────────────────────────────────────────────────────────┘
```
## Files to Create
1. `packages/noodl-editor/src/editor/src/services/CloneService.ts`
2. `packages/noodl-core-ui/src/preview/launcher/Launcher/components/CloneModal/CloneModal.tsx`
3. `packages/noodl-core-ui/src/preview/launcher/Launcher/components/CloneModal/CloneModal.module.scss`
4. `packages/noodl-core-ui/src/preview/launcher/Launcher/components/CloneModal/RepoBrowser.tsx`
5. `packages/noodl-core-ui/src/preview/launcher/Launcher/components/CloneModal/FolderSelector.tsx`
6. `packages/noodl-core-ui/src/preview/launcher/Launcher/components/CloneModal/CloneProgress.tsx`
7. `packages/noodl-core-ui/src/preview/launcher/Launcher/components/RepoCard/RepoCard.tsx`
8. `packages/noodl-editor/src/editor/src/services/GitHubApiClient.ts` (if not created in GIT-001)
## Files to Modify
1. `packages/noodl-core-ui/src/preview/launcher/Launcher/views/Projects.tsx`
- Add "Clone Repository" button to toolbar
2. `packages/noodl-core-ui/src/preview/launcher/Launcher/Launcher.tsx`
- Add clone modal state and rendering
3. `packages/noodl-utils/LocalProjectsModel.ts`
- Add cloned project to recent projects list
4. `packages/noodl-editor/src/editor/src/views/projectsview.ts`
- Ensure cloned project can be opened (may already work)
## Implementation Steps
### Phase 1: Clone Service
1. Create CloneService wrapper around noodl-git
2. Add progress normalization
3. Add cancellation support
4. Test with public repository
### Phase 2: URL-Based Cloning
1. Create basic CloneModal with URL input
2. Create FolderSelector component
3. Create CloneProgress component
4. Wire up clone flow
### Phase 3: Repository Browser
1. Create GitHubApiClient (or extend from GIT-001)
2. Create RepoBrowser component
3. Create RepoCard component
4. Add search/filter functionality
### Phase 4: Integration
1. Add clone button to dashboard
2. Open cloned project automatically
3. Add to recent projects
4. Handle errors gracefully
### Phase 5: Polish
1. Remember last folder
2. Add branch selection
3. Add shallow clone option
4. Improve error messages
## Error Handling
| Error | User Message | Recovery |
|-------|--------------|----------|
| Network error | "Unable to connect. Check your internet connection." | Retry button |
| Auth required | "This repository requires authentication. Connect your GitHub account." | Link to OAuth |
| Repo not found | "Repository not found. Check the URL and try again." | Edit URL |
| Permission denied | "You don't have access to this repository." | Suggest checking permissions |
| Folder not empty | "The selected folder is not empty. Choose an empty folder." | Folder picker |
| Disk full | "Not enough disk space to clone this repository." | Show required space |
## Testing Checklist
- [ ] Clone public repository via URL
- [ ] Clone private repository with OAuth token
- [ ] Clone private repository with PAT
- [ ] Repository browser shows user repos
- [ ] Repository browser shows org repos
- [ ] Search/filter works
- [ ] Folder picker opens and works
- [ ] Progress updates smoothly
- [ ] Cancel stops clone in progress
- [ ] Cloned project opens automatically
- [ ] Project appears in recent projects
- [ ] Error messages are helpful
- [ ] Works with various repo sizes
- [ ] Handles repos with submodules
## Dependencies
- GIT-001 (GitHub OAuth) - for repository browser with private repos
- DASH-001 (Tabbed Navigation) - for dashboard integration
## Blocked By
- GIT-001 (partially - URL cloning works without OAuth)
## Blocks
- COMP-004 (Organization Components) - uses similar repo browsing
## Estimated Effort
- Clone service: 2-3 hours
- URL-based clone modal: 3-4 hours
- Repository browser: 4-5 hours
- Integration & auto-open: 2-3 hours
- Polish & error handling: 2-3 hours
- **Total: 13-18 hours**
## Success Criteria
1. Users can clone by entering a URL
2. Users can browse and select their repositories
3. Clone progress is visible and accurate
4. Cloned projects open automatically
5. Private repos work with authentication
6. Errors are handled gracefully
7. Process can be cancelled
## Future Enhancements
- Clone from other providers (GitLab, Bitbucket)
- Clone specific branch/tag
- Clone with submodules options
- Clone into new project template
- Clone history (recently cloned repos)
- Detect Noodl projects vs generic repos

View File

@@ -0,0 +1,388 @@
# GIT-004: Auto-Initialization & Commit Encouragement
## Overview
Make version control a default part of the Noodl workflow by automatically initializing git for new projects and gently encouraging regular commits. This helps users avoid losing work and prepares them for collaboration.
## Context
Currently:
- New projects are not git-initialized by default
- Users must manually open Version Control panel and initialize
- There's no prompting to commit changes
- Closing a project with uncommitted changes has no warning
Many Noodl users are designers or low-code developers who may not be familiar with git. By making version control automatic and unobtrusive, we help them develop good habits without requiring git expertise.
### Existing Infrastructure
From `LocalProjectsModel.ts`:
```typescript
async isGitProject(project: ProjectModel): Promise<boolean> {
const gitPath = await getTopLevelWorkingDirectory(project._retainedProjectDirectory);
return gitPath !== null;
}
```
From `git.ts`:
```typescript
async initNewRepo(baseDir: string, options?: { bare: boolean }): Promise<void> {
if (this.baseDir) return;
this.baseDir = await init(baseDir, options);
await this._setupRepository();
}
```
## Requirements
### Functional Requirements
1. **Auto-Initialization**
- New projects are git-initialized by default
- Initial commit with project creation
- Option to disable in settings
- Existing non-git projects can be initialized easily
2. **Commit Encouragement**
- Periodic reminder when changes are uncommitted
- Reminder appears as subtle notification, not modal
- "Commit now" quick action
- "Remind me later" option
- Configurable reminder interval
3. **Quick Commit**
- One-click commit from notification
- Simple commit message input
- Default message suggestion
- Option to open full Version Control panel
4. **Close Warning**
- Warning when closing project with uncommitted changes
- Show number of uncommitted files
- Options: "Commit & Close", "Close Anyway", "Cancel"
- Can be disabled in settings
5. **Settings**
- Enable/disable auto-initialization
- Enable/disable commit reminders
- Reminder interval (15min, 30min, 1hr, 2hr)
- Enable/disable close warning
### Non-Functional Requirements
- Reminders are non-intrusive
- Quick commit is fast (<2 seconds)
- Auto-init doesn't slow project creation
- Works offline
## Technical Approach
### 1. Auto-Initialization in Project Creation
```typescript
// packages/noodl-editor/src/editor/src/models/projectmodel.ts
async createNewProject(name: string, template?: string): Promise<ProjectModel> {
const project = await this._createProject(name, template);
// Auto-initialize git if enabled
if (EditorSettings.instance.get('git.autoInitialize') !== false) {
try {
const git = new Git(mergeProject);
await git.initNewRepo(project._retainedProjectDirectory);
await git.commit('Initial commit');
} catch (error) {
console.warn('Failed to auto-initialize git:', error);
// Don't fail project creation if git init fails
}
}
return project;
}
```
### 2. Commit Reminder Service
```typescript
// packages/noodl-editor/src/editor/src/services/CommitReminderService.ts
class CommitReminderService {
private static instance: CommitReminderService;
private reminderTimer: NodeJS.Timer | null = null;
private lastRemindedAt: number = 0;
// Start monitoring for uncommitted changes
start(): void;
stop(): void;
// Check if reminder should show
shouldShowReminder(): Promise<boolean>;
// Show/dismiss reminder
showReminder(): void;
dismissReminder(snoozeMinutes?: number): void;
// Events
onReminderTriggered(callback: () => void): () => void;
}
```
### 3. Quick Commit Component
```typescript
// packages/noodl-core-ui/src/components/git/QuickCommitPopup/QuickCommitPopup.tsx
interface QuickCommitPopupProps {
uncommittedCount: number;
suggestedMessage: string;
onCommit: (message: string) => Promise<void>;
onDismiss: () => void;
onOpenFullPanel: () => void;
}
```
### 4. Close Warning Dialog
```typescript
// packages/noodl-core-ui/src/components/git/UnsavedChangesDialog/UnsavedChangesDialog.tsx
interface UnsavedChangesDialogProps {
uncommittedCount: number;
onCommitAndClose: () => Promise<void>;
onCloseAnyway: () => void;
onCancel: () => void;
}
```
### 5. Default Commit Messages
```typescript
// Smart default commit message generation
function generateDefaultCommitMessage(changes: GitStatus[]): string {
const added = changes.filter(c => c.status === 'added');
const modified = changes.filter(c => c.status === 'modified');
const deleted = changes.filter(c => c.status === 'deleted');
const parts: string[] = [];
if (added.length > 0) {
if (added.length === 1) {
parts.push(`Add ${getComponentName(added[0].path)}`);
} else {
parts.push(`Add ${added.length} files`);
}
}
if (modified.length > 0) {
if (modified.length === 1) {
parts.push(`Update ${getComponentName(modified[0].path)}`);
} else {
parts.push(`Update ${modified.length} files`);
}
}
if (deleted.length > 0) {
parts.push(`Remove ${deleted.length} files`);
}
return parts.join(', ') || 'Update project';
}
```
## UI Mockups
### Commit Reminder Notification
```
┌─────────────────────────────────────────────────────────────┐
│ 💾 You have 5 uncommitted changes │
│ │
│ It's been 30 minutes since your last commit. │
│ │
│ [Commit Now] [Remind Me Later ▾] [Dismiss] │
└─────────────────────────────────────────────────────────────┘
```
### Quick Commit Popup
```
┌─────────────────────────────────────────────────────────────┐
│ Quick Commit [×] │
├─────────────────────────────────────────────────────────────┤
│ 5 files changed │
│ │
│ Message: │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Update LoginPage and add UserProfile component │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ [Open Version Control] [Cancel] [Commit] │
└─────────────────────────────────────────────────────────────┘
```
### Close Warning Dialog
```
┌─────────────────────────────────────────────────────────────┐
│ ⚠️ Uncommitted Changes [×] │
├─────────────────────────────────────────────────────────────┤
│ │
│ You have 5 uncommitted changes in this project. │
│ │
│ These changes will be preserved locally but not versioned. │
│ To keep a history of your work, commit before closing. │
│ │
│ ☐ Don't show this again │
│ │
│ [Cancel] [Close Anyway] [Commit & Close] │
└─────────────────────────────────────────────────────────────┘
```
## Files to Create
1. `packages/noodl-editor/src/editor/src/services/CommitReminderService.ts`
2. `packages/noodl-core-ui/src/components/git/QuickCommitPopup/QuickCommitPopup.tsx`
3. `packages/noodl-core-ui/src/components/git/QuickCommitPopup/QuickCommitPopup.module.scss`
4. `packages/noodl-core-ui/src/components/git/UnsavedChangesDialog/UnsavedChangesDialog.tsx`
5. `packages/noodl-core-ui/src/components/git/CommitReminderToast/CommitReminderToast.tsx`
6. `packages/noodl-editor/src/editor/src/utils/git/defaultCommitMessage.ts`
## Files to Modify
1. `packages/noodl-editor/src/editor/src/models/projectmodel.ts`
- Add auto-initialization in project creation
2. `packages/noodl-editor/src/editor/src/pages/EditorPage/EditorPage.tsx`
- Add close warning handler
- Integrate CommitReminderService
3. `packages/noodl-utils/editorsettings.ts`
- Add git-related settings
4. `packages/noodl-editor/src/editor/src/views/panels/EditorSettingsPanel/`
- Add git settings section
5. `packages/noodl-editor/src/main/main.js`
- Handle close event for warning
## Settings Schema
```typescript
interface GitSettings {
// Auto-initialization
'git.autoInitialize': boolean; // default: true
// Commit reminders
'git.commitReminders.enabled': boolean; // default: true
'git.commitReminders.intervalMinutes': number; // default: 30
// Close warning
'git.closeWarning.enabled': boolean; // default: true
// Quick commit
'git.quickCommit.suggestMessage': boolean; // default: true
}
```
## Implementation Steps
### Phase 1: Auto-Initialization
1. Add git.autoInitialize setting
2. Modify project creation to init git
3. Add initial commit
4. Test with new projects
### Phase 2: Settings UI
1. Add Git section to Editor Settings panel
2. Implement all settings toggles
3. Store settings in EditorSettings
### Phase 3: Commit Reminder Service
1. Create CommitReminderService
2. Add timer-based reminder check
3. Create CommitReminderToast component
4. Integrate with editor lifecycle
### Phase 4: Quick Commit
1. Create QuickCommitPopup component
2. Implement default message generation
3. Wire up commit action
4. Add "Open full panel" option
### Phase 5: Close Warning
1. Create UnsavedChangesDialog
2. Hook into project close event
3. Implement "Commit & Close" flow
4. Add "Don't show again" option
### Phase 6: Polish
1. Snooze functionality
2. Notification stacking
3. Animation/transitions
4. Edge case handling
## Testing Checklist
- [ ] New project is git-initialized by default
- [ ] Initial commit is created
- [ ] Auto-init can be disabled
- [ ] Commit reminder appears after interval
- [ ] Reminder shows correct uncommitted count
- [ ] "Commit Now" opens quick commit popup
- [ ] "Remind Me Later" snoozes correctly
- [ ] Quick commit works with default message
- [ ] Quick commit works with custom message
- [ ] Close warning appears with uncommitted changes
- [ ] "Commit & Close" works
- [ ] "Close Anyway" works
- [ ] "Don't show again" persists
- [ ] Settings toggle all features correctly
- [ ] Works when offline
## Edge Cases
1. **Project already has git**: Don't re-initialize, just work with existing
2. **Template with git**: Use template's git if present, else init fresh
3. **Init fails**: Log warning, don't block project creation
4. **Commit fails**: Show error, offer to open Version Control panel
5. **Large commit**: Show progress, don't block UI
6. **No changes on reminder check**: Don't show reminder
## Dependencies
- GIT-002 (Git Status Dashboard) - for status detection infrastructure
## Blocked By
- GIT-002 (shares status checking code)
## Blocks
- None
## Estimated Effort
- Auto-initialization: 2-3 hours
- Settings UI: 2-3 hours
- Commit reminder service: 3-4 hours
- Quick commit popup: 2-3 hours
- Close warning: 2-3 hours
- Polish: 2-3 hours
- **Total: 13-19 hours**
## Success Criteria
1. New projects have git by default
2. Users are gently reminded to commit
3. Committing is easy and fast
4. Users are warned before losing work
5. All features can be disabled
6. Non-intrusive to workflow
## Future Enhancements
- Commit streak/gamification
- Auto-commit on significant changes
- Commit templates
- Branch suggestions
- Integration with cloud backup

View File

@@ -0,0 +1,388 @@
# GIT-005: Enhanced Push/Pull UI
## Overview
Improve the push/pull experience with better visibility, branch management, conflict previews, and dashboard-level sync controls. Make syncing with remotes more intuitive and less error-prone.
## Context
The current Version Control panel has push/pull functionality via `GitStatusButton`, but:
- Only visible when the panel is open
- Branch switching is buried in menus
- No preview of what will be pulled
- Conflict resolution is complex
This task brings sync operations to the forefront and adds safeguards.
### Existing Infrastructure
From `GitStatusButton.tsx`:
```typescript
// Status kinds: 'default', 'fetch', 'error-fetch', 'pull', 'push', 'push-repository', 'set-authorization'
case 'push': {
label = localCommitCount === 1 ? `Push 1 local commit` : `Push ${localCommitCount} local commits`;
}
case 'pull': {
label = remoteCommitCount === 1 ? `Pull 1 remote commit` : `Pull ${remoteCommitCount} remote commits`;
}
```
From `fetch.context.ts`:
```typescript
localCommitCount // Commits ahead of remote
remoteCommitCount // Commits behind remote
currentBranch // Current branch info
branches // All branches
```
## Requirements
### Functional Requirements
1. **Dashboard Sync Button**
- Visible sync button in project row (from GIT-002)
- One-click fetch & show status
- Quick push/pull from dashboard
2. **Branch Selector**
- Dropdown showing current branch
- Quick switch between branches
- Create new branch option
- Branch search for projects with many branches
- Remote branch indicators
3. **Pull Preview**
- Show what commits will be pulled
- List affected files
- Warning for potential conflicts
- "Preview" mode before actual pull
4. **Conflict Prevention**
- Check for conflicts before pull
- Suggest stashing changes first
- Clear conflict resolution workflow
- "Abort" option during conflicts
5. **Push Confirmation**
- Show commits being pushed
- Branch protection warning (if pushing to main)
- Force push warning (if needed)
6. **Sync Status Header**
- Always-visible status in editor header
- Current branch display
- Quick sync actions
- Connection indicator
### Non-Functional Requirements
- Sync operations don't block UI
- Progress visible for long operations
- Works offline (queues operations)
- Clear error messages
## Technical Approach
### 1. Sync Status Header Component
```typescript
// packages/noodl-core-ui/src/components/git/SyncStatusHeader/SyncStatusHeader.tsx
interface SyncStatusHeaderProps {
currentBranch: string;
aheadCount: number;
behindCount: number;
hasUncommitted: boolean;
isOnline: boolean;
lastFetchTime: number;
onPush: () => void;
onPull: () => void;
onFetch: () => void;
onBranchChange: (branch: string) => void;
}
```
### 2. Branch Selector Component
```typescript
// packages/noodl-core-ui/src/components/git/BranchSelector/BranchSelector.tsx
interface BranchSelectorProps {
currentBranch: Branch;
branches: Branch[];
onSelect: (branch: Branch) => void;
onCreate: (name: string) => void;
}
interface Branch {
name: string;
nameWithoutRemote: string;
isLocal: boolean;
isRemote: boolean;
isCurrent: boolean;
lastCommit?: {
sha: string;
message: string;
date: string;
};
}
```
### 3. Pull Preview Modal
```typescript
// packages/noodl-core-ui/src/components/git/PullPreviewModal/PullPreviewModal.tsx
interface PullPreviewModalProps {
commits: Commit[];
affectedFiles: FileChange[];
hasConflicts: boolean;
conflictFiles?: string[];
onPull: () => Promise<void>;
onCancel: () => void;
}
interface Commit {
sha: string;
message: string;
author: string;
date: string;
}
interface FileChange {
path: string;
status: 'added' | 'modified' | 'deleted';
hasConflict: boolean;
}
```
### 4. Conflict Resolution Flow
```typescript
// packages/noodl-editor/src/editor/src/services/ConflictResolutionService.ts
class ConflictResolutionService {
// Check for potential conflicts before pull
async previewConflicts(): Promise<ConflictPreview>;
// Handle stashing
async stashAndPull(): Promise<void>;
// Resolution strategies
async resolveWithOurs(file: string): Promise<void>;
async resolveWithTheirs(file: string): Promise<void>;
async openMergeTool(file: string): Promise<void>;
// Abort
async abortMerge(): Promise<void>;
}
```
## UI Mockups
### Sync Status Header (Editor)
```
┌─────────────────────────────────────────────────────────────────────────────┐
│ [main ▾] ↑3 ↓2 ●5 uncommitted 🟢 Connected [Fetch] [Pull] [Push] │
└─────────────────────────────────────────────────────────────────────────────┘
```
### Branch Selector Dropdown
```
┌─────────────────────────────────────┐
│ 🔍 Search branches... │
├─────────────────────────────────────┤
│ LOCAL │
│ ✓ main │
│ feature/new-login │
│ bugfix/header-styling │
├─────────────────────────────────────┤
│ REMOTE │
│ origin/develop │
│ origin/release-1.0 │
├─────────────────────────────────────┤
│ + Create new branch... │
└─────────────────────────────────────┘
```
### Pull Preview Modal
```
┌─────────────────────────────────────────────────────────────────────┐
│ Pull Preview [×] │
├─────────────────────────────────────────────────────────────────────┤
│ Pulling 3 commits from origin/main │
│ │
│ COMMITS │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ a1b2c3d Fix login validation John Doe 2 hours ago │ │
│ │ d4e5f6g Add password reset flow Jane Smith 5 hours ago │ │
│ │ h7i8j9k Update dependencies John Doe 1 day ago │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ FILES CHANGED (12) │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ M components/LoginPage.ndjson │ │
│ │ M components/Header.ndjson │ │
│ │ A components/PasswordReset.ndjson │ │
│ │ D components/OldLogin.ndjson │ │
│ │ ⚠️ M project.json (potential conflict) │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ ⚠️ You have uncommitted changes. They will be stashed before pull. │
│ │
│ [Cancel] [Pull Now] │
└─────────────────────────────────────────────────────────────────────┘
```
### Conflict Warning
```
┌─────────────────────────────────────────────────────────────────────┐
│ ⚠️ Potential Conflicts Detected [×] │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ The following files have been modified both locally and remotely: │
│ │
│ • project.json │
│ • components/LoginPage.ndjson │
│ │
│ Noodl will attempt to merge these changes automatically, but you │
│ may need to resolve conflicts manually. │
│ │
│ Recommended: Commit your local changes first for a cleaner merge. │
│ │
│ [Cancel] [Commit First] [Pull Anyway] │
└─────────────────────────────────────────────────────────────────────┘
```
## Files to Create
1. `packages/noodl-core-ui/src/components/git/SyncStatusHeader/SyncStatusHeader.tsx`
2. `packages/noodl-core-ui/src/components/git/SyncStatusHeader/SyncStatusHeader.module.scss`
3. `packages/noodl-core-ui/src/components/git/BranchSelector/BranchSelector.tsx`
4. `packages/noodl-core-ui/src/components/git/BranchSelector/BranchSelector.module.scss`
5. `packages/noodl-core-ui/src/components/git/PullPreviewModal/PullPreviewModal.tsx`
6. `packages/noodl-core-ui/src/components/git/PushConfirmModal/PushConfirmModal.tsx`
7. `packages/noodl-core-ui/src/components/git/ConflictWarningModal/ConflictWarningModal.tsx`
8. `packages/noodl-editor/src/editor/src/services/ConflictResolutionService.ts`
## Files to Modify
1. `packages/noodl-editor/src/editor/src/pages/EditorPage/EditorPage.tsx`
- Add SyncStatusHeader to editor layout
2. `packages/noodl-editor/src/editor/src/views/panels/VersionControlPanel/VersionControlPanel.tsx`
- Integrate new BranchSelector
- Add pull preview before pulling
3. `packages/noodl-editor/src/editor/src/views/panels/VersionControlPanel/components/GitStatusButton.tsx`
- Update to use new pull/push flows
4. `packages/noodl-editor/src/editor/src/views/panels/VersionControlPanel/context/fetch.context.ts`
- Add preview fetch logic
- Add conflict detection
5. `packages/noodl-core-ui/src/preview/launcher/Launcher/components/ProjectList/ProjectListRow.tsx`
- Add quick sync button (if not in GIT-002)
## Implementation Steps
### Phase 1: Branch Selector
1. Create BranchSelector component
2. Implement search/filter
3. Add create branch flow
4. Integrate into Version Control panel
### Phase 2: Sync Status Header
1. Create SyncStatusHeader component
2. Add to editor layout
3. Wire up actions
4. Add connection indicator
### Phase 3: Pull Preview
1. Create PullPreviewModal
2. Implement commit/file listing
3. Add conflict detection
4. Wire up pull action
### Phase 4: Conflict Handling
1. Create ConflictWarningModal
2. Create ConflictResolutionService
3. Implement stash-before-pull
4. Add abort functionality
### Phase 5: Push Enhancements
1. Create PushConfirmModal
2. Add branch protection warning
3. Show commit list
4. Handle force push
### Phase 6: Dashboard Integration
1. Add sync button to project rows
2. Quick push/pull from dashboard
3. Update status after sync
## Testing Checklist
- [ ] Branch selector shows all branches
- [ ] Branch search filters correctly
- [ ] Switching branches works
- [ ] Creating new branch works
- [ ] Sync status header shows correct counts
- [ ] Fetch updates status
- [ ] Pull preview shows correct commits
- [ ] Pull preview shows affected files
- [ ] Conflict warning appears when appropriate
- [ ] Stash-before-pull works
- [ ] Pull completes successfully
- [ ] Push confirmation shows commits
- [ ] Push completes successfully
- [ ] Dashboard sync button works
- [ ] Offline state handled gracefully
## Dependencies
- GIT-002 (Git Status Dashboard) - for dashboard integration
- GIT-001 (GitHub OAuth) - for authenticated operations
## Blocked By
- GIT-002
## Blocks
- None
## Estimated Effort
- Branch selector: 3-4 hours
- Sync status header: 2-3 hours
- Pull preview: 4-5 hours
- Conflict handling: 4-5 hours
- Push enhancements: 2-3 hours
- Dashboard integration: 2-3 hours
- **Total: 17-23 hours**
## Success Criteria
1. Branch switching is easy and visible
2. Users can preview what will be pulled
3. Conflict potential is detected before pull
4. Stashing is automatic when needed
5. Push shows what's being pushed
6. Quick sync available from dashboard
7. Status always visible in editor
## Future Enhancements
- Pull request creation
- Branch comparison
- Revert/cherry-pick commits
- Squash commits before push
- Auto-sync on save (optional)
- Branch naming conventions/templates

View File

@@ -0,0 +1,248 @@
# GIT Series: Git & GitHub Integration
## Overview
The GIT series transforms Noodl's version control experience from a manual, expert-only feature into a seamless, integrated part of the development workflow. By adding GitHub OAuth, surfacing git status in the dashboard, and encouraging good version control habits, we make collaboration accessible to all Noodl users.
## Target Environment
- **Editor**: React 19 version only
- **Runtime**: Not affected (git is editor-only)
- **Backwards Compatibility**: Existing git projects continue to work
## Task Dependency Graph
```
GIT-001 (GitHub OAuth)
├──────────────────────────┐
│ │
▼ ▼
GIT-002 (Dashboard Status) GIT-003 (Repository Cloning)
├──────────────────────────┐
│ │
▼ ▼
GIT-004 (Auto-Init) GIT-005 (Enhanced Push/Pull)
```
## Task Summary
| Task ID | Name | Est. Hours | Priority |
|---------|------|------------|----------|
| GIT-001 | GitHub OAuth Integration | 14-20 | Critical |
| GIT-002 | Git Status Dashboard Visibility | 11-16 | High |
| GIT-003 | Repository Cloning | 13-18 | High |
| GIT-004 | Auto-Initialization & Commit Encouragement | 13-19 | Medium |
| GIT-005 | Enhanced Push/Pull UI | 17-23 | Medium |
**Total Estimated: 68-96 hours**
## Implementation Order
### Week 1-2: Authentication & Status
1. **GIT-001** - GitHub OAuth (foundation for GitHub API access)
2. **GIT-002** - Dashboard status (leverages DASH-002 project list)
### Week 3: Cloning & Basic Flow
3. **GIT-003** - Repository cloning (depends on OAuth for private repos)
### Week 4: Polish & Encouragement
4. **GIT-004** - Auto-initialization (depends on status detection)
5. **GIT-005** - Enhanced push/pull (depends on status infrastructure)
## Existing Infrastructure
The codebase already has solid git foundations to build on:
### noodl-git Package
```
packages/noodl-git/src/
├── git.ts # Main Git class
├── core/
│ ├── clone.ts # Clone operations
│ ├── push.ts # Push operations
│ ├── pull.ts # Pull operations
│ └── ...
├── actions/ # Higher-level actions
└── constants.ts
```
Key existing methods:
- `git.initNewRepo()` - Initialize new repository
- `git.clone()` - Clone with progress
- `git.push()` - Push with progress
- `git.pull()` - Pull with rebase
- `git.status()` - Working directory status
- `git.getBranches()` - List branches
- `git.getCommitsCurrentBranch()` - Commit history
### Version Control Panel
```
packages/noodl-editor/src/editor/src/views/panels/VersionControlPanel/
├── VersionControlPanel.tsx
├── components/
│ ├── GitStatusButton.tsx # Push/pull status
│ ├── GitProviderPopout/ # Credentials management
│ ├── LocalChanges.tsx # Uncommitted files
│ ├── History.tsx # Commit history
│ └── BranchMerge.tsx # Branch operations
└── context/
└── fetch.context.ts # Git state management
```
### Credentials Storage
- `GitStore` - Stores credentials per-project encrypted
- `trampoline-askpass-handler` - Handles git credential prompts
- Currently uses PAT (Personal Access Token) for GitHub
## Key Technical Decisions
### OAuth vs PAT
**Current**: Personal Access Token per project
- User creates PAT on GitHub
- Copies to Noodl per project
- Stored encrypted in GitStore
**New (GIT-001)**: OAuth + PAT fallback
- One-click GitHub OAuth
- Token stored globally
- PAT remains for non-GitHub remotes
### Status Checking Strategy
**Approach**: Batch + Cache
- Check multiple projects in parallel
- Cache results with TTL
- Background refresh
**Why**: Git status requires opening each repo, which is slow. Caching makes dashboard responsive while keeping data fresh.
### Auto-Initialization
**Approach**: Opt-out
- Git initialized by default
- Initial commit created automatically
- Can disable in settings
**Why**: Most users benefit from version control. Making it default reduces "I lost my work" issues.
## Services to Create
| Service | Location | Purpose |
|---------|----------|---------|
| GitHubOAuthService | noodl-editor/services | OAuth flow, token management |
| GitHubApiClient | noodl-editor/services | GitHub REST API calls |
| ProjectGitStatusService | noodl-editor/services | Batch status checking, caching |
| CloneService | noodl-editor/services | Clone wrapper with progress |
| CommitReminderService | noodl-editor/services | Periodic commit reminders |
| ConflictResolutionService | noodl-editor/services | Conflict detection, resolution |
## Components to Create
| Component | Package | Purpose |
|-----------|---------|---------|
| GitHubConnectButton | noodl-core-ui | OAuth trigger button |
| GitHubAccountCard | noodl-core-ui | Connected account display |
| GitStatusBadge | noodl-core-ui | Status indicator in list |
| CloneModal | noodl-core-ui | Clone flow modal |
| RepoBrowser | noodl-core-ui | Repository list/search |
| QuickCommitPopup | noodl-core-ui | Fast commit dialog |
| SyncStatusHeader | noodl-core-ui | Editor header sync status |
| BranchSelector | noodl-core-ui | Branch dropdown |
| PullPreviewModal | noodl-core-ui | Preview before pull |
## Dependencies
### On DASH Series
- GIT-002 → DASH-002 (project list for status display)
- GIT-001 → DASH-001 (launcher context for account display)
### External Packages
May need:
```json
{
"@octokit/rest": "^20.0.0" // GitHub API client (optional)
}
```
## Security Considerations
1. **OAuth Tokens**: Store with electron's safeStorage API
2. **PKCE Flow**: Use PKCE for OAuth (no client secret in app)
3. **Token Scope**: Request minimum necessary (repo, read:org, read:user)
4. **Credential Cache**: Clear on logout/disconnect
5. **PAT Fallback**: Encrypted per-project storage continues
## Testing Strategy
### Unit Tests
- OAuth token exchange
- Status calculation logic
- Conflict detection
- Default commit message generation
### Integration Tests
- Clone from public repo
- Clone from private repo with auth
- Push/pull with mock remote
- Branch operations
### Manual Testing
- Full OAuth flow
- Dashboard status refresh
- Clone flow end-to-end
- Commit reminder timing
- Conflict resolution
## Cline Usage Notes
### Before Starting Each Task
1. Read the task document completely
2. Review existing git infrastructure:
- `packages/noodl-git/src/git.ts`
- `packages/noodl-editor/src/editor/src/views/panels/VersionControlPanel/`
3. Check GitStore and credential handling
### Key Gotchas
1. **Git operations are async**: Always use try/catch, git can fail
2. **Repository paths**: Use `_retainedProjectDirectory` from ProjectModel
3. **Merge strategy**: Noodl has custom merge for project.json (`mergeProject`)
4. **Auth caching**: Credentials cached by trampoline, may need clearing
5. **Electron context**: Some git ops need main process (deep links)
### Testing Git Operations
```bash
# In tests directory, run git tests
npm run test:editor -- --grep="Git"
```
## Success Criteria (Series Complete)
1. ✅ Users can authenticate with GitHub via OAuth
2. ✅ Git status visible in project dashboard
3. ✅ Users can clone repositories from UI
4. ✅ New projects have git by default
5. ✅ Users are reminded to commit regularly
6. ✅ Pull/push is intuitive with previews
7. ✅ Branch management is accessible
## Future Work (Post-GIT)
The GIT series enables:
- **COMP series**: Shared component repositories
- **DEPLOY series**: Auto-push to frontend repo on deploy
- **Community features**: Public component sharing
## Files in This Series
- `GIT-001-github-oauth.md`
- `GIT-002-dashboard-git-status.md`
- `GIT-003-repository-cloning.md`
- `GIT-004-auto-init-commit-encouragement.md`
- `GIT-005-enhanced-push-pull.md`
- `GIT-OVERVIEW.md` (this file)