Restored from origin/main (b4663fb): - .github/ workflows and issue templates - .gitignore (proper exclusions) - .opencode/agent/web_developer.md - AGENTS.md, BUILD.md, PROGRESS.md - dev-docs/ (9 architecture/implementation docs) - docs/screenshots/ (4 UI screenshots) - images/ (CodeNomad icons) - package-lock.json (dependency lockfile) - tasks/ (25+ project task files) Also restored original source files that were modified: - packages/ui/src/App.tsx - packages/ui/src/lib/logger.ts - packages/ui/src/stores/instances.ts - packages/server/src/server/routes/workspaces.ts - packages/server/src/workspaces/manager.ts - packages/server/src/workspaces/runtime.ts - packages/server/package.json Kept new additions: - Install-*.bat/.sh (enhanced installers) - Launch-*.bat/.sh (new launchers) - README.md (SEO optimized with GLM 4.7)
281 lines
5.9 KiB
Markdown
281 lines
5.9 KiB
Markdown
# Task 002: Empty State UI & Folder Selection
|
|
|
|
## Goal
|
|
|
|
Create the initial empty state interface that appears when no instances are running, with folder selection capability.
|
|
|
|
## Prerequisites
|
|
|
|
- Task 001 completed (project setup)
|
|
- Basic understanding of SolidJS components
|
|
- Electron IPC understanding
|
|
|
|
## Acceptance Criteria
|
|
|
|
- [ ] Empty state displays when no instances exist
|
|
- [ ] "Select Folder" button visible and styled
|
|
- [ ] Clicking button triggers Electron dialog
|
|
- [ ] Selected folder path displays temporarily
|
|
- [ ] UI matches design spec (centered, clean)
|
|
- [ ] Keyboard shortcut Cmd/Ctrl+N works
|
|
- [ ] Error handling for cancelled selection
|
|
|
|
## Steps
|
|
|
|
### 1. Create Empty State Component
|
|
|
|
**src/components/empty-state.tsx:**
|
|
|
|
**Structure:**
|
|
|
|
- Centered container
|
|
- Large folder icon (from lucide-solid)
|
|
- Subheading: "Select a folder to start coding with AI"
|
|
- Primary button: "Select Folder"
|
|
- Helper text: "Keyboard shortcut: Cmd/Ctrl+N"
|
|
|
|
**Styling:**
|
|
|
|
- Use TailwindCSS utilities
|
|
- Center vertically and horizontally
|
|
- Max width: 500px
|
|
- Padding: 32px
|
|
- Icon size: 64px
|
|
- Text sizes: Heading 24px, body 16px, helper 14px
|
|
- Colors: Follow design spec (light/dark mode)
|
|
|
|
**Props:**
|
|
|
|
- `onSelectFolder: () => void` - Callback when button clicked
|
|
|
|
### 2. Create UI Store
|
|
|
|
**src/stores/ui.ts:**
|
|
|
|
**State:**
|
|
|
|
```typescript
|
|
interface UIStore {
|
|
hasInstances: boolean
|
|
selectedFolder: string | null
|
|
isSelectingFolder: boolean
|
|
}
|
|
```
|
|
|
|
**Signals:**
|
|
|
|
- `hasInstances` - Reactive boolean
|
|
- `selectedFolder` - Reactive string or null
|
|
- `isSelectingFolder` - Reactive boolean (loading state)
|
|
|
|
**Actions:**
|
|
|
|
- `setHasInstances(value: boolean)`
|
|
- `setSelectedFolder(path: string | null)`
|
|
- `setIsSelectingFolder(value: boolean)`
|
|
|
|
### 3. Implement IPC for Folder Selection
|
|
|
|
**electron/main/main.ts additions:**
|
|
|
|
**IPC Handler:**
|
|
|
|
- Register handler for 'dialog:selectFolder'
|
|
- Use `dialog.showOpenDialog()` with:
|
|
- `properties: ['openDirectory']`
|
|
- Title: "Select Project Folder"
|
|
- Button label: "Select"
|
|
- Return selected folder path or null if cancelled
|
|
- Handle errors gracefully
|
|
|
|
**electron/preload/index.ts additions:**
|
|
|
|
**Expose method:**
|
|
|
|
```typescript
|
|
electronAPI: {
|
|
selectFolder: () => Promise<string | null>
|
|
}
|
|
```
|
|
|
|
**Type definitions:**
|
|
|
|
```typescript
|
|
interface ElectronAPI {
|
|
selectFolder: () => Promise<string | null>
|
|
}
|
|
|
|
declare global {
|
|
interface Window {
|
|
electronAPI: ElectronAPI
|
|
}
|
|
}
|
|
```
|
|
|
|
### 4. Update App Component
|
|
|
|
**src/App.tsx:**
|
|
|
|
**Logic:**
|
|
|
|
- Import UI store
|
|
- Import EmptyState component
|
|
- Check if `hasInstances` is false
|
|
- If false, render EmptyState
|
|
- If true, render placeholder for instance UI (future)
|
|
|
|
**Folder selection handler:**
|
|
|
|
```typescript
|
|
async function handleSelectFolder() {
|
|
setIsSelectingFolder(true)
|
|
try {
|
|
const folder = await window.electronAPI.selectFolder()
|
|
if (folder) {
|
|
setSelectedFolder(folder)
|
|
// TODO: Will trigger instance creation in Task 003
|
|
console.log("Selected folder:", folder)
|
|
}
|
|
} catch (error) {
|
|
console.error("Folder selection failed:", error)
|
|
// TODO: Show error toast (Task 010)
|
|
} finally {
|
|
setIsSelectingFolder(false)
|
|
}
|
|
}
|
|
```
|
|
|
|
### 5. Add Keyboard Shortcut
|
|
|
|
**electron/main/menu.ts (new file):**
|
|
|
|
**Create application menu:**
|
|
|
|
- File menu:
|
|
- New Instance (Cmd/Ctrl+N)
|
|
- Click: Send 'menu:newInstance' to renderer
|
|
- Separator
|
|
- Quit (Cmd/Ctrl+Q)
|
|
|
|
**Platform-specific menu:**
|
|
|
|
- macOS: Include app menu with About, Hide, etc.
|
|
- Windows/Linux: Standard File menu
|
|
|
|
**Register menu in main.ts:**
|
|
|
|
- Import Menu, buildFromTemplate
|
|
- Create menu structure
|
|
- Set as application menu
|
|
|
|
**electron/preload/index.ts additions:**
|
|
|
|
```typescript
|
|
electronAPI: {
|
|
onNewInstance: (callback: () => void) => void
|
|
}
|
|
```
|
|
|
|
**src/App.tsx additions:**
|
|
|
|
- Listen for 'newInstance' event
|
|
- Trigger handleSelectFolder when received
|
|
|
|
### 6. Add Loading State
|
|
|
|
**Button states:**
|
|
|
|
- Default: "Select Folder"
|
|
- Loading: "Selecting..." with spinner icon
|
|
- Disabled when isSelectingFolder is true
|
|
|
|
**Spinner component:**
|
|
|
|
- Use lucide-solid Loader2 icon
|
|
- Add spin animation class
|
|
- Size: 16px
|
|
|
|
### 7. Add Validation
|
|
|
|
**Folder validation (in handler):**
|
|
|
|
- Check if folder exists
|
|
- Check if readable
|
|
- Check if it's actually a directory
|
|
- Show appropriate error if invalid
|
|
|
|
**Error messages:**
|
|
|
|
- "Folder does not exist"
|
|
- "Cannot access folder (permission denied)"
|
|
- "Please select a directory, not a file"
|
|
|
|
### 8. Style Refinements
|
|
|
|
**Responsive behavior:**
|
|
|
|
- Works at minimum window size (800x600)
|
|
- Maintains centering
|
|
- Text remains readable
|
|
|
|
**Accessibility:**
|
|
|
|
- Button has proper ARIA labels
|
|
- Keyboard focus visible
|
|
- Screen reader friendly text
|
|
|
|
**Theme support:**
|
|
|
|
- Test in light mode
|
|
- Test in dark mode (use prefers-color-scheme)
|
|
- Icons and text have proper contrast
|
|
|
|
### 9. Add Helpful Context
|
|
|
|
**Additional helper text:**
|
|
|
|
- "Examples: ~/projects/my-app"
|
|
- "You can have multiple instances of the same folder"
|
|
|
|
**Icon improvements:**
|
|
|
|
- Use animated folder icon (optional)
|
|
- Add subtle entrance animation (fade in)
|
|
|
|
## Testing Checklist
|
|
|
|
**Manual Tests:**
|
|
|
|
1. Launch app → Empty state appears
|
|
2. Click "Select Folder" → Dialog opens
|
|
3. Select folder → Path logged to console
|
|
4. Cancel dialog → No error, back to empty state
|
|
5. Press Cmd/Ctrl+N → Dialog opens
|
|
6. Select non-directory → Error shown
|
|
7. Select restricted folder → Permission error shown
|
|
8. Resize window → Layout stays centered
|
|
|
|
**Edge Cases:**
|
|
|
|
- Very long folder paths (ellipsis)
|
|
- Special characters in folder name
|
|
- Folder on network drive
|
|
- Folder that gets deleted while selected
|
|
|
|
## Dependencies
|
|
|
|
- **Blocks:** Task 003 (needs folder path to create instance)
|
|
- **Blocked by:** Task 001 (needs project setup)
|
|
|
|
## Estimated Time
|
|
|
|
2-3 hours
|
|
|
|
## Notes
|
|
|
|
- Keep UI simple and clean
|
|
- Focus on UX - clear messaging
|
|
- Don't implement instance creation yet (that's Task 003)
|
|
- Log selected folder to console for verification
|
|
- Prepare for state management patterns used in later tasks
|