This document outlines the implementation plan for Hive, an Electron-based desktop application for managing multiple git projects and their worktrees with integrated OpenCode AI sessions.
The implementation is divided into 11 focused sessions, each with:
- Clear objectives
- Definition of done
- Testing criteria for verification
- Vitest - Fast, Vite-native unit testing (shared config with Electron-Vite)
- React Testing Library - Component testing with user-centric approach
- Playwright - E2E testing with official Electron support
- @testing-library/user-event - Realistic user interaction simulation
- In-memory SQLite - Database test isolation
test/
├── setup.ts # Global test setup
├── utils/
│ ├── electron-test-utils.ts # Electron testing helpers
│ └── db-test-utils.ts # Database testing helpers
├── session-1/
│ └── scaffolding.test.ts
├── session-2/
│ └── layout.test.ts
├── session-3/
│ └── database.test.ts
├── session-4/
│ └── projects.test.ts
├── session-5/
│ └── worktrees.test.ts
├── session-6/
│ └── theming.test.ts
├── session-7/
│ └── session-tabs.test.ts
├── session-8/
│ └── session-view.test.ts
├── session-9/
│ └── session-history.test.ts
├── session-10/
│ └── polish.test.ts
└── session-11/
└── opencode-integration.test.ts
- Initialize Electron + Vite + React + TypeScript project
- Set up Tailwind CSS and shadcn/ui
- Configure project structure as per PRD
- Set up development tooling (ESLint, Prettier, pnpm)
- Initialize Electron-Vite project with React + TypeScript template
- Configure pnpm as package manager
- Set up Tailwind CSS with dark mode support
- Initialize shadcn/ui with required components
- Create directory structure as per PRD architecture
- Configure TypeScript paths and aliases
- Set up ESLint and Prettier configurations
- Create basic window with proper Electron security settings
- Set up preload script with context bridge
- Set up Vitest, React Testing Library, and Playwright
- Project builds successfully with
pnpm build - Development server runs with
pnpm dev - Electron window opens with React app rendered
- Tailwind CSS classes work correctly
- shadcn/ui Button component renders properly
- Dark/light mode toggles work via Tailwind classes
- Context isolation is enabled, nodeIntegration is disabled
- Preload script exposes typed API to renderer
- Test suite runs with
pnpm test
// test/session-1/scaffolding.test.ts
describe('Session 1: Project Scaffolding', () => {
test('Electron app launches without errors', async () => {
// App should launch and create main window
})
test('React app renders in renderer process', async () => {
// Verify React root element exists
})
test('Tailwind CSS classes are applied', async () => {
// Verify a test element has correct Tailwind styles
})
test('shadcn/ui components render correctly', async () => {
// Verify Button component renders with correct styling
})
test('Preload API is available in renderer', async () => {
// Verify window.db exists and has query methods
})
test('Context isolation is enabled', async () => {
// Verify window.require is undefined
// Verify window.process is undefined
})
})- Implement the three-panel layout (Left Sidebar, Main Pane, Right Sidebar)
- Create resizable panels with proper constraints
- Build collapsible right sidebar
- Add window title bar and basic chrome
- Create
LeftSidebar.tsxcomponent (240px default, 200-400px resizable) - Create
MainPane.tsxcomponent (fills remaining space) - Create
RightSidebar.tsxcomponent (280px, collapsible placeholder) - Implement resize handles between panels
- Add resize constraints (min/max widths)
- Persist panel sizes to localStorage
- Create basic header/toolbar area
- Add theme toggle button in header
- Three-panel layout renders correctly
- Left sidebar is resizable between 200-400px
- Right sidebar is collapsible via toggle button
- Panel sizes persist across app restarts
- Main pane fills remaining horizontal space
- Layout is responsive to window resize
- Theme toggle switches between dark/light modes
- Right sidebar shows "File Tree — Coming Soon" placeholder
// test/session-2/layout.test.ts
describe('Session 2: Application Layout', () => {
test('Three-panel layout renders', async () => {
// Verify all three panels exist in DOM
})
test('Left sidebar has correct default width', async () => {
// Verify left sidebar is 240px wide
})
test('Left sidebar respects min/max constraints', async () => {
// Attempt to resize below 200px - should be prevented
// Attempt to resize above 400px - should be prevented
})
test('Right sidebar collapses and expands', async () => {
// Click collapse button, verify width is 0 or hidden
// Click expand button, verify width is restored
})
test('Panel sizes persist after restart', async () => {
// Resize panel, reload app, verify size is preserved
})
test('Theme toggle switches modes', async () => {
// Click toggle, verify body has 'dark' class
// Click again, verify body does not have 'dark' class
})
test('Main pane fills remaining space', async () => {
// Calculate expected width based on sidebar widths
// Verify main pane width matches expected
})
})- Set up SQLite database with better-sqlite3
- Implement schema as defined in PRD
- Create migration system
- Build database service with CRUD operations
- Install and configure better-sqlite3
- Create database initialization in main process
- Implement schema creation (projects, worktrees, sessions, session_messages, settings)
- Create migration system with version tracking
- Build DatabaseService class with typed methods
- Implement IPC handlers for database operations
- Create database connection pooling/management
- Add database location at
~/.hive/hive.db
- Database file is created at
~/.hive/hive.db - All tables are created with correct schema
- Foreign key constraints work correctly
- Indexes are created for performance
- Migration system tracks and runs migrations
- DatabaseService provides typed CRUD operations
- IPC handlers expose database to renderer
- Database operations complete in < 50ms
// test/session-3/database.test.ts
describe('Session 3: Database', () => {
test('Database file is created in correct location', async () => {
// Verify ~/.hive/hive.db exists after app init
})
test('All tables are created', async () => {
// Query sqlite_master for projects, worktrees, sessions,
// session_messages, settings tables
})
test('Foreign key constraints are enforced', async () => {
// Attempt to insert worktree with invalid project_id
// Should throw constraint violation
})
test('Indexes exist for performance', async () => {
// Query sqlite_master for expected indexes
})
test('Schema version is tracked', async () => {
// Verify settings table has schema_version key
})
test('CRUD operations work for projects', async () => {
// Create, Read, Update, Delete a project
})
test('CRUD operations work for worktrees', async () => {
// Create project, then CRUD worktree
})
test('Cascade delete works', async () => {
// Create project with worktrees and sessions
// Delete project, verify children are deleted
})
test('Database operations complete under 50ms', async () => {
// Time typical operations, assert < 50ms
})
})- Implement project list in left sidebar
- Build "Add Project" functionality with folder picker
- Create project validation (git repo check)
- Implement project removal and context menu
- Create
ProjectList.tsxcomponent - Create
ProjectItem.tsxcomponent with expand/collapse - Create
AddProjectButton.tsxwith folder picker dialog - Implement project validation (must be git repo)
- Create Zustand store for projects (
useProjectStore.ts) - Implement IPC handlers for project operations
- Add context menu (Remove, Open in Finder, Copy Path, Edit Name)
- Implement toast notifications for success/error states
- Update lastAccessedAt on project interaction
- Project list displays all added projects
- "Add Project" button opens native folder picker
- Non-git directories are rejected with error toast
- Duplicate projects are rejected with error toast
- Projects are persisted to SQLite database
- Context menu works with all actions
- Projects can be removed from Hive
- Project names are editable
- lastAccessedAt updates on interaction
// test/session-4/projects.test.ts
describe('Session 4: Project Management', () => {
test('Project list renders empty state', async () => {
// Verify empty state message when no projects
})
test('Add project via folder picker', async () => {
// Mock folder picker, verify project appears in list
})
test('Reject non-git directory', async () => {
// Attempt to add non-git folder
// Verify error toast appears
// Verify project is not added
})
test('Reject duplicate project', async () => {
// Add project, attempt to add same path again
// Verify error toast appears
})
test('Project persists after restart', async () => {
// Add project, restart app, verify project still listed
})
test('Remove project via context menu', async () => {
// Right-click project, select Remove
// Verify project is removed from list
// Verify project is removed from database
})
test('Open in Finder works', async () => {
// Verify shell.showItemInFolder is called with correct path
})
test('Copy path to clipboard', async () => {
// Verify clipboard.writeText is called with project path
})
test('Edit project name', async () => {
// Edit name, verify database is updated
// Verify UI reflects new name
})
test('lastAccessedAt updates on interaction', async () => {
// Click project, verify timestamp updated
})
})- Implement Git service using simple-git
- Build worktree creation with city naming algorithm
- Implement worktree list display
- Create worktree actions (Archive, Unbranch, etc.)
- Install and configure simple-git
- Create GitService class in main process
- Implement city name selection algorithm with collision handling
- Create worktree at
~/.hive-worktrees/{project}/{city} - Create
WorktreeList.tsxandWorktreeItem.tsxcomponents - Implement worktree creation flow ("+" button)
- Implement Archive action (remove worktree + delete branch)
- Implement Unbranch action (remove worktree, keep branch)
- Add Open in Terminal / Open in Editor actions
- Create worktree context menu
- Implement worktree synchronization on app startup
- Create Zustand store for worktrees (
useWorktreeStore.ts)
- Worktrees display under their parent project
- "+" button creates new worktree with city name
- City names avoid collisions with existing branches
- Worktrees are created at
~/.hive-worktrees/{project}/{city} - Archive removes worktree AND deletes branch
- Unbranch removes worktree but preserves branch
- Open in Terminal launches terminal at worktree path
- Open in Editor opens worktree in configured editor
- Worktree status syncs with actual git state on startup
// test/session-5/worktrees.test.ts
describe('Session 5: Git Worktree Operations', () => {
test('Create worktree with city name', async () => {
// Click "+" button on project
// Verify worktree appears in list with city name
// Verify git worktree exists on disk
// Verify git branch exists
})
test('City name avoids existing branch collision', async () => {
// Create branch named "tokyo" manually
// Create worktree, verify name is not "tokyo"
})
test('City name adds suffix after 10 collisions', async () => {
// Mock random to always return same cities
// Verify suffix (-v1, -v2) is added
})
test('Worktree path is correct', async () => {
// Verify path is ~/.hive-worktrees/{project-name}/{city-name}
})
test('Archive removes worktree and branch', async () => {
// Create worktree, archive it
// Verify worktree directory deleted
// Verify git branch deleted
// Verify status is "archived" in database
})
test('Unbranch removes worktree but keeps branch', async () => {
// Create worktree, unbranch it
// Verify worktree directory deleted
// Verify git branch still exists
})
test('Open in Terminal launches terminal', async () => {
// Verify correct shell command is executed
})
test('Worktree sync detects missing worktrees', async () => {
// Create worktree, manually delete directory
// Restart app, verify worktree is marked appropriately
})
test('Clicking worktree sets it as active', async () => {
// Click worktree, verify it becomes active selection
// Verify lastAccessedAt is updated
})
})- Implement complete theming system (dark/light/system)
- Create settings persistence in SQLite
- Build theme toggle UI with proper state management
- Create Zustand theme store (
useThemeStore.ts) - Implement CSS variables for theming (shadcn/ui approach)
- Add system theme detection (prefers-color-scheme)
- Create theme toggle dropdown (Dark / Light / System)
- Persist theme preference to SQLite settings table
- Apply theme class to document on app load
- Listen for system theme changes when "System" selected
- Create IPC handlers for settings operations
- Three theme options available: Dark, Light, System
- Theme persists across app restarts
- "System" option follows OS preference
- Theme changes apply immediately without flicker
- All UI components respect current theme
- Theme is readable from SQLite settings table
- System theme changes are detected in real-time
// test/session-6/theming.test.ts
describe('Session 6: Theme System', () => {
test('Default theme is dark', async () => {
// Fresh install, verify dark theme is applied
})
test('Theme toggle cycles through options', async () => {
// Click toggle, verify each option is applied
})
test('Theme persists after restart', async () => {
// Set to light, restart, verify light is applied
})
test('System theme follows OS preference', async () => {
// Set to system, mock OS preference
// Verify correct theme is applied
})
test('All shadcn components respect theme', async () => {
// Render various components
// Verify CSS variables are applied correctly
})
test('Theme setting is stored in database', async () => {
// Query settings table for theme key
// Verify value matches current theme
})
test('No flash of wrong theme on load', async () => {
// Set theme, reload, measure time to correct theme
// Should be applied before first paint
})
})- Implement session tab bar UI
- Build tab creation, switching, closing
- Implement tab drag-to-reorder
- Create session Zustand store
- Create
SessionTabs.tsxcomponent - Create Zustand store for sessions (
useSessionStore.ts) - Implement tab switching
- Implement tab close (x button, middle-click)
- Implement "+" button to create new session
- Add tab drag-to-reorder functionality
- Implement tab overflow with scroll arrows
- Create session data model in database
- Link sessions to active worktree
- Show empty state when no sessions
- Tab bar renders for active worktree
- Clicking tab switches active session
- "+" button creates new session tab
- x button closes session tab
- Middle-click closes session tab
- Tabs can be reordered via drag-and-drop
- Tab overflow shows scroll arrows
- Sessions are persisted to database
- Switching worktrees shows that worktree's sessions
// test/session-7/session-tabs.test.ts
describe('Session 7: Session Tabs', () => {
test('Tab bar renders for active worktree', async () => {
// Select worktree, verify tab bar appears
})
test('Create new session via + button', async () => {
// Click +, verify new tab appears
// Verify session in database
})
test('Click tab switches active session', async () => {
// Create multiple tabs, click each
// Verify content area updates
})
test('Close tab via x button', async () => {
// Click x, verify tab is removed
})
test('Close tab via middle-click', async () => {
// Middle-click tab, verify tab is removed
})
test('Drag tab to reorder', async () => {
// Create 3 tabs, drag first to last
// Verify order is updated in UI and database
})
test('Tab overflow shows scroll arrows', async () => {
// Create many tabs, verify arrows appear
// Click arrows, verify scroll behavior
})
test('Switching worktree shows different sessions', async () => {
// Create sessions in worktree A
// Switch to worktree B
// Verify different sessions shown
})
test('Empty state when no sessions', async () => {
// Select worktree with no sessions
// Verify empty state message
})
})- Create session view placeholder UI
- Build message list structure
- Create input area structure
- Prepare for OpenCode integration
- Create
SessionView.tsxcomponent - Design message list layout (scrollable)
- Create message input area (textarea + send button)
- Add placeholder content for demo
- Create loading/connecting state UI
- Create error state UI
- Design code block component structure
- Prepare interface types for OpenCode integration
- Session view renders when session tab is active
- Message list area is scrollable
- Input area has textarea and send button
- Placeholder messages demonstrate layout
- Loading spinner shows during "connecting"
- Error state shows retry option
- Code blocks have syntax highlighting placeholder
- Interface types match OpenCode SDK expectations
// test/session-8/session-view.test.ts
describe('Session 8: Session View', () => {
test('Session view renders for active tab', async () => {
// Select tab, verify session view appears
})
test('Message list is scrollable', async () => {
// Add many messages, verify scroll works
})
test('Input area accepts text', async () => {
// Type in textarea, verify value updates
})
test('Send button is present', async () => {
// Verify send button exists and is clickable
})
test('Loading state shows spinner', async () => {
// Set loading state, verify spinner visible
})
test('Error state shows retry button', async () => {
// Set error state, verify retry button visible
})
test('Code block structure renders', async () => {
// Add code message, verify code block styling
})
})- Implement session history panel
- Build search across sessions
- Create filter by project/worktree/date
- Handle orphaned sessions display
- Create
SessionHistory.tsxcomponent - Implement history panel UI (modal or slide-out)
- Build search input with keyword search
- Implement filter by project dropdown
- Implement filter by worktree (including archived)
- Implement date range filter
- Create session preview on hover/select
- Implement "Load into tab" action
- Style orphaned sessions with visual indicator
- Add keyboard shortcut (Cmd/Ctrl + K) for quick access
- Session history panel opens via menu/shortcut
- Search finds sessions by keyword
- Filter by project works
- Filter by worktree works (including archived)
- Date range filter works
- Session preview shows content snippet
- "Load" action opens session in new tab
- Orphaned sessions have visual indicator (muted/italic)
- Cmd/Ctrl + K opens history panel
// test/session-9/session-history.test.ts
describe('Session 9: Session History', () => {
test('History panel opens via keyboard shortcut', async () => {
// Press Cmd+K, verify panel opens
})
test('Search finds sessions by keyword', async () => {
// Create sessions with specific content
// Search for keyword, verify matches shown
})
test('Filter by project', async () => {
// Create sessions in different projects
// Filter by one project, verify only those shown
})
test('Filter by worktree includes archived', async () => {
// Create archived worktree with sessions
// Filter by archived worktree, verify sessions shown
})
test('Date range filter works', async () => {
// Create sessions on different dates
// Filter by range, verify correct sessions
})
test('Session preview shows content', async () => {
// Hover/select session, verify preview appears
})
test('Load session into new tab', async () => {
// Click load, verify tab appears with session
})
test('Orphaned sessions have visual indicator', async () => {
// Archive worktree, view its sessions
// Verify muted/italic styling
})
})- Implement comprehensive error handling
- Set up logging system
- Add toast notifications
- Final UI polish and performance optimization
- Create logging service (writes to
~/.hive/logs/) - Implement error boundary components
- Create toast notification system
- Add loading states to all async operations
- Implement graceful degradation for git failures
- Add retry options where appropriate
- Optimize database queries with proper indexing
- Add performance monitoring for key operations
- Verify < 3 second launch time
- Verify < 200MB idle memory
- Final UI polish pass
- Logs are written to
~/.hive/logs/ - Error boundaries catch and display React errors
- Toast notifications show for all user actions
- Loading spinners on all async operations
- Git operation failures show helpful messages
- Retry buttons work for recoverable errors
- App launches in < 3 seconds
- Idle memory < 200MB
- UI feedback < 100ms for interactions
- Database queries < 50ms
// test/session-10/polish.test.ts
describe('Session 10: Error Handling & Polish', () => {
test('Logs are created in correct location', async () => {
// Trigger log event, verify file in ~/.hive/logs/
})
test('Error boundary catches React errors', async () => {
// Throw error in component, verify boundary UI
})
test('Toast shows on project add success', async () => {
// Add project, verify success toast
})
test('Toast shows on project add failure', async () => {
// Add invalid project, verify error toast
})
test('Loading spinner shows during git operations', async () => {
// Trigger git operation, verify spinner visible
})
test('Git failure shows helpful error message', async () => {
// Mock git failure, verify user-friendly message
})
test('App launches in under 3 seconds', async () => {
// Time from launch to interactive
})
test('Idle memory is under 200MB', async () => {
// Measure memory after idle period
})
test('UI feedback is under 100ms', async () => {
// Time click to visual feedback
})
test('Database queries complete under 50ms', async () => {
// Time typical queries
})
})- Integrate OpenCode SDK for AI coding sessions
- Implement real-time streaming responses
- Connect session UI to actual OpenCode backend
- Handle session lifecycle management
- Install and configure OpenCode SDK
- Create OpenCodeService class in main process
- Implement process spawning for OpenCode sessions
- Create IPC streaming channel for real-time responses
- Connect SessionView to OpenCode backend
- Implement message sending functionality
- Implement streaming response display
- Handle session connection/disconnection lifecycle
- Implement session history retrieval from OpenCode
- Add code block actions (copy, apply)
- Handle OpenCode errors gracefully
- OpenCode SDK is installed and configured
- Sessions connect to OpenCode backend
- User messages are sent to OpenCode
- Streaming responses display in real-time
- Code blocks render with syntax highlighting
- Copy code action works
- Session history loads from OpenCode
- Connection errors show user-friendly messages
- Disconnection is handled gracefully
- Multiple concurrent sessions work correctly
// test/session-11/opencode-integration.test.ts
describe('Session 11: OpenCode Integration', () => {
test('OpenCode service initializes correctly', async () => {
// Verify OpenCodeService is available in main process
})
test('Session connects to OpenCode backend', async () => {
// Create session, verify connection established
})
test('User message is sent to OpenCode', async () => {
// Type message, click send
// Verify message sent to OpenCode API
})
test('Streaming response displays in real-time', async () => {
// Send message, verify response streams character by character
})
test('Code blocks render with syntax highlighting', async () => {
// Receive code response, verify syntax highlighting applied
})
test('Copy code action copies to clipboard', async () => {
// Click copy on code block
// Verify clipboard contains code
})
test('Session history loads correctly', async () => {
// Open existing session, verify history displayed
})
test('Connection error shows retry option', async () => {
// Mock connection failure
// Verify error message and retry button
})
test('Multiple sessions work concurrently', async () => {
// Open 3 sessions in different tabs
// Send messages to each, verify independent operation
})
test('Session disconnects cleanly on close', async () => {
// Close session tab
// Verify OpenCode connection is terminated
})
})Session 1 (Scaffolding)
|
v
Session 2 (Layout)
|
+------------------+
| |
v v
Session 3 (Database) Session 6 (Theming) [can run parallel]
|
v
Session 4 (Projects)
|
v
Session 5 (Worktrees)
|
v
Session 7 (Session Tabs)
|
v
Session 8 (Session View)
|
v
Session 9 (Session History)
|
v
Session 10 (Polish)
|
v
Session 11 (OpenCode Integration)
Session 11 requires OpenCode SDK documentation. Implementation details include:
- Process spawning in main process
- IPC streaming for real-time responses
- Session lifecycle management
This session can begin once OpenCode SDK documentation is available.
The right sidebar is explicitly a placeholder in v1. Implementation in Session 2 is minimal (collapsible panel with "Coming Soon" message).
Per PRD, these are NOT included in v1:
- Keyboard shortcuts customization
- Plugin system
- Cloud sync
- Team features
- Advanced git operations (commits, push, pull, merge)
- Multiple windows
- Auto-updates
- Onboarding flow
- Command palette (except Cmd+K for session history)