Files
NomadArch/tasks/done/002-empty-state-ui.md
Gemini AI 157449a9ad restore: recover deleted documentation, CI/CD, and infrastructure files
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)
2025-12-23 13:03:48 +04:00

5.9 KiB

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:

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:

electronAPI: {
  selectFolder: () => Promise<string | null>
}

Type definitions:

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:

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:

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