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,335 @@
|
||||
# GIT-001: GitHub OAuth Integration
|
||||
|
||||
## Overview
|
||||
|
||||
Add GitHub OAuth as an authentication method alongside the existing Personal Access Token (PAT) approach. This provides a smoother onboarding experience and enables access to GitHub's API for advanced features like repository browsing and organization access.
|
||||
|
||||
## Context
|
||||
|
||||
Currently, Noodl uses Personal Access Tokens for GitHub authentication:
|
||||
- Stored per-project in `GitStore` (encrypted locally)
|
||||
- Prompted via `GitProviderPopout` component
|
||||
- Used by `trampoline-askpass-handler` for git operations
|
||||
|
||||
OAuth provides advantages:
|
||||
- No need to manually create and copy PATs
|
||||
- Automatic token refresh
|
||||
- Access to GitHub API (not just git operations)
|
||||
- Org/repo scope selection
|
||||
|
||||
## Current State
|
||||
|
||||
### Existing Authentication Flow
|
||||
```
|
||||
User → GitProviderPopout → Enter PAT → GitStore.set() → Git operations use PAT
|
||||
```
|
||||
|
||||
### Key Files
|
||||
- `packages/noodl-editor/src/editor/src/views/panels/VersionControlPanel/components/GitProviderPopout/`
|
||||
- `packages/noodl-store/src/GitStore.ts` (assumed location)
|
||||
- `packages/noodl-git/src/core/trampoline/trampoline-askpass-handler.ts`
|
||||
|
||||
## Requirements
|
||||
|
||||
### Functional Requirements
|
||||
|
||||
1. **OAuth Flow**
|
||||
- "Connect with GitHub" button in settings/dashboard
|
||||
- Opens GitHub OAuth in system browser
|
||||
- Handles callback via custom protocol (`noodl://github-callback`)
|
||||
- Exchanges code for access token
|
||||
- Stores token securely
|
||||
|
||||
2. **Scope Selection**
|
||||
- Request appropriate scopes: `repo`, `read:org`, `read:user`
|
||||
- Display what permissions are being requested
|
||||
- Option to request additional scopes later
|
||||
|
||||
3. **Account Management**
|
||||
- Show connected GitHub account (avatar, username)
|
||||
- "Disconnect" option
|
||||
- Support multiple accounts (stretch goal)
|
||||
|
||||
4. **Organization Access**
|
||||
- List user's organizations
|
||||
- Allow selecting which orgs to access
|
||||
- Remember org selection
|
||||
|
||||
5. **Token Management**
|
||||
- Secure storage using electron's safeStorage or keytar
|
||||
- Automatic token refresh (GitHub OAuth tokens don't expire but can be revoked)
|
||||
- Handle token revocation gracefully
|
||||
|
||||
6. **Fallback to PAT**
|
||||
- Keep existing PAT flow as alternative
|
||||
- "Use Personal Access Token instead" option
|
||||
- Clear migration path from PAT to OAuth
|
||||
|
||||
### Non-Functional Requirements
|
||||
|
||||
- OAuth flow completes in <30 seconds
|
||||
- Token stored securely (encrypted at rest)
|
||||
- Works behind corporate proxies
|
||||
- Graceful offline handling
|
||||
|
||||
## Technical Approach
|
||||
|
||||
### 1. GitHub OAuth App Setup
|
||||
|
||||
Register OAuth App in GitHub:
|
||||
- Application name: "OpenNoodl"
|
||||
- Homepage URL: `https://opennoodl.net`
|
||||
- Callback URL: `noodl://github-callback`
|
||||
|
||||
Store Client ID in app (Client Secret not needed for public clients using PKCE).
|
||||
|
||||
### 2. OAuth Flow Implementation
|
||||
|
||||
```typescript
|
||||
// packages/noodl-editor/src/editor/src/services/GitHubOAuthService.ts
|
||||
|
||||
class GitHubOAuthService {
|
||||
private static instance: GitHubOAuthService;
|
||||
|
||||
// OAuth flow
|
||||
async initiateOAuth(): Promise<void>;
|
||||
async handleCallback(code: string, state: string): Promise<GitHubToken>;
|
||||
|
||||
// Token management
|
||||
async getToken(): Promise<string | null>;
|
||||
async refreshToken(): Promise<string>;
|
||||
async revokeToken(): Promise<void>;
|
||||
|
||||
// Account info
|
||||
async getCurrentUser(): Promise<GitHubUser>;
|
||||
async getOrganizations(): Promise<GitHubOrg[]>;
|
||||
|
||||
// State
|
||||
isAuthenticated(): boolean;
|
||||
onAuthStateChanged(callback: (authenticated: boolean) => void): void;
|
||||
}
|
||||
```
|
||||
|
||||
### 3. PKCE Flow (Recommended for Desktop Apps)
|
||||
|
||||
```typescript
|
||||
// Generate PKCE challenge
|
||||
function generatePKCE(): { verifier: string; challenge: string } {
|
||||
const verifier = crypto.randomBytes(32).toString('base64url');
|
||||
const challenge = crypto
|
||||
.createHash('sha256')
|
||||
.update(verifier)
|
||||
.digest('base64url');
|
||||
return { verifier, challenge };
|
||||
}
|
||||
|
||||
// OAuth URL
|
||||
function getAuthorizationUrl(state: string, challenge: string): string {
|
||||
const params = new URLSearchParams({
|
||||
client_id: GITHUB_CLIENT_ID,
|
||||
redirect_uri: 'noodl://github-callback',
|
||||
scope: 'repo read:org read:user',
|
||||
state,
|
||||
code_challenge: challenge,
|
||||
code_challenge_method: 'S256'
|
||||
});
|
||||
return `https://github.com/login/oauth/authorize?${params}`;
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Deep Link Handler
|
||||
|
||||
```typescript
|
||||
// packages/noodl-editor/src/main/main.js
|
||||
|
||||
// Register protocol handler
|
||||
app.setAsDefaultProtocolClient('noodl');
|
||||
|
||||
// Handle deep links
|
||||
app.on('open-url', (event, url) => {
|
||||
event.preventDefault();
|
||||
if (url.startsWith('noodl://github-callback')) {
|
||||
const params = new URL(url).searchParams;
|
||||
const code = params.get('code');
|
||||
const state = params.get('state');
|
||||
handleGitHubCallback(code, state);
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### 5. Secure Token Storage
|
||||
|
||||
```typescript
|
||||
// Use electron's safeStorage API
|
||||
import { safeStorage } from 'electron';
|
||||
|
||||
async function storeToken(token: string): Promise<void> {
|
||||
const encrypted = safeStorage.encryptString(token);
|
||||
await store.set('github.token', encrypted.toString('base64'));
|
||||
}
|
||||
|
||||
async function getToken(): Promise<string | null> {
|
||||
const encrypted = await store.get('github.token');
|
||||
if (!encrypted) return null;
|
||||
return safeStorage.decryptString(Buffer.from(encrypted, 'base64'));
|
||||
}
|
||||
```
|
||||
|
||||
### 6. Integration with Existing Git Auth
|
||||
|
||||
```typescript
|
||||
// packages/noodl-utils/LocalProjectsModel.ts
|
||||
|
||||
setCurrentGlobalGitAuth(projectId: string) {
|
||||
const func = async (endpoint: string) => {
|
||||
if (endpoint.includes('github.com')) {
|
||||
// Try OAuth token first
|
||||
const oauthToken = await GitHubOAuthService.instance.getToken();
|
||||
if (oauthToken) {
|
||||
return {
|
||||
username: 'oauth2',
|
||||
password: oauthToken
|
||||
};
|
||||
}
|
||||
|
||||
// Fall back to PAT
|
||||
const config = await GitStore.get('github', projectId);
|
||||
return {
|
||||
username: 'noodl',
|
||||
password: config?.password
|
||||
};
|
||||
}
|
||||
// ... rest of existing logic
|
||||
};
|
||||
|
||||
setRequestGitAccount(func);
|
||||
}
|
||||
```
|
||||
|
||||
## Files to Create
|
||||
|
||||
1. `packages/noodl-editor/src/editor/src/services/GitHubOAuthService.ts`
|
||||
2. `packages/noodl-editor/src/editor/src/services/GitHubApiClient.ts`
|
||||
3. `packages/noodl-core-ui/src/preview/launcher/Launcher/components/GitHubAccountCard/GitHubAccountCard.tsx`
|
||||
4. `packages/noodl-core-ui/src/preview/launcher/Launcher/components/GitHubConnectButton/GitHubConnectButton.tsx`
|
||||
5. `packages/noodl-core-ui/src/preview/launcher/Launcher/components/OrgSelector/OrgSelector.tsx`
|
||||
6. `packages/noodl-editor/src/editor/src/views/panels/VersionControlPanel/components/GitProviderPopout/sections/OAuthSection.tsx`
|
||||
|
||||
## Files to Modify
|
||||
|
||||
1. `packages/noodl-editor/src/main/main.js`
|
||||
- Add deep link protocol handler for `noodl://`
|
||||
|
||||
2. `packages/noodl-utils/LocalProjectsModel.ts`
|
||||
- Update `setCurrentGlobalGitAuth` to prefer OAuth token
|
||||
|
||||
3. `packages/noodl-editor/src/editor/src/views/panels/VersionControlPanel/components/GitProviderPopout/GitProviderPopout.tsx`
|
||||
- Add OAuth option alongside PAT
|
||||
|
||||
4. `packages/noodl-core-ui/src/preview/launcher/Launcher/components/LauncherSidebar/LauncherSidebar.tsx`
|
||||
- Add GitHub account display/connect button
|
||||
|
||||
## Implementation Steps
|
||||
|
||||
### Phase 1: OAuth Service Foundation
|
||||
1. Create GitHubOAuthService class
|
||||
2. Implement PKCE flow
|
||||
3. Set up deep link handler in main process
|
||||
4. Implement secure token storage
|
||||
|
||||
### Phase 2: UI Components
|
||||
1. Create GitHubConnectButton
|
||||
2. Create GitHubAccountCard
|
||||
3. Add OAuth section to GitProviderPopout
|
||||
4. Add account display to launcher sidebar
|
||||
|
||||
### Phase 3: API Integration
|
||||
1. Create GitHubApiClient for REST API calls
|
||||
2. Implement user info fetching
|
||||
3. Implement organization listing
|
||||
4. Create OrgSelector component
|
||||
|
||||
### Phase 4: Git Integration
|
||||
1. Update LocalProjectsModel auth function
|
||||
2. Test with git operations
|
||||
3. Handle token expiry/revocation
|
||||
4. Add fallback to PAT
|
||||
|
||||
### Phase 5: Polish
|
||||
1. Error handling and messages
|
||||
2. Offline handling
|
||||
3. Loading states
|
||||
4. Settings persistence
|
||||
|
||||
## Security Considerations
|
||||
|
||||
1. **PKCE**: Use PKCE flow instead of client secret (more secure for desktop apps)
|
||||
2. **Token Storage**: Use electron's safeStorage API (OS-level encryption)
|
||||
3. **State Parameter**: Verify state to prevent CSRF attacks
|
||||
4. **Scope Limitation**: Request minimum required scopes
|
||||
5. **Token Exposure**: Never log tokens, clear from memory when not needed
|
||||
|
||||
## Testing Checklist
|
||||
|
||||
- [ ] OAuth flow completes successfully
|
||||
- [ ] Token stored securely
|
||||
- [ ] Token retrieved correctly for git operations
|
||||
- [ ] Clone works with OAuth token
|
||||
- [ ] Push works with OAuth token
|
||||
- [ ] Pull works with OAuth token
|
||||
- [ ] Disconnect clears token
|
||||
- [ ] Fallback to PAT works
|
||||
- [ ] Organizations listed correctly
|
||||
- [ ] Deep link works on macOS
|
||||
- [ ] Deep link works on Windows
|
||||
- [ ] Handles network errors gracefully
|
||||
- [ ] Handles token revocation gracefully
|
||||
|
||||
## Dependencies
|
||||
|
||||
- DASH-001 (for launcher context to display account)
|
||||
|
||||
### External Dependencies
|
||||
|
||||
May need to add:
|
||||
```json
|
||||
{
|
||||
"keytar": "^7.9.0" // Alternative to safeStorage for older Electron
|
||||
}
|
||||
```
|
||||
|
||||
## Blocked By
|
||||
|
||||
- DASH-001 (Tabbed Navigation) - for launcher UI placement
|
||||
|
||||
## Blocks
|
||||
|
||||
- GIT-003 (Repository Cloning) - needs auth for private repos
|
||||
- COMP-004 (Organization Components) - needs org access
|
||||
|
||||
## Estimated Effort
|
||||
|
||||
- OAuth service: 4-6 hours
|
||||
- Deep link handler: 2-3 hours
|
||||
- UI components: 3-4 hours
|
||||
- Git integration: 2-3 hours
|
||||
- Testing & polish: 3-4 hours
|
||||
- **Total: 14-20 hours**
|
||||
|
||||
## Success Criteria
|
||||
|
||||
1. Users can authenticate with GitHub via OAuth
|
||||
2. OAuth tokens are stored securely
|
||||
3. Git operations work with OAuth tokens
|
||||
4. Users can see their connected account
|
||||
5. Users can disconnect and reconnect
|
||||
6. PAT remains available as fallback
|
||||
7. Flow works on both macOS and Windows
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
- Multiple GitHub account support
|
||||
- GitLab OAuth
|
||||
- Bitbucket OAuth
|
||||
- GitHub Enterprise support
|
||||
- Fine-grained personal access tokens
|
||||
@@ -0,0 +1,426 @@
|
||||
# GIT-002: Git Status Dashboard Visibility
|
||||
|
||||
## Overview
|
||||
|
||||
Surface git status information directly in the project list on the dashboard, allowing users to see at a glance which projects need attention (uncommitted changes, unpushed commits, available updates) without opening each project.
|
||||
|
||||
## Context
|
||||
|
||||
Currently, git status is only visible inside the VersionControlPanel after opening a project. Users with many projects have no way to know which ones have uncommitted changes or need syncing.
|
||||
|
||||
The new launcher already has mock data for git sync status in `LauncherProjectCard`, but it's not connected to real data.
|
||||
|
||||
### Existing Infrastructure
|
||||
|
||||
From `LauncherProjectCard.tsx`:
|
||||
```typescript
|
||||
export enum CloudSyncType {
|
||||
None = 'none',
|
||||
Git = 'git'
|
||||
}
|
||||
|
||||
export interface LauncherProjectData {
|
||||
cloudSyncMeta: {
|
||||
type: CloudSyncType;
|
||||
source?: string; // Remote URL
|
||||
};
|
||||
pullAmount?: number;
|
||||
pushAmount?: number;
|
||||
uncommittedChangesAmount?: number;
|
||||
}
|
||||
```
|
||||
|
||||
From `VersionControlPanel/context/fetch.context.ts`:
|
||||
```typescript
|
||||
// Already calculates:
|
||||
localCommitCount // Commits ahead of remote
|
||||
remoteCommitCount // Commits behind remote
|
||||
workingDirectoryStatus // Uncommitted files
|
||||
```
|
||||
|
||||
## Requirements
|
||||
|
||||
### Functional Requirements
|
||||
|
||||
1. **Status Indicators in Project List**
|
||||
- Not Initialized: Gray indicator, no version control
|
||||
- Local Only: Yellow indicator, git but no remote
|
||||
- Synced: Green checkmark, up to date
|
||||
- Has Uncommitted Changes: Yellow dot, local modifications
|
||||
- Ahead: Blue up arrow, local commits to push
|
||||
- Behind: Orange down arrow, remote commits to pull
|
||||
- Diverged: Red warning, both ahead and behind
|
||||
|
||||
2. **Status Details**
|
||||
- Tooltip showing details on hover
|
||||
- "3 commits to push, 2 to pull"
|
||||
- "5 uncommitted files"
|
||||
- Last sync time
|
||||
|
||||
3. **Quick Actions**
|
||||
- Quick sync button (fetch + show status)
|
||||
- Link to open Version Control panel
|
||||
|
||||
4. **Background Refresh**
|
||||
- Check status on dashboard load
|
||||
- Periodic refresh (every 5 minutes)
|
||||
- Manual refresh button
|
||||
- Status cached to avoid repeated git operations
|
||||
|
||||
5. **Performance**
|
||||
- Parallel status checks for multiple projects
|
||||
- Debounced/throttled to avoid overwhelming git
|
||||
- Cached results with TTL
|
||||
|
||||
### Non-Functional Requirements
|
||||
|
||||
- Status check per project: <500ms
|
||||
- Dashboard load not blocked by status checks
|
||||
- Works offline (shows cached/stale data)
|
||||
|
||||
## Data Model
|
||||
|
||||
### Git Status Types
|
||||
|
||||
```typescript
|
||||
enum ProjectGitStatus {
|
||||
Unknown = 'unknown', // Haven't checked yet
|
||||
NotInitialized = 'not-init', // Not a git repo
|
||||
LocalOnly = 'local-only', // Git but no remote
|
||||
Synced = 'synced', // Up to date with remote
|
||||
Uncommitted = 'uncommitted', // Has local changes
|
||||
Ahead = 'ahead', // Has commits to push
|
||||
Behind = 'behind', // Has commits to pull
|
||||
Diverged = 'diverged', // Both ahead and behind
|
||||
Error = 'error' // Failed to check
|
||||
}
|
||||
|
||||
interface ProjectGitStatusDetails {
|
||||
status: ProjectGitStatus;
|
||||
aheadCount?: number;
|
||||
behindCount?: number;
|
||||
uncommittedCount?: number;
|
||||
lastFetchTime?: number;
|
||||
remoteUrl?: string;
|
||||
currentBranch?: string;
|
||||
error?: string;
|
||||
}
|
||||
```
|
||||
|
||||
### Cache Structure
|
||||
|
||||
```typescript
|
||||
interface GitStatusCache {
|
||||
[projectPath: string]: {
|
||||
status: ProjectGitStatusDetails;
|
||||
checkedAt: number;
|
||||
isStale: boolean;
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
## Technical Approach
|
||||
|
||||
### 1. Git Status Service
|
||||
|
||||
```typescript
|
||||
// packages/noodl-editor/src/editor/src/services/ProjectGitStatusService.ts
|
||||
|
||||
class ProjectGitStatusService {
|
||||
private static instance: ProjectGitStatusService;
|
||||
private cache: GitStatusCache = {};
|
||||
private checkQueue: Set<string> = new Set();
|
||||
private isChecking = false;
|
||||
|
||||
// Check single project
|
||||
async checkStatus(projectPath: string): Promise<ProjectGitStatusDetails>;
|
||||
|
||||
// Check multiple projects (batched)
|
||||
async checkStatusBatch(projectPaths: string[]): Promise<Map<string, ProjectGitStatusDetails>>;
|
||||
|
||||
// Get cached status
|
||||
getCachedStatus(projectPath: string): ProjectGitStatusDetails | null;
|
||||
|
||||
// Clear cache
|
||||
invalidateCache(projectPath?: string): void;
|
||||
|
||||
// Subscribe to status changes
|
||||
onStatusChanged(callback: (path: string, status: ProjectGitStatusDetails) => void): () => void;
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Status Check Implementation
|
||||
|
||||
```typescript
|
||||
async checkStatus(projectPath: string): Promise<ProjectGitStatusDetails> {
|
||||
const git = new Git(mergeProject);
|
||||
|
||||
try {
|
||||
// Check if it's a git repo
|
||||
const gitPath = await getTopLevelWorkingDirectory(projectPath);
|
||||
if (!gitPath) {
|
||||
return { status: ProjectGitStatus.NotInitialized };
|
||||
}
|
||||
|
||||
await git.openRepository(projectPath);
|
||||
|
||||
// Check for remote
|
||||
const remoteName = await git.getRemoteName();
|
||||
if (!remoteName) {
|
||||
return { status: ProjectGitStatus.LocalOnly };
|
||||
}
|
||||
|
||||
// Get working directory status
|
||||
const workingStatus = await git.status();
|
||||
const uncommittedCount = workingStatus.length;
|
||||
|
||||
// Get commit counts (requires fetch for accuracy)
|
||||
const commits = await git.getCommitsCurrentBranch();
|
||||
const aheadCount = commits.filter(c => c.isLocalAhead).length;
|
||||
const behindCount = commits.filter(c => c.isRemoteAhead).length;
|
||||
|
||||
// Determine status
|
||||
let status: ProjectGitStatus;
|
||||
if (uncommittedCount > 0) {
|
||||
status = ProjectGitStatus.Uncommitted;
|
||||
} else if (aheadCount > 0 && behindCount > 0) {
|
||||
status = ProjectGitStatus.Diverged;
|
||||
} else if (aheadCount > 0) {
|
||||
status = ProjectGitStatus.Ahead;
|
||||
} else if (behindCount > 0) {
|
||||
status = ProjectGitStatus.Behind;
|
||||
} else {
|
||||
status = ProjectGitStatus.Synced;
|
||||
}
|
||||
|
||||
return {
|
||||
status,
|
||||
aheadCount,
|
||||
behindCount,
|
||||
uncommittedCount,
|
||||
lastFetchTime: Date.now(),
|
||||
remoteUrl: git.OriginUrl,
|
||||
currentBranch: await git.getCurrentBranchName()
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
status: ProjectGitStatus.Error,
|
||||
error: error.message
|
||||
};
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Dashboard Integration Hook
|
||||
|
||||
```typescript
|
||||
// packages/noodl-core-ui/src/preview/launcher/Launcher/hooks/useProjectGitStatus.ts
|
||||
|
||||
function useProjectGitStatus(projectPaths: string[]) {
|
||||
const [statuses, setStatuses] = useState<Map<string, ProjectGitStatusDetails>>(new Map());
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
// Initial check
|
||||
ProjectGitStatusService.instance
|
||||
.checkStatusBatch(projectPaths)
|
||||
.then(setStatuses)
|
||||
.finally(() => setIsLoading(false));
|
||||
|
||||
// Subscribe to updates
|
||||
const unsubscribe = ProjectGitStatusService.instance.onStatusChanged((path, status) => {
|
||||
setStatuses(prev => new Map(prev).set(path, status));
|
||||
});
|
||||
|
||||
return unsubscribe;
|
||||
}, [projectPaths]);
|
||||
|
||||
const refresh = useCallback(() => {
|
||||
ProjectGitStatusService.instance.invalidateCache();
|
||||
// Re-trigger check
|
||||
}, []);
|
||||
|
||||
return { statuses, isLoading, refresh };
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Visual Status Badge
|
||||
|
||||
Already started in DASH-002 as `GitStatusBadge`, but needs real data connection:
|
||||
|
||||
```typescript
|
||||
// Enhanced GitStatusBadge props
|
||||
interface GitStatusBadgeProps {
|
||||
status: ProjectGitStatus;
|
||||
details: ProjectGitStatusDetails;
|
||||
showTooltip?: boolean;
|
||||
size?: 'small' | 'medium';
|
||||
}
|
||||
```
|
||||
|
||||
## Files to Create
|
||||
|
||||
1. `packages/noodl-editor/src/editor/src/services/ProjectGitStatusService.ts`
|
||||
2. `packages/noodl-core-ui/src/preview/launcher/Launcher/hooks/useProjectGitStatus.ts`
|
||||
3. `packages/noodl-core-ui/src/preview/launcher/Launcher/components/GitStatusBadge/GitStatusBadge.tsx` (if not created in DASH-002)
|
||||
4. `packages/noodl-core-ui/src/preview/launcher/Launcher/components/GitStatusBadge/GitStatusBadge.module.scss`
|
||||
5. `packages/noodl-core-ui/src/preview/launcher/Launcher/components/GitStatusTooltip/GitStatusTooltip.tsx`
|
||||
|
||||
## Files to Modify
|
||||
|
||||
1. `packages/noodl-core-ui/src/preview/launcher/Launcher/views/Projects.tsx`
|
||||
- Use `useProjectGitStatus` hook
|
||||
- Pass status to project cards/rows
|
||||
|
||||
2. `packages/noodl-core-ui/src/preview/launcher/Launcher/components/ProjectList/ProjectListRow.tsx`
|
||||
- Display GitStatusBadge with real data
|
||||
|
||||
3. `packages/noodl-core-ui/src/preview/launcher/Launcher/components/LauncherProjectCard/LauncherProjectCard.tsx`
|
||||
- Update to use real status data (for grid view)
|
||||
|
||||
4. `packages/noodl-core-ui/src/preview/launcher/Launcher/Launcher.tsx`
|
||||
- Replace mock project data with real data connection
|
||||
|
||||
## Visual Specifications
|
||||
|
||||
### Status Badge Icons & Colors
|
||||
|
||||
| Status | Icon | Color | Background |
|
||||
|--------|------|-------|------------|
|
||||
| Unknown | ◌ (spinner) | Gray | Transparent |
|
||||
| Not Initialized | ⊘ | Gray (#6B7280) | Transparent |
|
||||
| Local Only | 💾 | Yellow (#EAB308) | Yellow/10 |
|
||||
| Synced | ✓ | Green (#22C55E) | Green/10 |
|
||||
| Uncommitted | ● | Yellow (#EAB308) | Yellow/10 |
|
||||
| Ahead | ↑ | Blue (#3B82F6) | Blue/10 |
|
||||
| Behind | ↓ | Orange (#F97316) | Orange/10 |
|
||||
| Diverged | ⚠ | Red (#EF4444) | Red/10 |
|
||||
| Error | ✕ | Red (#EF4444) | Red/10 |
|
||||
|
||||
### Tooltip Content
|
||||
|
||||
```
|
||||
┌─────────────────────────────────┐
|
||||
│ main branch │
|
||||
│ ↑ 3 commits to push │
|
||||
│ ↓ 2 commits to pull │
|
||||
│ ● 5 uncommitted files │
|
||||
│ │
|
||||
│ Last synced: 10 minutes ago │
|
||||
│ Remote: github.com/user/repo │
|
||||
└─────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Implementation Steps
|
||||
|
||||
### Phase 1: Service Foundation
|
||||
1. Create ProjectGitStatusService
|
||||
2. Implement single project status check
|
||||
3. Add caching logic
|
||||
4. Create batch checking with parallelization
|
||||
|
||||
### Phase 2: Hook & Data Flow
|
||||
1. Create useProjectGitStatus hook
|
||||
2. Connect to Projects view
|
||||
3. Replace mock data with real data
|
||||
4. Add loading states
|
||||
|
||||
### Phase 3: Visual Components
|
||||
1. Create/update GitStatusBadge
|
||||
2. Create GitStatusTooltip
|
||||
3. Integrate into ProjectListRow
|
||||
4. Integrate into LauncherProjectCard
|
||||
|
||||
### Phase 4: Refresh & Background
|
||||
1. Add manual refresh button
|
||||
2. Implement periodic background refresh
|
||||
3. Add refresh on window focus
|
||||
4. Handle offline state
|
||||
|
||||
### Phase 5: Polish
|
||||
1. Performance optimization
|
||||
2. Error handling
|
||||
3. Stale data indicators
|
||||
4. Animation on status change
|
||||
|
||||
## Performance Considerations
|
||||
|
||||
1. **Parallel Checking**: Check up to 5 projects simultaneously
|
||||
2. **Debouncing**: Don't re-check same project within 10 seconds
|
||||
3. **Cache TTL**: Status valid for 5 minutes, stale after
|
||||
4. **Lazy Loading**: Only check visible projects first
|
||||
5. **Background Priority**: Use requestIdleCallback for non-visible
|
||||
|
||||
```typescript
|
||||
// Throttled batch check
|
||||
async checkStatusBatch(projectPaths: string[]): Promise<Map<string, ProjectGitStatusDetails>> {
|
||||
const CONCURRENCY = 5;
|
||||
const results = new Map();
|
||||
|
||||
for (let i = 0; i < projectPaths.length; i += CONCURRENCY) {
|
||||
const batch = projectPaths.slice(i, i + CONCURRENCY);
|
||||
const batchResults = await Promise.all(
|
||||
batch.map(path => this.checkStatus(path))
|
||||
);
|
||||
batch.forEach((path, idx) => results.set(path, batchResults[idx]));
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
```
|
||||
|
||||
## Testing Checklist
|
||||
|
||||
- [ ] Detects non-git project correctly
|
||||
- [ ] Detects git project without remote
|
||||
- [ ] Shows synced status when up to date
|
||||
- [ ] Shows uncommitted when local changes exist
|
||||
- [ ] Shows ahead when local commits exist
|
||||
- [ ] Shows behind when remote commits exist
|
||||
- [ ] Shows diverged when both ahead and behind
|
||||
- [ ] Tooltip shows correct details
|
||||
- [ ] Refresh updates status
|
||||
- [ ] Status persists across dashboard navigation
|
||||
- [ ] Handles deleted projects gracefully
|
||||
- [ ] Handles network errors gracefully
|
||||
- [ ] Performance acceptable with 20+ projects
|
||||
|
||||
## Dependencies
|
||||
|
||||
- DASH-002 (Project List Redesign) - for UI integration
|
||||
|
||||
## Blocked By
|
||||
|
||||
- DASH-002
|
||||
|
||||
## Blocks
|
||||
|
||||
- GIT-004 (Auto-initialization) - needs status detection
|
||||
- GIT-005 (Enhanced Push/Pull) - shares status infrastructure
|
||||
|
||||
## Estimated Effort
|
||||
|
||||
- Status service: 3-4 hours
|
||||
- Hook & data flow: 2-3 hours
|
||||
- Visual components: 2-3 hours
|
||||
- Background refresh: 2-3 hours
|
||||
- Polish & testing: 2-3 hours
|
||||
- **Total: 11-16 hours**
|
||||
|
||||
## Success Criteria
|
||||
|
||||
1. Git status visible at a glance in project list
|
||||
2. Status updates without manual refresh
|
||||
3. Tooltip provides actionable details
|
||||
4. Performance acceptable with many projects
|
||||
5. Works offline with cached data
|
||||
6. Handles edge cases gracefully
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
- Quick commit from dashboard
|
||||
- Quick push/pull buttons per project
|
||||
- Bulk sync all projects
|
||||
- Branch indicator
|
||||
- Last commit message preview
|
||||
- Contributor avatars (from git log)
|
||||
@@ -0,0 +1,346 @@
|
||||
# GIT-003: Repository Cloning
|
||||
|
||||
## Overview
|
||||
|
||||
Add the ability to clone GitHub repositories directly from the Noodl dashboard, similar to how VS Code handles cloning. Users can browse their repositories, select one, choose a local folder, and have the project cloned and opened automatically.
|
||||
|
||||
## Context
|
||||
|
||||
Currently, to work with an existing Noodl project from GitHub, users must:
|
||||
1. Clone the repo manually using git CLI or another tool
|
||||
2. Open Noodl
|
||||
3. Use "Open folder" to navigate to the cloned project
|
||||
|
||||
This task streamlines that to:
|
||||
1. Click "Clone from GitHub"
|
||||
2. Select repository
|
||||
3. Choose folder
|
||||
4. Project opens automatically
|
||||
|
||||
### Existing Infrastructure
|
||||
|
||||
The `noodl-git` package already has clone functionality:
|
||||
```typescript
|
||||
// From git.ts
|
||||
async clone({ url, directory, singleBranch, onProgress }: GitCloneOptions): Promise<void>
|
||||
```
|
||||
|
||||
And clone tests show it working:
|
||||
```typescript
|
||||
await git.clone({
|
||||
url: 'https://github.com/github/testrepo.git',
|
||||
directory: tempDir,
|
||||
onProgress: (progress) => { result.push(progress); }
|
||||
});
|
||||
```
|
||||
|
||||
## Requirements
|
||||
|
||||
### Functional Requirements
|
||||
|
||||
1. **Clone Entry Points**
|
||||
- "Clone Repository" button in dashboard toolbar
|
||||
- "Clone from GitHub" option in "Create Project" menu
|
||||
- Right-click empty area → "Clone Repository"
|
||||
|
||||
2. **Repository Browser**
|
||||
- List user's repositories (requires OAuth from GIT-001)
|
||||
- List organization repositories
|
||||
- Search/filter repositories
|
||||
- Show repo details: name, description, visibility, last updated
|
||||
- "Clone URL" input for direct URL entry
|
||||
|
||||
3. **Folder Selection**
|
||||
- Native folder picker dialog
|
||||
- Remember last used parent folder
|
||||
- Validate folder is empty or doesn't exist
|
||||
- Show full path before cloning
|
||||
|
||||
4. **Clone Process**
|
||||
- Progress indicator with stages
|
||||
- Cancel button
|
||||
- Error handling with clear messages
|
||||
- Retry option on failure
|
||||
|
||||
5. **Post-Clone Actions**
|
||||
- Automatically open project in editor
|
||||
- Add to recent projects
|
||||
- Show success notification
|
||||
|
||||
6. **Branch Selection (Optional)**
|
||||
- Default to main/master
|
||||
- Option to select different branch
|
||||
- Shallow clone option for large repos
|
||||
|
||||
### Non-Functional Requirements
|
||||
|
||||
- Clone progress updates smoothly
|
||||
- Cancellation works immediately
|
||||
- Handles large repositories
|
||||
- Works with private repositories (with auth)
|
||||
- Clear error messages for common failures
|
||||
|
||||
## Technical Approach
|
||||
|
||||
### 1. Clone Service
|
||||
|
||||
```typescript
|
||||
// packages/noodl-editor/src/editor/src/services/CloneService.ts
|
||||
|
||||
interface CloneOptions {
|
||||
url: string;
|
||||
directory: string;
|
||||
branch?: string;
|
||||
shallow?: boolean;
|
||||
onProgress?: (progress: CloneProgress) => void;
|
||||
}
|
||||
|
||||
interface CloneProgress {
|
||||
phase: 'counting' | 'compressing' | 'receiving' | 'resolving' | 'checking-out';
|
||||
percent: number;
|
||||
message: string;
|
||||
}
|
||||
|
||||
interface CloneResult {
|
||||
success: boolean;
|
||||
projectPath?: string;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
class CloneService {
|
||||
private static instance: CloneService;
|
||||
private activeClone: AbortController | null = null;
|
||||
|
||||
async clone(options: CloneOptions): Promise<CloneResult>;
|
||||
cancel(): void;
|
||||
|
||||
// GitHub API integration
|
||||
async listUserRepos(): Promise<GitHubRepo[]>;
|
||||
async listOrgRepos(orgName: string): Promise<GitHubRepo[]>;
|
||||
async searchRepos(query: string): Promise<GitHubRepo[]>;
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Repository Browser Component
|
||||
|
||||
```typescript
|
||||
// RepoBrowser.tsx
|
||||
|
||||
interface RepoBrowserProps {
|
||||
onSelect: (repo: GitHubRepo) => void;
|
||||
onUrlSubmit: (url: string) => void;
|
||||
}
|
||||
|
||||
interface GitHubRepo {
|
||||
id: number;
|
||||
name: string;
|
||||
fullName: string;
|
||||
description: string;
|
||||
private: boolean;
|
||||
htmlUrl: string;
|
||||
cloneUrl: string;
|
||||
sshUrl: string;
|
||||
defaultBranch: string;
|
||||
updatedAt: string;
|
||||
owner: {
|
||||
login: string;
|
||||
avatarUrl: string;
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Clone Modal Flow
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ Clone Repository [×] │
|
||||
├─────────────────────────────────────────────────────────────────────┤
|
||||
│ ┌─────────────────────────────────────────────────────────────────┐ │
|
||||
│ │ [🔍 Search repositories... ] │ │
|
||||
│ └─────────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ [Your Repositories ▾] [Organizations: acme-corp ▾] │
|
||||
│ │
|
||||
│ ┌─────────────────────────────────────────────────────────────────┐ │
|
||||
│ │ 📁 noodl-project-template ★ 12 2 days ago │ │
|
||||
│ │ A starter template for Noodl projects [Private 🔒] │ │
|
||||
│ ├─────────────────────────────────────────────────────────────────┤ │
|
||||
│ │ 📁 my-awesome-app ★ 5 1 week ago │ │
|
||||
│ │ An awesome application built with Noodl [Public 🌍] │ │
|
||||
│ ├─────────────────────────────────────────────────────────────────┤ │
|
||||
│ │ 📁 client-dashboard ★ 0 3 weeks ago │ │
|
||||
│ │ Dashboard for client project [Private 🔒] │ │
|
||||
│ └─────────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ─── OR enter repository URL ───────────────────────────────────── │
|
||||
│ ┌─────────────────────────────────────────────────────────────────┐ │
|
||||
│ │ https://github.com/user/repo.git │ │
|
||||
│ └─────────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ [Cancel] [Next →] │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 4. Folder Selection Step
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ Clone Repository [×] │
|
||||
├─────────────────────────────────────────────────────────────────────┤
|
||||
│ Repository: github.com/user/my-awesome-app │
|
||||
│ │
|
||||
│ Clone to: │
|
||||
│ ┌─────────────────────────────────────────────────────────────────┐ │
|
||||
│ │ /Users/richard/Projects/my-awesome-app [Browse...] │ │
|
||||
│ └─────────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ☐ Clone only the default branch (faster) │
|
||||
│ │
|
||||
│ [← Back] [Cancel] [Clone]│
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 5. Progress Step
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ Cloning Repository [×] │
|
||||
├─────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ Cloning my-awesome-app... │
|
||||
│ │
|
||||
│ ┌─────────────────────────────────────────────────────────────────┐ │
|
||||
│ │████████████████████████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░│ 42% │ │
|
||||
│ └─────────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ Receiving objects: 1,234 of 2,891 │
|
||||
│ │
|
||||
│ [Cancel] │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Files to Create
|
||||
|
||||
1. `packages/noodl-editor/src/editor/src/services/CloneService.ts`
|
||||
2. `packages/noodl-core-ui/src/preview/launcher/Launcher/components/CloneModal/CloneModal.tsx`
|
||||
3. `packages/noodl-core-ui/src/preview/launcher/Launcher/components/CloneModal/CloneModal.module.scss`
|
||||
4. `packages/noodl-core-ui/src/preview/launcher/Launcher/components/CloneModal/RepoBrowser.tsx`
|
||||
5. `packages/noodl-core-ui/src/preview/launcher/Launcher/components/CloneModal/FolderSelector.tsx`
|
||||
6. `packages/noodl-core-ui/src/preview/launcher/Launcher/components/CloneModal/CloneProgress.tsx`
|
||||
7. `packages/noodl-core-ui/src/preview/launcher/Launcher/components/RepoCard/RepoCard.tsx`
|
||||
8. `packages/noodl-editor/src/editor/src/services/GitHubApiClient.ts` (if not created in GIT-001)
|
||||
|
||||
## Files to Modify
|
||||
|
||||
1. `packages/noodl-core-ui/src/preview/launcher/Launcher/views/Projects.tsx`
|
||||
- Add "Clone Repository" button to toolbar
|
||||
|
||||
2. `packages/noodl-core-ui/src/preview/launcher/Launcher/Launcher.tsx`
|
||||
- Add clone modal state and rendering
|
||||
|
||||
3. `packages/noodl-utils/LocalProjectsModel.ts`
|
||||
- Add cloned project to recent projects list
|
||||
|
||||
4. `packages/noodl-editor/src/editor/src/views/projectsview.ts`
|
||||
- Ensure cloned project can be opened (may already work)
|
||||
|
||||
## Implementation Steps
|
||||
|
||||
### Phase 1: Clone Service
|
||||
1. Create CloneService wrapper around noodl-git
|
||||
2. Add progress normalization
|
||||
3. Add cancellation support
|
||||
4. Test with public repository
|
||||
|
||||
### Phase 2: URL-Based Cloning
|
||||
1. Create basic CloneModal with URL input
|
||||
2. Create FolderSelector component
|
||||
3. Create CloneProgress component
|
||||
4. Wire up clone flow
|
||||
|
||||
### Phase 3: Repository Browser
|
||||
1. Create GitHubApiClient (or extend from GIT-001)
|
||||
2. Create RepoBrowser component
|
||||
3. Create RepoCard component
|
||||
4. Add search/filter functionality
|
||||
|
||||
### Phase 4: Integration
|
||||
1. Add clone button to dashboard
|
||||
2. Open cloned project automatically
|
||||
3. Add to recent projects
|
||||
4. Handle errors gracefully
|
||||
|
||||
### Phase 5: Polish
|
||||
1. Remember last folder
|
||||
2. Add branch selection
|
||||
3. Add shallow clone option
|
||||
4. Improve error messages
|
||||
|
||||
## Error Handling
|
||||
|
||||
| Error | User Message | Recovery |
|
||||
|-------|--------------|----------|
|
||||
| Network error | "Unable to connect. Check your internet connection." | Retry button |
|
||||
| Auth required | "This repository requires authentication. Connect your GitHub account." | Link to OAuth |
|
||||
| Repo not found | "Repository not found. Check the URL and try again." | Edit URL |
|
||||
| Permission denied | "You don't have access to this repository." | Suggest checking permissions |
|
||||
| Folder not empty | "The selected folder is not empty. Choose an empty folder." | Folder picker |
|
||||
| Disk full | "Not enough disk space to clone this repository." | Show required space |
|
||||
|
||||
## Testing Checklist
|
||||
|
||||
- [ ] Clone public repository via URL
|
||||
- [ ] Clone private repository with OAuth token
|
||||
- [ ] Clone private repository with PAT
|
||||
- [ ] Repository browser shows user repos
|
||||
- [ ] Repository browser shows org repos
|
||||
- [ ] Search/filter works
|
||||
- [ ] Folder picker opens and works
|
||||
- [ ] Progress updates smoothly
|
||||
- [ ] Cancel stops clone in progress
|
||||
- [ ] Cloned project opens automatically
|
||||
- [ ] Project appears in recent projects
|
||||
- [ ] Error messages are helpful
|
||||
- [ ] Works with various repo sizes
|
||||
- [ ] Handles repos with submodules
|
||||
|
||||
## Dependencies
|
||||
|
||||
- GIT-001 (GitHub OAuth) - for repository browser with private repos
|
||||
- DASH-001 (Tabbed Navigation) - for dashboard integration
|
||||
|
||||
## Blocked By
|
||||
|
||||
- GIT-001 (partially - URL cloning works without OAuth)
|
||||
|
||||
## Blocks
|
||||
|
||||
- COMP-004 (Organization Components) - uses similar repo browsing
|
||||
|
||||
## Estimated Effort
|
||||
|
||||
- Clone service: 2-3 hours
|
||||
- URL-based clone modal: 3-4 hours
|
||||
- Repository browser: 4-5 hours
|
||||
- Integration & auto-open: 2-3 hours
|
||||
- Polish & error handling: 2-3 hours
|
||||
- **Total: 13-18 hours**
|
||||
|
||||
## Success Criteria
|
||||
|
||||
1. Users can clone by entering a URL
|
||||
2. Users can browse and select their repositories
|
||||
3. Clone progress is visible and accurate
|
||||
4. Cloned projects open automatically
|
||||
5. Private repos work with authentication
|
||||
6. Errors are handled gracefully
|
||||
7. Process can be cancelled
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
- Clone from other providers (GitLab, Bitbucket)
|
||||
- Clone specific branch/tag
|
||||
- Clone with submodules options
|
||||
- Clone into new project template
|
||||
- Clone history (recently cloned repos)
|
||||
- Detect Noodl projects vs generic repos
|
||||
@@ -0,0 +1,388 @@
|
||||
# GIT-004: Auto-Initialization & Commit Encouragement
|
||||
|
||||
## Overview
|
||||
|
||||
Make version control a default part of the Noodl workflow by automatically initializing git for new projects and gently encouraging regular commits. This helps users avoid losing work and prepares them for collaboration.
|
||||
|
||||
## Context
|
||||
|
||||
Currently:
|
||||
- New projects are not git-initialized by default
|
||||
- Users must manually open Version Control panel and initialize
|
||||
- There's no prompting to commit changes
|
||||
- Closing a project with uncommitted changes has no warning
|
||||
|
||||
Many Noodl users are designers or low-code developers who may not be familiar with git. By making version control automatic and unobtrusive, we help them develop good habits without requiring git expertise.
|
||||
|
||||
### Existing Infrastructure
|
||||
|
||||
From `LocalProjectsModel.ts`:
|
||||
```typescript
|
||||
async isGitProject(project: ProjectModel): Promise<boolean> {
|
||||
const gitPath = await getTopLevelWorkingDirectory(project._retainedProjectDirectory);
|
||||
return gitPath !== null;
|
||||
}
|
||||
```
|
||||
|
||||
From `git.ts`:
|
||||
```typescript
|
||||
async initNewRepo(baseDir: string, options?: { bare: boolean }): Promise<void> {
|
||||
if (this.baseDir) return;
|
||||
this.baseDir = await init(baseDir, options);
|
||||
await this._setupRepository();
|
||||
}
|
||||
```
|
||||
|
||||
## Requirements
|
||||
|
||||
### Functional Requirements
|
||||
|
||||
1. **Auto-Initialization**
|
||||
- New projects are git-initialized by default
|
||||
- Initial commit with project creation
|
||||
- Option to disable in settings
|
||||
- Existing non-git projects can be initialized easily
|
||||
|
||||
2. **Commit Encouragement**
|
||||
- Periodic reminder when changes are uncommitted
|
||||
- Reminder appears as subtle notification, not modal
|
||||
- "Commit now" quick action
|
||||
- "Remind me later" option
|
||||
- Configurable reminder interval
|
||||
|
||||
3. **Quick Commit**
|
||||
- One-click commit from notification
|
||||
- Simple commit message input
|
||||
- Default message suggestion
|
||||
- Option to open full Version Control panel
|
||||
|
||||
4. **Close Warning**
|
||||
- Warning when closing project with uncommitted changes
|
||||
- Show number of uncommitted files
|
||||
- Options: "Commit & Close", "Close Anyway", "Cancel"
|
||||
- Can be disabled in settings
|
||||
|
||||
5. **Settings**
|
||||
- Enable/disable auto-initialization
|
||||
- Enable/disable commit reminders
|
||||
- Reminder interval (15min, 30min, 1hr, 2hr)
|
||||
- Enable/disable close warning
|
||||
|
||||
### Non-Functional Requirements
|
||||
|
||||
- Reminders are non-intrusive
|
||||
- Quick commit is fast (<2 seconds)
|
||||
- Auto-init doesn't slow project creation
|
||||
- Works offline
|
||||
|
||||
## Technical Approach
|
||||
|
||||
### 1. Auto-Initialization in Project Creation
|
||||
|
||||
```typescript
|
||||
// packages/noodl-editor/src/editor/src/models/projectmodel.ts
|
||||
|
||||
async createNewProject(name: string, template?: string): Promise<ProjectModel> {
|
||||
const project = await this._createProject(name, template);
|
||||
|
||||
// Auto-initialize git if enabled
|
||||
if (EditorSettings.instance.get('git.autoInitialize') !== false) {
|
||||
try {
|
||||
const git = new Git(mergeProject);
|
||||
await git.initNewRepo(project._retainedProjectDirectory);
|
||||
await git.commit('Initial commit');
|
||||
} catch (error) {
|
||||
console.warn('Failed to auto-initialize git:', error);
|
||||
// Don't fail project creation if git init fails
|
||||
}
|
||||
}
|
||||
|
||||
return project;
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Commit Reminder Service
|
||||
|
||||
```typescript
|
||||
// packages/noodl-editor/src/editor/src/services/CommitReminderService.ts
|
||||
|
||||
class CommitReminderService {
|
||||
private static instance: CommitReminderService;
|
||||
private reminderTimer: NodeJS.Timer | null = null;
|
||||
private lastRemindedAt: number = 0;
|
||||
|
||||
// Start monitoring for uncommitted changes
|
||||
start(): void;
|
||||
stop(): void;
|
||||
|
||||
// Check if reminder should show
|
||||
shouldShowReminder(): Promise<boolean>;
|
||||
|
||||
// Show/dismiss reminder
|
||||
showReminder(): void;
|
||||
dismissReminder(snoozeMinutes?: number): void;
|
||||
|
||||
// Events
|
||||
onReminderTriggered(callback: () => void): () => void;
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Quick Commit Component
|
||||
|
||||
```typescript
|
||||
// packages/noodl-core-ui/src/components/git/QuickCommitPopup/QuickCommitPopup.tsx
|
||||
|
||||
interface QuickCommitPopupProps {
|
||||
uncommittedCount: number;
|
||||
suggestedMessage: string;
|
||||
onCommit: (message: string) => Promise<void>;
|
||||
onDismiss: () => void;
|
||||
onOpenFullPanel: () => void;
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Close Warning Dialog
|
||||
|
||||
```typescript
|
||||
// packages/noodl-core-ui/src/components/git/UnsavedChangesDialog/UnsavedChangesDialog.tsx
|
||||
|
||||
interface UnsavedChangesDialogProps {
|
||||
uncommittedCount: number;
|
||||
onCommitAndClose: () => Promise<void>;
|
||||
onCloseAnyway: () => void;
|
||||
onCancel: () => void;
|
||||
}
|
||||
```
|
||||
|
||||
### 5. Default Commit Messages
|
||||
|
||||
```typescript
|
||||
// Smart default commit message generation
|
||||
function generateDefaultCommitMessage(changes: GitStatus[]): string {
|
||||
const added = changes.filter(c => c.status === 'added');
|
||||
const modified = changes.filter(c => c.status === 'modified');
|
||||
const deleted = changes.filter(c => c.status === 'deleted');
|
||||
|
||||
const parts: string[] = [];
|
||||
|
||||
if (added.length > 0) {
|
||||
if (added.length === 1) {
|
||||
parts.push(`Add ${getComponentName(added[0].path)}`);
|
||||
} else {
|
||||
parts.push(`Add ${added.length} files`);
|
||||
}
|
||||
}
|
||||
|
||||
if (modified.length > 0) {
|
||||
if (modified.length === 1) {
|
||||
parts.push(`Update ${getComponentName(modified[0].path)}`);
|
||||
} else {
|
||||
parts.push(`Update ${modified.length} files`);
|
||||
}
|
||||
}
|
||||
|
||||
if (deleted.length > 0) {
|
||||
parts.push(`Remove ${deleted.length} files`);
|
||||
}
|
||||
|
||||
return parts.join(', ') || 'Update project';
|
||||
}
|
||||
```
|
||||
|
||||
## UI Mockups
|
||||
|
||||
### Commit Reminder Notification
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ 💾 You have 5 uncommitted changes │
|
||||
│ │
|
||||
│ It's been 30 minutes since your last commit. │
|
||||
│ │
|
||||
│ [Commit Now] [Remind Me Later ▾] [Dismiss] │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Quick Commit Popup
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Quick Commit [×] │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ 5 files changed │
|
||||
│ │
|
||||
│ Message: │
|
||||
│ ┌─────────────────────────────────────────────────────────┐ │
|
||||
│ │ Update LoginPage and add UserProfile component │ │
|
||||
│ └─────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ [Open Version Control] [Cancel] [Commit] │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Close Warning Dialog
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ ⚠️ Uncommitted Changes [×] │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ You have 5 uncommitted changes in this project. │
|
||||
│ │
|
||||
│ These changes will be preserved locally but not versioned. │
|
||||
│ To keep a history of your work, commit before closing. │
|
||||
│ │
|
||||
│ ☐ Don't show this again │
|
||||
│ │
|
||||
│ [Cancel] [Close Anyway] [Commit & Close] │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Files to Create
|
||||
|
||||
1. `packages/noodl-editor/src/editor/src/services/CommitReminderService.ts`
|
||||
2. `packages/noodl-core-ui/src/components/git/QuickCommitPopup/QuickCommitPopup.tsx`
|
||||
3. `packages/noodl-core-ui/src/components/git/QuickCommitPopup/QuickCommitPopup.module.scss`
|
||||
4. `packages/noodl-core-ui/src/components/git/UnsavedChangesDialog/UnsavedChangesDialog.tsx`
|
||||
5. `packages/noodl-core-ui/src/components/git/CommitReminderToast/CommitReminderToast.tsx`
|
||||
6. `packages/noodl-editor/src/editor/src/utils/git/defaultCommitMessage.ts`
|
||||
|
||||
## Files to Modify
|
||||
|
||||
1. `packages/noodl-editor/src/editor/src/models/projectmodel.ts`
|
||||
- Add auto-initialization in project creation
|
||||
|
||||
2. `packages/noodl-editor/src/editor/src/pages/EditorPage/EditorPage.tsx`
|
||||
- Add close warning handler
|
||||
- Integrate CommitReminderService
|
||||
|
||||
3. `packages/noodl-utils/editorsettings.ts`
|
||||
- Add git-related settings
|
||||
|
||||
4. `packages/noodl-editor/src/editor/src/views/panels/EditorSettingsPanel/`
|
||||
- Add git settings section
|
||||
|
||||
5. `packages/noodl-editor/src/main/main.js`
|
||||
- Handle close event for warning
|
||||
|
||||
## Settings Schema
|
||||
|
||||
```typescript
|
||||
interface GitSettings {
|
||||
// Auto-initialization
|
||||
'git.autoInitialize': boolean; // default: true
|
||||
|
||||
// Commit reminders
|
||||
'git.commitReminders.enabled': boolean; // default: true
|
||||
'git.commitReminders.intervalMinutes': number; // default: 30
|
||||
|
||||
// Close warning
|
||||
'git.closeWarning.enabled': boolean; // default: true
|
||||
|
||||
// Quick commit
|
||||
'git.quickCommit.suggestMessage': boolean; // default: true
|
||||
}
|
||||
```
|
||||
|
||||
## Implementation Steps
|
||||
|
||||
### Phase 1: Auto-Initialization
|
||||
1. Add git.autoInitialize setting
|
||||
2. Modify project creation to init git
|
||||
3. Add initial commit
|
||||
4. Test with new projects
|
||||
|
||||
### Phase 2: Settings UI
|
||||
1. Add Git section to Editor Settings panel
|
||||
2. Implement all settings toggles
|
||||
3. Store settings in EditorSettings
|
||||
|
||||
### Phase 3: Commit Reminder Service
|
||||
1. Create CommitReminderService
|
||||
2. Add timer-based reminder check
|
||||
3. Create CommitReminderToast component
|
||||
4. Integrate with editor lifecycle
|
||||
|
||||
### Phase 4: Quick Commit
|
||||
1. Create QuickCommitPopup component
|
||||
2. Implement default message generation
|
||||
3. Wire up commit action
|
||||
4. Add "Open full panel" option
|
||||
|
||||
### Phase 5: Close Warning
|
||||
1. Create UnsavedChangesDialog
|
||||
2. Hook into project close event
|
||||
3. Implement "Commit & Close" flow
|
||||
4. Add "Don't show again" option
|
||||
|
||||
### Phase 6: Polish
|
||||
1. Snooze functionality
|
||||
2. Notification stacking
|
||||
3. Animation/transitions
|
||||
4. Edge case handling
|
||||
|
||||
## Testing Checklist
|
||||
|
||||
- [ ] New project is git-initialized by default
|
||||
- [ ] Initial commit is created
|
||||
- [ ] Auto-init can be disabled
|
||||
- [ ] Commit reminder appears after interval
|
||||
- [ ] Reminder shows correct uncommitted count
|
||||
- [ ] "Commit Now" opens quick commit popup
|
||||
- [ ] "Remind Me Later" snoozes correctly
|
||||
- [ ] Quick commit works with default message
|
||||
- [ ] Quick commit works with custom message
|
||||
- [ ] Close warning appears with uncommitted changes
|
||||
- [ ] "Commit & Close" works
|
||||
- [ ] "Close Anyway" works
|
||||
- [ ] "Don't show again" persists
|
||||
- [ ] Settings toggle all features correctly
|
||||
- [ ] Works when offline
|
||||
|
||||
## Edge Cases
|
||||
|
||||
1. **Project already has git**: Don't re-initialize, just work with existing
|
||||
2. **Template with git**: Use template's git if present, else init fresh
|
||||
3. **Init fails**: Log warning, don't block project creation
|
||||
4. **Commit fails**: Show error, offer to open Version Control panel
|
||||
5. **Large commit**: Show progress, don't block UI
|
||||
6. **No changes on reminder check**: Don't show reminder
|
||||
|
||||
## Dependencies
|
||||
|
||||
- GIT-002 (Git Status Dashboard) - for status detection infrastructure
|
||||
|
||||
## Blocked By
|
||||
|
||||
- GIT-002 (shares status checking code)
|
||||
|
||||
## Blocks
|
||||
|
||||
- None
|
||||
|
||||
## Estimated Effort
|
||||
|
||||
- Auto-initialization: 2-3 hours
|
||||
- Settings UI: 2-3 hours
|
||||
- Commit reminder service: 3-4 hours
|
||||
- Quick commit popup: 2-3 hours
|
||||
- Close warning: 2-3 hours
|
||||
- Polish: 2-3 hours
|
||||
- **Total: 13-19 hours**
|
||||
|
||||
## Success Criteria
|
||||
|
||||
1. New projects have git by default
|
||||
2. Users are gently reminded to commit
|
||||
3. Committing is easy and fast
|
||||
4. Users are warned before losing work
|
||||
5. All features can be disabled
|
||||
6. Non-intrusive to workflow
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
- Commit streak/gamification
|
||||
- Auto-commit on significant changes
|
||||
- Commit templates
|
||||
- Branch suggestions
|
||||
- Integration with cloud backup
|
||||
@@ -0,0 +1,388 @@
|
||||
# GIT-005: Enhanced Push/Pull UI
|
||||
|
||||
## Overview
|
||||
|
||||
Improve the push/pull experience with better visibility, branch management, conflict previews, and dashboard-level sync controls. Make syncing with remotes more intuitive and less error-prone.
|
||||
|
||||
## Context
|
||||
|
||||
The current Version Control panel has push/pull functionality via `GitStatusButton`, but:
|
||||
- Only visible when the panel is open
|
||||
- Branch switching is buried in menus
|
||||
- No preview of what will be pulled
|
||||
- Conflict resolution is complex
|
||||
|
||||
This task brings sync operations to the forefront and adds safeguards.
|
||||
|
||||
### Existing Infrastructure
|
||||
|
||||
From `GitStatusButton.tsx`:
|
||||
```typescript
|
||||
// Status kinds: 'default', 'fetch', 'error-fetch', 'pull', 'push', 'push-repository', 'set-authorization'
|
||||
|
||||
case 'push': {
|
||||
label = localCommitCount === 1 ? `Push 1 local commit` : `Push ${localCommitCount} local commits`;
|
||||
}
|
||||
|
||||
case 'pull': {
|
||||
label = remoteCommitCount === 1 ? `Pull 1 remote commit` : `Pull ${remoteCommitCount} remote commits`;
|
||||
}
|
||||
```
|
||||
|
||||
From `fetch.context.ts`:
|
||||
```typescript
|
||||
localCommitCount // Commits ahead of remote
|
||||
remoteCommitCount // Commits behind remote
|
||||
currentBranch // Current branch info
|
||||
branches // All branches
|
||||
```
|
||||
|
||||
## Requirements
|
||||
|
||||
### Functional Requirements
|
||||
|
||||
1. **Dashboard Sync Button**
|
||||
- Visible sync button in project row (from GIT-002)
|
||||
- One-click fetch & show status
|
||||
- Quick push/pull from dashboard
|
||||
|
||||
2. **Branch Selector**
|
||||
- Dropdown showing current branch
|
||||
- Quick switch between branches
|
||||
- Create new branch option
|
||||
- Branch search for projects with many branches
|
||||
- Remote branch indicators
|
||||
|
||||
3. **Pull Preview**
|
||||
- Show what commits will be pulled
|
||||
- List affected files
|
||||
- Warning for potential conflicts
|
||||
- "Preview" mode before actual pull
|
||||
|
||||
4. **Conflict Prevention**
|
||||
- Check for conflicts before pull
|
||||
- Suggest stashing changes first
|
||||
- Clear conflict resolution workflow
|
||||
- "Abort" option during conflicts
|
||||
|
||||
5. **Push Confirmation**
|
||||
- Show commits being pushed
|
||||
- Branch protection warning (if pushing to main)
|
||||
- Force push warning (if needed)
|
||||
|
||||
6. **Sync Status Header**
|
||||
- Always-visible status in editor header
|
||||
- Current branch display
|
||||
- Quick sync actions
|
||||
- Connection indicator
|
||||
|
||||
### Non-Functional Requirements
|
||||
|
||||
- Sync operations don't block UI
|
||||
- Progress visible for long operations
|
||||
- Works offline (queues operations)
|
||||
- Clear error messages
|
||||
|
||||
## Technical Approach
|
||||
|
||||
### 1. Sync Status Header Component
|
||||
|
||||
```typescript
|
||||
// packages/noodl-core-ui/src/components/git/SyncStatusHeader/SyncStatusHeader.tsx
|
||||
|
||||
interface SyncStatusHeaderProps {
|
||||
currentBranch: string;
|
||||
aheadCount: number;
|
||||
behindCount: number;
|
||||
hasUncommitted: boolean;
|
||||
isOnline: boolean;
|
||||
lastFetchTime: number;
|
||||
onPush: () => void;
|
||||
onPull: () => void;
|
||||
onFetch: () => void;
|
||||
onBranchChange: (branch: string) => void;
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Branch Selector Component
|
||||
|
||||
```typescript
|
||||
// packages/noodl-core-ui/src/components/git/BranchSelector/BranchSelector.tsx
|
||||
|
||||
interface BranchSelectorProps {
|
||||
currentBranch: Branch;
|
||||
branches: Branch[];
|
||||
onSelect: (branch: Branch) => void;
|
||||
onCreate: (name: string) => void;
|
||||
}
|
||||
|
||||
interface Branch {
|
||||
name: string;
|
||||
nameWithoutRemote: string;
|
||||
isLocal: boolean;
|
||||
isRemote: boolean;
|
||||
isCurrent: boolean;
|
||||
lastCommit?: {
|
||||
sha: string;
|
||||
message: string;
|
||||
date: string;
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Pull Preview Modal
|
||||
|
||||
```typescript
|
||||
// packages/noodl-core-ui/src/components/git/PullPreviewModal/PullPreviewModal.tsx
|
||||
|
||||
interface PullPreviewModalProps {
|
||||
commits: Commit[];
|
||||
affectedFiles: FileChange[];
|
||||
hasConflicts: boolean;
|
||||
conflictFiles?: string[];
|
||||
onPull: () => Promise<void>;
|
||||
onCancel: () => void;
|
||||
}
|
||||
|
||||
interface Commit {
|
||||
sha: string;
|
||||
message: string;
|
||||
author: string;
|
||||
date: string;
|
||||
}
|
||||
|
||||
interface FileChange {
|
||||
path: string;
|
||||
status: 'added' | 'modified' | 'deleted';
|
||||
hasConflict: boolean;
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Conflict Resolution Flow
|
||||
|
||||
```typescript
|
||||
// packages/noodl-editor/src/editor/src/services/ConflictResolutionService.ts
|
||||
|
||||
class ConflictResolutionService {
|
||||
// Check for potential conflicts before pull
|
||||
async previewConflicts(): Promise<ConflictPreview>;
|
||||
|
||||
// Handle stashing
|
||||
async stashAndPull(): Promise<void>;
|
||||
|
||||
// Resolution strategies
|
||||
async resolveWithOurs(file: string): Promise<void>;
|
||||
async resolveWithTheirs(file: string): Promise<void>;
|
||||
async openMergeTool(file: string): Promise<void>;
|
||||
|
||||
// Abort
|
||||
async abortMerge(): Promise<void>;
|
||||
}
|
||||
```
|
||||
|
||||
## UI Mockups
|
||||
|
||||
### Sync Status Header (Editor)
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||
│ [main ▾] ↑3 ↓2 ●5 uncommitted 🟢 Connected [Fetch] [Pull] [Push] │
|
||||
└─────────────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Branch Selector Dropdown
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────┐
|
||||
│ 🔍 Search branches... │
|
||||
├─────────────────────────────────────┤
|
||||
│ LOCAL │
|
||||
│ ✓ main │
|
||||
│ feature/new-login │
|
||||
│ bugfix/header-styling │
|
||||
├─────────────────────────────────────┤
|
||||
│ REMOTE │
|
||||
│ origin/develop │
|
||||
│ origin/release-1.0 │
|
||||
├─────────────────────────────────────┤
|
||||
│ + Create new branch... │
|
||||
└─────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Pull Preview Modal
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ Pull Preview [×] │
|
||||
├─────────────────────────────────────────────────────────────────────┤
|
||||
│ Pulling 3 commits from origin/main │
|
||||
│ │
|
||||
│ COMMITS │
|
||||
│ ┌─────────────────────────────────────────────────────────────────┐ │
|
||||
│ │ a1b2c3d Fix login validation John Doe 2 hours ago │ │
|
||||
│ │ d4e5f6g Add password reset flow Jane Smith 5 hours ago │ │
|
||||
│ │ h7i8j9k Update dependencies John Doe 1 day ago │ │
|
||||
│ └─────────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ FILES CHANGED (12) │
|
||||
│ ┌─────────────────────────────────────────────────────────────────┐ │
|
||||
│ │ M components/LoginPage.ndjson │ │
|
||||
│ │ M components/Header.ndjson │ │
|
||||
│ │ A components/PasswordReset.ndjson │ │
|
||||
│ │ D components/OldLogin.ndjson │ │
|
||||
│ │ ⚠️ M project.json (potential conflict) │ │
|
||||
│ └─────────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ⚠️ You have uncommitted changes. They will be stashed before pull. │
|
||||
│ │
|
||||
│ [Cancel] [Pull Now] │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Conflict Warning
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ ⚠️ Potential Conflicts Detected [×] │
|
||||
├─────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ The following files have been modified both locally and remotely: │
|
||||
│ │
|
||||
│ • project.json │
|
||||
│ • components/LoginPage.ndjson │
|
||||
│ │
|
||||
│ Noodl will attempt to merge these changes automatically, but you │
|
||||
│ may need to resolve conflicts manually. │
|
||||
│ │
|
||||
│ Recommended: Commit your local changes first for a cleaner merge. │
|
||||
│ │
|
||||
│ [Cancel] [Commit First] [Pull Anyway] │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Files to Create
|
||||
|
||||
1. `packages/noodl-core-ui/src/components/git/SyncStatusHeader/SyncStatusHeader.tsx`
|
||||
2. `packages/noodl-core-ui/src/components/git/SyncStatusHeader/SyncStatusHeader.module.scss`
|
||||
3. `packages/noodl-core-ui/src/components/git/BranchSelector/BranchSelector.tsx`
|
||||
4. `packages/noodl-core-ui/src/components/git/BranchSelector/BranchSelector.module.scss`
|
||||
5. `packages/noodl-core-ui/src/components/git/PullPreviewModal/PullPreviewModal.tsx`
|
||||
6. `packages/noodl-core-ui/src/components/git/PushConfirmModal/PushConfirmModal.tsx`
|
||||
7. `packages/noodl-core-ui/src/components/git/ConflictWarningModal/ConflictWarningModal.tsx`
|
||||
8. `packages/noodl-editor/src/editor/src/services/ConflictResolutionService.ts`
|
||||
|
||||
## Files to Modify
|
||||
|
||||
1. `packages/noodl-editor/src/editor/src/pages/EditorPage/EditorPage.tsx`
|
||||
- Add SyncStatusHeader to editor layout
|
||||
|
||||
2. `packages/noodl-editor/src/editor/src/views/panels/VersionControlPanel/VersionControlPanel.tsx`
|
||||
- Integrate new BranchSelector
|
||||
- Add pull preview before pulling
|
||||
|
||||
3. `packages/noodl-editor/src/editor/src/views/panels/VersionControlPanel/components/GitStatusButton.tsx`
|
||||
- Update to use new pull/push flows
|
||||
|
||||
4. `packages/noodl-editor/src/editor/src/views/panels/VersionControlPanel/context/fetch.context.ts`
|
||||
- Add preview fetch logic
|
||||
- Add conflict detection
|
||||
|
||||
5. `packages/noodl-core-ui/src/preview/launcher/Launcher/components/ProjectList/ProjectListRow.tsx`
|
||||
- Add quick sync button (if not in GIT-002)
|
||||
|
||||
## Implementation Steps
|
||||
|
||||
### Phase 1: Branch Selector
|
||||
1. Create BranchSelector component
|
||||
2. Implement search/filter
|
||||
3. Add create branch flow
|
||||
4. Integrate into Version Control panel
|
||||
|
||||
### Phase 2: Sync Status Header
|
||||
1. Create SyncStatusHeader component
|
||||
2. Add to editor layout
|
||||
3. Wire up actions
|
||||
4. Add connection indicator
|
||||
|
||||
### Phase 3: Pull Preview
|
||||
1. Create PullPreviewModal
|
||||
2. Implement commit/file listing
|
||||
3. Add conflict detection
|
||||
4. Wire up pull action
|
||||
|
||||
### Phase 4: Conflict Handling
|
||||
1. Create ConflictWarningModal
|
||||
2. Create ConflictResolutionService
|
||||
3. Implement stash-before-pull
|
||||
4. Add abort functionality
|
||||
|
||||
### Phase 5: Push Enhancements
|
||||
1. Create PushConfirmModal
|
||||
2. Add branch protection warning
|
||||
3. Show commit list
|
||||
4. Handle force push
|
||||
|
||||
### Phase 6: Dashboard Integration
|
||||
1. Add sync button to project rows
|
||||
2. Quick push/pull from dashboard
|
||||
3. Update status after sync
|
||||
|
||||
## Testing Checklist
|
||||
|
||||
- [ ] Branch selector shows all branches
|
||||
- [ ] Branch search filters correctly
|
||||
- [ ] Switching branches works
|
||||
- [ ] Creating new branch works
|
||||
- [ ] Sync status header shows correct counts
|
||||
- [ ] Fetch updates status
|
||||
- [ ] Pull preview shows correct commits
|
||||
- [ ] Pull preview shows affected files
|
||||
- [ ] Conflict warning appears when appropriate
|
||||
- [ ] Stash-before-pull works
|
||||
- [ ] Pull completes successfully
|
||||
- [ ] Push confirmation shows commits
|
||||
- [ ] Push completes successfully
|
||||
- [ ] Dashboard sync button works
|
||||
- [ ] Offline state handled gracefully
|
||||
|
||||
## Dependencies
|
||||
|
||||
- GIT-002 (Git Status Dashboard) - for dashboard integration
|
||||
- GIT-001 (GitHub OAuth) - for authenticated operations
|
||||
|
||||
## Blocked By
|
||||
|
||||
- GIT-002
|
||||
|
||||
## Blocks
|
||||
|
||||
- None
|
||||
|
||||
## Estimated Effort
|
||||
|
||||
- Branch selector: 3-4 hours
|
||||
- Sync status header: 2-3 hours
|
||||
- Pull preview: 4-5 hours
|
||||
- Conflict handling: 4-5 hours
|
||||
- Push enhancements: 2-3 hours
|
||||
- Dashboard integration: 2-3 hours
|
||||
- **Total: 17-23 hours**
|
||||
|
||||
## Success Criteria
|
||||
|
||||
1. Branch switching is easy and visible
|
||||
2. Users can preview what will be pulled
|
||||
3. Conflict potential is detected before pull
|
||||
4. Stashing is automatic when needed
|
||||
5. Push shows what's being pushed
|
||||
6. Quick sync available from dashboard
|
||||
7. Status always visible in editor
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
- Pull request creation
|
||||
- Branch comparison
|
||||
- Revert/cherry-pick commits
|
||||
- Squash commits before push
|
||||
- Auto-sync on save (optional)
|
||||
- Branch naming conventions/templates
|
||||
@@ -0,0 +1,248 @@
|
||||
# GIT Series: Git & GitHub Integration
|
||||
|
||||
## Overview
|
||||
|
||||
The GIT series transforms Noodl's version control experience from a manual, expert-only feature into a seamless, integrated part of the development workflow. By adding GitHub OAuth, surfacing git status in the dashboard, and encouraging good version control habits, we make collaboration accessible to all Noodl users.
|
||||
|
||||
## Target Environment
|
||||
|
||||
- **Editor**: React 19 version only
|
||||
- **Runtime**: Not affected (git is editor-only)
|
||||
- **Backwards Compatibility**: Existing git projects continue to work
|
||||
|
||||
## Task Dependency Graph
|
||||
|
||||
```
|
||||
GIT-001 (GitHub OAuth)
|
||||
│
|
||||
├──────────────────────────┐
|
||||
│ │
|
||||
▼ ▼
|
||||
GIT-002 (Dashboard Status) GIT-003 (Repository Cloning)
|
||||
│
|
||||
├──────────────────────────┐
|
||||
│ │
|
||||
▼ ▼
|
||||
GIT-004 (Auto-Init) GIT-005 (Enhanced Push/Pull)
|
||||
```
|
||||
|
||||
## Task Summary
|
||||
|
||||
| Task ID | Name | Est. Hours | Priority |
|
||||
|---------|------|------------|----------|
|
||||
| GIT-001 | GitHub OAuth Integration | 14-20 | Critical |
|
||||
| GIT-002 | Git Status Dashboard Visibility | 11-16 | High |
|
||||
| GIT-003 | Repository Cloning | 13-18 | High |
|
||||
| GIT-004 | Auto-Initialization & Commit Encouragement | 13-19 | Medium |
|
||||
| GIT-005 | Enhanced Push/Pull UI | 17-23 | Medium |
|
||||
|
||||
**Total Estimated: 68-96 hours**
|
||||
|
||||
## Implementation Order
|
||||
|
||||
### Week 1-2: Authentication & Status
|
||||
1. **GIT-001** - GitHub OAuth (foundation for GitHub API access)
|
||||
2. **GIT-002** - Dashboard status (leverages DASH-002 project list)
|
||||
|
||||
### Week 3: Cloning & Basic Flow
|
||||
3. **GIT-003** - Repository cloning (depends on OAuth for private repos)
|
||||
|
||||
### Week 4: Polish & Encouragement
|
||||
4. **GIT-004** - Auto-initialization (depends on status detection)
|
||||
5. **GIT-005** - Enhanced push/pull (depends on status infrastructure)
|
||||
|
||||
## Existing Infrastructure
|
||||
|
||||
The codebase already has solid git foundations to build on:
|
||||
|
||||
### noodl-git Package
|
||||
```
|
||||
packages/noodl-git/src/
|
||||
├── git.ts # Main Git class
|
||||
├── core/
|
||||
│ ├── clone.ts # Clone operations
|
||||
│ ├── push.ts # Push operations
|
||||
│ ├── pull.ts # Pull operations
|
||||
│ └── ...
|
||||
├── actions/ # Higher-level actions
|
||||
└── constants.ts
|
||||
```
|
||||
|
||||
Key existing methods:
|
||||
- `git.initNewRepo()` - Initialize new repository
|
||||
- `git.clone()` - Clone with progress
|
||||
- `git.push()` - Push with progress
|
||||
- `git.pull()` - Pull with rebase
|
||||
- `git.status()` - Working directory status
|
||||
- `git.getBranches()` - List branches
|
||||
- `git.getCommitsCurrentBranch()` - Commit history
|
||||
|
||||
### Version Control Panel
|
||||
```
|
||||
packages/noodl-editor/src/editor/src/views/panels/VersionControlPanel/
|
||||
├── VersionControlPanel.tsx
|
||||
├── components/
|
||||
│ ├── GitStatusButton.tsx # Push/pull status
|
||||
│ ├── GitProviderPopout/ # Credentials management
|
||||
│ ├── LocalChanges.tsx # Uncommitted files
|
||||
│ ├── History.tsx # Commit history
|
||||
│ └── BranchMerge.tsx # Branch operations
|
||||
└── context/
|
||||
└── fetch.context.ts # Git state management
|
||||
```
|
||||
|
||||
### Credentials Storage
|
||||
- `GitStore` - Stores credentials per-project encrypted
|
||||
- `trampoline-askpass-handler` - Handles git credential prompts
|
||||
- Currently uses PAT (Personal Access Token) for GitHub
|
||||
|
||||
## Key Technical Decisions
|
||||
|
||||
### OAuth vs PAT
|
||||
|
||||
**Current**: Personal Access Token per project
|
||||
- User creates PAT on GitHub
|
||||
- Copies to Noodl per project
|
||||
- Stored encrypted in GitStore
|
||||
|
||||
**New (GIT-001)**: OAuth + PAT fallback
|
||||
- One-click GitHub OAuth
|
||||
- Token stored globally
|
||||
- PAT remains for non-GitHub remotes
|
||||
|
||||
### Status Checking Strategy
|
||||
|
||||
**Approach**: Batch + Cache
|
||||
- Check multiple projects in parallel
|
||||
- Cache results with TTL
|
||||
- Background refresh
|
||||
|
||||
**Why**: Git status requires opening each repo, which is slow. Caching makes dashboard responsive while keeping data fresh.
|
||||
|
||||
### Auto-Initialization
|
||||
|
||||
**Approach**: Opt-out
|
||||
- Git initialized by default
|
||||
- Initial commit created automatically
|
||||
- Can disable in settings
|
||||
|
||||
**Why**: Most users benefit from version control. Making it default reduces "I lost my work" issues.
|
||||
|
||||
## Services to Create
|
||||
|
||||
| Service | Location | Purpose |
|
||||
|---------|----------|---------|
|
||||
| GitHubOAuthService | noodl-editor/services | OAuth flow, token management |
|
||||
| GitHubApiClient | noodl-editor/services | GitHub REST API calls |
|
||||
| ProjectGitStatusService | noodl-editor/services | Batch status checking, caching |
|
||||
| CloneService | noodl-editor/services | Clone wrapper with progress |
|
||||
| CommitReminderService | noodl-editor/services | Periodic commit reminders |
|
||||
| ConflictResolutionService | noodl-editor/services | Conflict detection, resolution |
|
||||
|
||||
## Components to Create
|
||||
|
||||
| Component | Package | Purpose |
|
||||
|-----------|---------|---------|
|
||||
| GitHubConnectButton | noodl-core-ui | OAuth trigger button |
|
||||
| GitHubAccountCard | noodl-core-ui | Connected account display |
|
||||
| GitStatusBadge | noodl-core-ui | Status indicator in list |
|
||||
| CloneModal | noodl-core-ui | Clone flow modal |
|
||||
| RepoBrowser | noodl-core-ui | Repository list/search |
|
||||
| QuickCommitPopup | noodl-core-ui | Fast commit dialog |
|
||||
| SyncStatusHeader | noodl-core-ui | Editor header sync status |
|
||||
| BranchSelector | noodl-core-ui | Branch dropdown |
|
||||
| PullPreviewModal | noodl-core-ui | Preview before pull |
|
||||
|
||||
## Dependencies
|
||||
|
||||
### On DASH Series
|
||||
- GIT-002 → DASH-002 (project list for status display)
|
||||
- GIT-001 → DASH-001 (launcher context for account display)
|
||||
|
||||
### External Packages
|
||||
May need:
|
||||
```json
|
||||
{
|
||||
"@octokit/rest": "^20.0.0" // GitHub API client (optional)
|
||||
}
|
||||
```
|
||||
|
||||
## Security Considerations
|
||||
|
||||
1. **OAuth Tokens**: Store with electron's safeStorage API
|
||||
2. **PKCE Flow**: Use PKCE for OAuth (no client secret in app)
|
||||
3. **Token Scope**: Request minimum necessary (repo, read:org, read:user)
|
||||
4. **Credential Cache**: Clear on logout/disconnect
|
||||
5. **PAT Fallback**: Encrypted per-project storage continues
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
### Unit Tests
|
||||
- OAuth token exchange
|
||||
- Status calculation logic
|
||||
- Conflict detection
|
||||
- Default commit message generation
|
||||
|
||||
### Integration Tests
|
||||
- Clone from public repo
|
||||
- Clone from private repo with auth
|
||||
- Push/pull with mock remote
|
||||
- Branch operations
|
||||
|
||||
### Manual Testing
|
||||
- Full OAuth flow
|
||||
- Dashboard status refresh
|
||||
- Clone flow end-to-end
|
||||
- Commit reminder timing
|
||||
- Conflict resolution
|
||||
|
||||
## Cline Usage Notes
|
||||
|
||||
### Before Starting Each Task
|
||||
|
||||
1. Read the task document completely
|
||||
2. Review existing git infrastructure:
|
||||
- `packages/noodl-git/src/git.ts`
|
||||
- `packages/noodl-editor/src/editor/src/views/panels/VersionControlPanel/`
|
||||
3. Check GitStore and credential handling
|
||||
|
||||
### Key Gotchas
|
||||
|
||||
1. **Git operations are async**: Always use try/catch, git can fail
|
||||
2. **Repository paths**: Use `_retainedProjectDirectory` from ProjectModel
|
||||
3. **Merge strategy**: Noodl has custom merge for project.json (`mergeProject`)
|
||||
4. **Auth caching**: Credentials cached by trampoline, may need clearing
|
||||
5. **Electron context**: Some git ops need main process (deep links)
|
||||
|
||||
### Testing Git Operations
|
||||
|
||||
```bash
|
||||
# In tests directory, run git tests
|
||||
npm run test:editor -- --grep="Git"
|
||||
```
|
||||
|
||||
## Success Criteria (Series Complete)
|
||||
|
||||
1. ✅ Users can authenticate with GitHub via OAuth
|
||||
2. ✅ Git status visible in project dashboard
|
||||
3. ✅ Users can clone repositories from UI
|
||||
4. ✅ New projects have git by default
|
||||
5. ✅ Users are reminded to commit regularly
|
||||
6. ✅ Pull/push is intuitive with previews
|
||||
7. ✅ Branch management is accessible
|
||||
|
||||
## Future Work (Post-GIT)
|
||||
|
||||
The GIT series enables:
|
||||
- **COMP series**: Shared component repositories
|
||||
- **DEPLOY series**: Auto-push to frontend repo on deploy
|
||||
- **Community features**: Public component sharing
|
||||
|
||||
## Files in This Series
|
||||
|
||||
- `GIT-001-github-oauth.md`
|
||||
- `GIT-002-dashboard-git-status.md`
|
||||
- `GIT-003-repository-cloning.md`
|
||||
- `GIT-004-auto-init-commit-encouragement.md`
|
||||
- `GIT-005-enhanced-push-pull.md`
|
||||
- `GIT-OVERVIEW.md` (this file)
|
||||
Reference in New Issue
Block a user