--- 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`