Files
OpenNoodl/dev-docs/tasks/phase-3/TASK-002-github-integration/GIT-002-dashboard-git-status.md

427 lines
12 KiB
Markdown

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