From fa62433219bfe974478cb2893eb16c5d35e8ae05 Mon Sep 17 00:00:00 2001 From: matt Date: Thu, 12 Feb 2026 01:59:55 +0000 Subject: [PATCH] docs(04): create workspace UI phase plans Plan 01: ContextSwitcher dropdown, ConnectionStatusBadge, SidebarHeader integration, keyboard shortcut (Cmd+Shift+K), and availableContexts state. Plan 02: WorkspaceSection settings for SSH profile CRUD with auto-refresh. Co-Authored-By: Claude Opus 4.6 --- .planning/ROADMAP.md | 29 +- .../phases/04-workspace-ui/04-01-PLAN.md | 302 ++++++++++++++++++ .../phases/04-workspace-ui/04-02-PLAN.md | 256 +++++++++++++++ 3 files changed, 572 insertions(+), 15 deletions(-) create mode 100644 .planning/phases/04-workspace-ui/04-01-PLAN.md create mode 100644 .planning/phases/04-workspace-ui/04-02-PLAN.md diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md index 8d076c0f..39b91dfc 100644 --- a/.planning/ROADMAP.md +++ b/.planning/ROADMAP.md @@ -13,8 +13,8 @@ This roadmap transforms claude-devtools from a single-mode application (local XO Decimal phases appear between their surrounding integers in numeric order. - [x] **Phase 1: Provider Plumbing** - Fix SSH session parsing and subagent loading ✓ 2026-02-12 -- [ ] **Phase 2: Service Infrastructure** - ServiceContextRegistry and IPC context API -- [ ] **Phase 3: State Management** - Snapshot/restore system for instant switching +- [x] **Phase 2: Service Infrastructure** - ServiceContextRegistry and IPC context API ✓ 2026-02-12 +- [x] **Phase 3: State Management** - Snapshot/restore system for instant switching ✓ 2026-02-12 - [ ] **Phase 4: Workspace UI** - Context switcher and connection profiles ## Phase Details @@ -45,9 +45,9 @@ Plans: **Plans**: 3 plans Plans: -- [ ] 02-01-PLAN.md — ServiceContext bundle class, ServiceContextRegistry coordinator, and dispose() methods for FileWatcher/DataCache -- [ ] 02-02-PLAN.md — Wire registry into main/index.ts and update all IPC handlers to route via registry -- [ ] 02-03-PLAN.md — Context management IPC channels, preload bridge, and connection profiles in ConfigManager +- [x] 02-01-PLAN.md — ServiceContext bundle class, ServiceContextRegistry coordinator, and dispose() methods for FileWatcher/DataCache ✓ +- [x] 02-02-PLAN.md — Wire registry into main/index.ts and update all IPC handlers to route via registry ✓ +- [x] 02-03-PLAN.md — Context management IPC channels, preload bridge, and connection profiles in ConfigManager ✓ ### Phase 3: State Management **Goal**: Context switching preserves exact UI state per workspace with instant restoration @@ -59,11 +59,10 @@ Plans: 3. Previously visited context restores instantly without refetching data 4. Loading overlay prevents stale data flash during context switch 5. Context snapshots survive app restart (stored in IndexedDB) -**Plans**: 1-2 plans +**Plans**: 1 plan Plans: -- [ ] 03-01: Context snapshot system and contextSlice -- [ ] 03-02: IndexedDB persistence with expiration handling +- [x] 03-01-PLAN.md — Context snapshot system: contextSlice, IndexedDB storage, overlay, validation, and store wiring ✓ ### Phase 4: Workspace UI **Goal**: Users can visually manage and switch between workspaces with clear status indicators @@ -75,11 +74,11 @@ Plans: 3. Connection status indicators clearly show connected/connecting/disconnected/error states with distinct visual treatment 4. User can save SSH connection as a profile, then reconnect to it later without re-entering credentials 5. User can switch workspaces using keyboard shortcut (Cmd/Ctrl+K or similar) -**Plans**: 1-2 plans +**Plans**: 2 plans Plans: -- [ ] 04-01: ContextSwitcher component and status indicators -- [ ] 04-02: Connection profiles UI in settings +- [ ] 04-01-PLAN.md — ContextSwitcher dropdown, ConnectionStatusBadge, SidebarHeader integration, and Cmd+Shift+K shortcut +- [ ] 04-02-PLAN.md — WorkspaceSection settings for SSH profile CRUD with auto-refresh ## Progress @@ -89,10 +88,10 @@ Phases execute in numeric order: 1 → 2 → 3 → 4 | Phase | Plans Complete | Status | Completed | |-------|----------------|--------|-----------| | 1. Provider Plumbing | 1/1 | ✓ Complete | 2026-02-12 | -| 2. Service Infrastructure | 0/3 | Not started | - | -| 3. State Management | 0/1-2 | Not started | - | -| 4. Workspace UI | 0/1-2 | Not started | - | +| 2. Service Infrastructure | 3/3 | ✓ Complete | 2026-02-12 | +| 3. State Management | 1/1 | ✓ Complete | 2026-02-12 | +| 4. Workspace UI | 0/2 | Not started | - | --- *Roadmap created: 2026-02-12* -*Last updated: 2026-02-12 after Phase 2 planning complete* +*Last updated: 2026-02-12 after Phase 4 planning complete* diff --git a/.planning/phases/04-workspace-ui/04-01-PLAN.md b/.planning/phases/04-workspace-ui/04-01-PLAN.md new file mode 100644 index 00000000..946cab2c --- /dev/null +++ b/.planning/phases/04-workspace-ui/04-01-PLAN.md @@ -0,0 +1,302 @@ +--- +phase: 04-workspace-ui +plan: 01 +type: execute +wave: 1 +depends_on: [] +files_modified: + - src/renderer/components/common/ContextSwitcher.tsx + - src/renderer/components/common/ConnectionStatusBadge.tsx + - src/renderer/components/layout/SidebarHeader.tsx + - src/renderer/hooks/useKeyboardShortcuts.ts + - src/renderer/store/slices/contextSlice.ts + - src/renderer/App.tsx +autonomous: true + +must_haves: + truths: + - "User sees context switcher in SidebarHeader Row 1 listing Local + all connected SSH workspaces" + - "Each workspace item shows connection status icon (connected/connecting/disconnected/error with distinct colors)" + - "User can click a workspace in the dropdown to switch contexts (triggers switchContext from contextSlice)" + - "User can press Cmd/Ctrl+Shift+K to cycle to the next context" + - "Context switcher is disabled while isContextSwitching is true (prevents race condition)" + - "Available contexts refresh on mount and when SSH status changes" + artifacts: + - path: "src/renderer/components/common/ContextSwitcher.tsx" + provides: "Dropdown listing local + SSH contexts with status badges, switch-on-click" + min_lines: 60 + - path: "src/renderer/components/common/ConnectionStatusBadge.tsx" + provides: "Icon component rendering 4 connection states with distinct visual treatment" + min_lines: 25 + - path: "src/renderer/components/layout/SidebarHeader.tsx" + provides: "Modified Row 1 with ContextSwitcher before project name" + - path: "src/renderer/hooks/useKeyboardShortcuts.ts" + provides: "Cmd+Shift+K shortcut for context cycling" + - path: "src/renderer/store/slices/contextSlice.ts" + provides: "availableContexts state + fetchAvailableContexts action" + key_links: + - from: "src/renderer/components/common/ContextSwitcher.tsx" + to: "src/renderer/store/slices/contextSlice.ts" + via: "useStore consuming availableContexts, activeContextId, switchContext, isContextSwitching" + pattern: "useStore.*availableContexts|switchContext" + - from: "src/renderer/components/common/ConnectionStatusBadge.tsx" + to: "src/renderer/store/slices/connectionSlice.ts" + via: "useStore consuming connectionState for SSH contexts" + pattern: "connectionState" + - from: "src/renderer/components/layout/SidebarHeader.tsx" + to: "src/renderer/components/common/ContextSwitcher.tsx" + via: "ContextSwitcher rendered in Row 1" + pattern: " +Create the context switcher UI and keyboard shortcuts for workspace switching. + +Purpose: Users need a visible, always-accessible way to see which workspace (local or SSH) they are in, switch between workspaces, and understand connection status at a glance. This is the primary user-facing UI for the multi-context system built in Phases 1-3. + +Output: ContextSwitcher dropdown in SidebarHeader, ConnectionStatusBadge icons, Cmd+Shift+K shortcut, and availableContexts state in contextSlice. + + + +@/home/bskim/.claude/get-shit-done/workflows/execute-plan.md +@/home/bskim/.claude/get-shit-done/templates/summary.md + + + +@.planning/PROJECT.md +@.planning/ROADMAP.md +@.planning/STATE.md +@.planning/phases/04-workspace-ui/04-RESEARCH.md +@.planning/phases/03-state-management/03-01-SUMMARY.md +@src/renderer/components/layout/SidebarHeader.tsx +@src/renderer/hooks/useKeyboardShortcuts.ts +@src/renderer/store/slices/contextSlice.ts +@src/renderer/store/slices/connectionSlice.ts +@src/renderer/App.tsx +@src/renderer/components/settings/sections/ConnectionSection.tsx +@src/preload/index.ts +@src/shared/types/api.ts + + + + + + Task 1: Create ConnectionStatusBadge and ContextSwitcher components + + src/renderer/components/common/ConnectionStatusBadge.tsx + src/renderer/components/common/ContextSwitcher.tsx + src/renderer/store/slices/contextSlice.ts + + +**1. Add availableContexts state to contextSlice.ts:** + +Add to `ContextSlice` interface: +- `availableContexts: ContextInfo[]` (import `ContextInfo` from `@shared/types/api`) +- `fetchAvailableContexts: () => Promise` + +Add to slice creator initial state: +- `availableContexts: [{ id: 'local', type: 'local' as const }]` + +Implement `fetchAvailableContexts`: +- Call `window.electronAPI.context.list()` +- `set({ availableContexts: result })` +- Wrap in try/catch, on error fallback to `[{ id: 'local', type: 'local' }]` + +Also call `fetchAvailableContexts()` inside `initializeContextSystem()` after setting `contextSnapshotsReady: true`. + +**2. Create ConnectionStatusBadge.tsx:** + +Small icon component that renders connection state visually: + +```typescript +import { Loader2, Monitor, Wifi, WifiOff } from 'lucide-react'; +import { useStore } from '@renderer/store'; +``` + +Props: `{ contextId: string; className?: string }` + +Logic: +- If `contextId === 'local'`: render `` +- If SSH: read `connectionState` and `connectedHost` from store via `useStore` + - Determine if this specific SSH context matches the connected host: `contextId === 'ssh-' + connectedHost` + - If match: use `connectionState` value + - If no match: treat as `'disconnected'` +- Render by state: + - `'connected'`: `` + - `'connecting'`: `` + - `'disconnected'`: `` + - `'error'`: `` + +Apply `className` prop to wrapper if provided. + +**3. Create ContextSwitcher.tsx:** + +Follow the SidebarHeader dropdown pattern exactly (useRef, outside click, escape key, `inset-x-4` dropdown positioning). + +Props: none (reads everything from store). + +Store selectors (via `useShallow`): +- `activeContextId`, `isContextSwitching`, `availableContexts`, `switchContext` + +State: +- `isOpen: boolean` (dropdown visibility) +- `dropdownRef: useRef` + +Effects: +- Close on outside click (same pattern as SidebarHeader line 266-283) +- Close on Escape key (same pattern as SidebarHeader line 286-295) + +Render: +- Trigger button: + - `ConnectionStatusBadge` for active context + - Display label: `'Local'` for `contextId === 'local'`, strip `'ssh-'` prefix for SSH (e.g., `ssh-192.168.1.10` -> `192.168.1.10`) + - `ChevronDown` icon (rotate-180 when open) + - `disabled={isContextSwitching}` and `opacity-50` when switching + - `style={{ WebkitAppRegion: 'no-drag' }}` (sits in drag region) + +- Dropdown panel (when `isOpen && !isContextSwitching`): + - Fixed backdrop overlay `className="fixed inset-0 z-10"` (same as SidebarHeader) + - Panel: `absolute inset-x-4 top-full z-20 mt-1 max-h-[250px] overflow-y-auto rounded-lg py-1 shadow-xl` + - Background: `var(--color-surface-sidebar)`, border: `var(--color-border)` + - Header label: "Switch Workspace" (same style as SidebarHeader "Switch Repository") + - For each context in `availableContexts`: + - Button with hover state (follow ProjectDropdownItem pattern) + - `ConnectionStatusBadge` icon + - Label text (Local or host name) + - `Check` icon if `ctx.id === activeContextId` + - onClick: `switchContext(ctx.id)` then `setIsOpen(false)` + +Use theme CSS variables throughout (no hardcoded colors except green-400/red-400 for status). + + + Run `pnpm typecheck` - zero errors. Verify ContextSwitcher.tsx, ConnectionStatusBadge.tsx, and updated contextSlice.ts exist with correct exports. Confirm `availableContexts` is in the ContextSlice interface. + + + ContextSwitcher renders a dropdown listing Local + SSH contexts with status badges. ConnectionStatusBadge shows 4 distinct states. contextSlice tracks availableContexts fetched from context.list() IPC. All three files type-check cleanly. + + + + + Task 2: Wire ContextSwitcher into SidebarHeader and add keyboard shortcut + + src/renderer/components/layout/SidebarHeader.tsx + src/renderer/hooks/useKeyboardShortcuts.ts + src/renderer/App.tsx + + +**1. Modify SidebarHeader.tsx - Add ContextSwitcher to Row 1:** + +Import `ContextSwitcher` from `'../common/ContextSwitcher'`. + +In the Row 1 div (the one with `height: HEADER_ROW1_HEIGHT`, `paddingLeft: var(--macos-traffic-light-padding-left)`), add `ContextSwitcher` as the FIRST child before the existing project name button: + +```tsx +{/* ROW 1: Project Identity (Title Bar / Drag Region) */} +
+ {/* NEW: Context Switcher - workspace indicator */} + + + {/* Existing: Project name dropdown button */} + + + {/* Existing: Collapse sidebar button */} + +
+``` + +The ContextSwitcher already has `WebkitAppRegion: 'no-drag'` on its button, and the Row 1 div is the drag region, so it integrates naturally. + +Add a small visual separator between ContextSwitcher and project name. Use a `
` with `className="mx-1 h-4 w-px"` and `style={{ backgroundColor: 'var(--color-border)' }}` between them. This gives a subtle vertical line separating "workspace" from "project". + +Ensure the Row 1 layout uses `gap-2` or appropriate spacing so the new element fits without overflow. The existing `justify-between` may need adjustment - change to `gap-2` and keep the collapse button at the right with `ml-auto`. + +**2. Modify useKeyboardShortcuts.ts - Add Cmd+Shift+K:** + +Add to useShallow selector: +- `availableContexts: s.availableContexts` +- `activeContextId: s.activeContextId` +- `switchContext: s.switchContext` +- `isContextSwitching: s.isContextSwitching` + +IMPORTANT: The Cmd+Shift+K check MUST come BEFORE the existing Cmd+K check (line 209). Since both match `event.key === 'k'`, the shiftKey variant must be tested first: + +```typescript +// Cmd+Shift+K: Cycle to next workspace context +if (event.key === 'k' && event.shiftKey) { + event.preventDefault(); + if (!isContextSwitching && availableContexts.length > 1) { + const currentIndex = availableContexts.findIndex(c => c.id === activeContextId); + const nextIndex = (currentIndex + 1) % availableContexts.length; + void switchContext(availableContexts[nextIndex].id); + } + return; +} + +// Cmd+K: Open command palette for global search (existing) +if (event.key === 'k') { + ... +} +``` + +Add `availableContexts`, `activeContextId`, `switchContext`, `isContextSwitching` to the useEffect dependency array. + +**3. Modify App.tsx - Refresh contexts on SSH status change:** + +Add a useEffect that listens for SSH status changes and refreshes the available contexts list: + +```typescript +// Refresh available contexts when SSH connection state changes +useEffect(() => { + if (!window.electronAPI.ssh?.onStatus) return; + const cleanup = window.electronAPI.ssh.onStatus(() => { + void useStore.getState().fetchAvailableContexts(); + }); + return cleanup; +}, []); +``` + +This ensures the ContextSwitcher dropdown always reflects current SSH connections. Place this after the existing context system initialization effect. + + + Run `pnpm typecheck` - zero errors. Run `pnpm test` - all tests pass. Run `pnpm build` - production build succeeds. Verify SidebarHeader renders ContextSwitcher in Row 1. Verify useKeyboardShortcuts handles Cmd+Shift+K before Cmd+K. + + + SidebarHeader Row 1 shows [ContextSwitcher | separator | ProjectName | CollapseBtn]. Cmd+Shift+K cycles through available contexts. SSH status changes trigger context list refresh. No regressions in existing tests or type checking. + + + + + + +1. `pnpm typecheck` passes with zero errors +2. `pnpm test` passes with no regressions +3. `pnpm build` succeeds +4. ContextSwitcher.tsx exports a component that renders a dropdown +5. ConnectionStatusBadge.tsx exports a component with 4 visual states +6. SidebarHeader.tsx imports and renders ContextSwitcher in Row 1 +7. useKeyboardShortcuts.ts checks Cmd+Shift+K BEFORE Cmd+K +8. contextSlice.ts has availableContexts state and fetchAvailableContexts action +9. App.tsx has SSH status listener that refreshes available contexts + + + +- Context switcher visible in sidebar header showing active workspace with status icon +- Dropdown lists Local + all connected SSH workspaces +- Connection status icons: Monitor (local), Wifi/green (connected), Spinner (connecting), WifiOff/muted (disconnected), WifiOff/red (error) +- Clicking a workspace triggers context switch with overlay +- Cmd+Shift+K cycles through available workspaces +- Switcher disabled during active context switch +- Available contexts refresh when SSH status changes + + + +After completion, create `.planning/phases/04-workspace-ui/04-01-SUMMARY.md` + diff --git a/.planning/phases/04-workspace-ui/04-02-PLAN.md b/.planning/phases/04-workspace-ui/04-02-PLAN.md new file mode 100644 index 00000000..61502229 --- /dev/null +++ b/.planning/phases/04-workspace-ui/04-02-PLAN.md @@ -0,0 +1,256 @@ +--- +phase: 04-workspace-ui +plan: 02 +type: execute +wave: 2 +depends_on: ["04-01"] +files_modified: + - src/renderer/components/settings/sections/WorkspaceSection.tsx + - src/renderer/components/settings/sections/index.ts + - src/renderer/components/settings/SettingsTabs.tsx + - src/renderer/components/settings/SettingsView.tsx +autonomous: true + +must_haves: + truths: + - "User can see a 'Workspace' tab in settings that lists saved SSH connection profiles" + - "User can add a new SSH profile with name, host, port, username, and auth method" + - "User can edit an existing SSH profile's fields" + - "User can delete an SSH profile with confirmation" + - "Profile changes persist across app restarts (stored via ConfigManager)" + - "After saving/deleting a profile, the context switcher dropdown reflects the change" + artifacts: + - path: "src/renderer/components/settings/sections/WorkspaceSection.tsx" + provides: "CRUD UI for SSH connection profiles following NotificationTriggerSettings pattern" + min_lines: 100 + - path: "src/renderer/components/settings/sections/index.ts" + provides: "Barrel export including WorkspaceSection" + - path: "src/renderer/components/settings/SettingsTabs.tsx" + provides: "New 'workspace' tab option in settings tabs" + - path: "src/renderer/components/settings/SettingsView.tsx" + provides: "WorkspaceSection rendered when workspace tab active" + key_links: + - from: "src/renderer/components/settings/sections/WorkspaceSection.tsx" + to: "window.electronAPI.config" + via: "config.get() reads profiles, config.update('ssh', ...) writes profiles" + pattern: "config\\.get|config\\.update.*ssh" + - from: "src/renderer/components/settings/sections/WorkspaceSection.tsx" + to: "window.electronAPI.context.list" + via: "Refreshes available contexts after profile save/delete" + pattern: "context\\.list|fetchAvailableContexts" + - from: "src/renderer/components/settings/SettingsView.tsx" + to: "src/renderer/components/settings/sections/WorkspaceSection.tsx" + via: "Conditionally renders WorkspaceSection when activeSection === 'workspace'" + pattern: " +Create the SSH connection profiles settings section for managing saved workspaces. + +Purpose: Users need a persistent way to save, edit, and delete SSH connection profiles so they can reconnect to previously configured remote machines without re-entering credentials. This completes the workspace management story alongside the context switcher from Plan 01. + +Output: WorkspaceSection in settings with full CRUD for SSH profiles, integrated into settings tabs, with automatic context list refresh on changes. + + + +@/home/bskim/.claude/get-shit-done/workflows/execute-plan.md +@/home/bskim/.claude/get-shit-done/templates/summary.md + + + +@.planning/PROJECT.md +@.planning/ROADMAP.md +@.planning/STATE.md +@.planning/phases/04-workspace-ui/04-RESEARCH.md +@.planning/phases/04-workspace-ui/04-01-SUMMARY.md +@src/renderer/components/settings/SettingsView.tsx +@src/renderer/components/settings/SettingsTabs.tsx +@src/renderer/components/settings/sections/ConnectionSection.tsx +@src/renderer/components/settings/sections/index.ts +@src/shared/types/api.ts +@src/main/services/infrastructure/ConfigManager.ts + + + + + + Task 1: Create WorkspaceSection settings component with SSH profile CRUD + + src/renderer/components/settings/sections/WorkspaceSection.tsx + + +Create a settings section following the pattern established by ConnectionSection and NotificationTriggerSettings. + +**Imports:** +```typescript +import { useCallback, useEffect, useState } from 'react'; +import { useStore } from '@renderer/store'; +import { Edit2, Loader2, Plus, Save, Server, Trash2, X } from 'lucide-react'; +import { SettingsSectionHeader } from '../components/SettingsSectionHeader'; +import type { SshConnectionProfile, SshAuthMethod } from '@shared/types'; +``` + +**State management:** +- `profiles: SshConnectionProfile[]` - loaded from config +- `loading: boolean` - initial load state +- `editingId: string | null` - profile being edited (null = not editing) +- `showAddForm: boolean` - new profile form visibility +- Form state: `formName`, `formHost`, `formPort`, `formUsername`, `formAuthMethod`, `formPrivateKeyPath` + +**Profile loading:** +On mount, call `window.electronAPI.config.get()` and extract `config.ssh?.profiles ?? []`. Set into `profiles` state. + +Create `loadProfiles` callback that refetches from config and updates state. Call after every CRUD operation. + +**Add profile handler:** +- Generate ID: `crypto.randomUUID()` (available in renderer) +- Build profile object: `{ id, name: formName, host: formHost, port: parseInt(formPort) || 22, username: formUsername, authMethod: formAuthMethod, privateKeyPath: formAuthMethod === 'privateKey' ? formPrivateKeyPath : undefined }` +- Save: `await window.electronAPI.config.update('ssh', { profiles: [...profiles, newProfile] })` +- After save: `await loadProfiles()`, reset form, `setShowAddForm(false)` +- After save: `void useStore.getState().fetchAvailableContexts()` to refresh context switcher + +**Edit profile handler:** +- Populate form fields from selected profile when `editingId` changes +- Save: Replace profile in array by id, `await window.electronAPI.config.update('ssh', { profiles: updatedProfiles })` +- After save: `await loadProfiles()`, `setEditingId(null)` +- After save: `void useStore.getState().fetchAvailableContexts()` + +**Delete profile handler:** +- Filter profile out: `profiles.filter(p => p.id !== id)` +- Save: `await window.electronAPI.config.update('ssh', { profiles: filtered })` +- After delete: `await loadProfiles()` +- After delete: `void useStore.getState().fetchAvailableContexts()` + +**UI Structure:** + +``` +SettingsSectionHeader title="Workspace Profiles" +Description text: "Save SSH connection profiles for quick reconnection" + +{loading && Loader2 spinner} + +{!loading && profiles.length === 0 && empty state message} + +{profiles.map(profile => ( + ProfileCard: + - If editingId === profile.id: render inline edit form + - Else: render display card with: + - Server icon + profile.name (bold) + - profile.username@profile.host:profile.port (muted text) + - Auth method badge (muted) + - Edit button (Edit2 icon) + - Delete button (Trash2 icon, confirm with window.confirm()) +))} + +{showAddForm ? ( + Add Profile Form: + - Name input (required) + - Host input (required) + - Port input (default 22) + - Username input (required) + - Auth method select (auto/agent/privateKey/password) + - Private key path input (conditional on authMethod === 'privateKey') + - Save button + Cancel button +) : ( + Add Profile button (Plus icon) +)} +``` + +**Styling:** +- Use `var(--color-surface-raised)` for card backgrounds +- Use `var(--color-border)` for card borders +- Use `var(--color-text)`, `var(--color-text-secondary)`, `var(--color-text-muted)` for text hierarchy +- Input styling: same as ConnectionSection (`inputClass` and `inputStyle` pattern) +- Buttons: same styling as ConnectionSection action buttons +- Cards: `rounded-md border p-4 space-y-2` with surface-raised background + + + Run `pnpm typecheck` - zero errors. Verify WorkspaceSection.tsx exists and exports the component. Confirm it imports SshConnectionProfile from shared types. + + + WorkspaceSection renders a list of saved SSH profiles with add/edit/delete functionality. Profile changes are persisted via ConfigManager and trigger context list refresh. Component follows existing settings patterns. + + + + + Task 2: Wire WorkspaceSection into SettingsView and SettingsTabs + + src/renderer/components/settings/sections/index.ts + src/renderer/components/settings/SettingsTabs.tsx + src/renderer/components/settings/SettingsView.tsx + + +**1. Update sections/index.ts barrel export:** + +Add: `export { WorkspaceSection } from './WorkspaceSection';` + +**2. Update SettingsTabs.tsx:** + +Add `'workspace'` to the `SettingsSection` type: +```typescript +export type SettingsSection = 'general' | 'connection' | 'workspace' | 'notifications' | 'advanced'; +``` + +Add workspace tab to the `tabs` array, positioned after 'connection': +```typescript +{ id: 'workspace', label: 'Workspaces', icon: Server }, +``` + +Import `Server` from lucide-react (it may already be imported - check first, do not duplicate). + +The tabs array order should be: general, connection, workspace, notifications, advanced. + +**3. Update SettingsView.tsx:** + +Import `WorkspaceSection` from the sections barrel: +```typescript +import { + AdvancedSection, + ConnectionSection, + GeneralSection, + NotificationsSection, + WorkspaceSection, +} from './sections'; +``` + +Add the workspace section render block in the content area, between connection and notifications: +```tsx +{activeSection === 'workspace' && } +``` + +The component takes no props (it manages its own state internally, similar to ConnectionSection). + + + Run `pnpm typecheck` - zero errors. Run `pnpm test` - all tests pass. Run `pnpm build` - production build succeeds. Verify SettingsTabs includes 'workspace' option. Verify SettingsView renders WorkspaceSection when workspace tab is active. + + + Settings view has a "Workspaces" tab showing SSH profile management. Tab sits between Connection and Notifications in the tab bar. The full CRUD flow works: add profile -> appears in list -> edit fields -> save -> delete with confirm. Profile changes refresh context switcher dropdown automatically. + + + + + + +1. `pnpm typecheck` passes with zero errors +2. `pnpm test` passes with no regressions +3. `pnpm build` succeeds +4. WorkspaceSection.tsx exists with CRUD operations for SSH profiles +5. SettingsTabs.tsx includes 'workspace' in SettingsSection type +6. SettingsView.tsx renders WorkspaceSection when workspace tab is active +7. sections/index.ts exports WorkspaceSection +8. Profile add/edit/delete calls config.update('ssh', ...) and fetchAvailableContexts() + + + +- "Workspaces" tab visible in settings between Connection and Notifications +- Empty state shown when no profiles saved +- User can add SSH profile with name, host, port, username, auth method +- User can inline-edit existing profile fields +- User can delete profile with confirmation dialog +- Profile changes persist via ConfigManager (survive app restart) +- After any profile change, context switcher dropdown refreshes automatically + + + +After completion, create `.planning/phases/04-workspace-ui/04-02-SUMMARY.md` +