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 <noreply@anthropic.com>
11 KiB
| phase | plan | type | wave | depends_on | files_modified | autonomous | must_haves | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 04-workspace-ui | 02 | execute | 2 |
|
|
true |
|
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.
<execution_context> @/home/bskim/.claude/get-shit-done/workflows/execute-plan.md @/home/bskim/.claude/get-shit-done/templates/summary.md </execution_context>
@.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:
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 configloading: boolean- initial load stateeditingId: 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
editingIdchanges - 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 (
inputClassandinputStylepattern) - Buttons: same styling as ConnectionSection action buttons
- Cards:
rounded-md border p-4 space-y-2with surface-raised background Runpnpm 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.
Add: export { WorkspaceSection } from './WorkspaceSection';
2. Update SettingsTabs.tsx:
Add 'workspace' to the SettingsSection type:
export type SettingsSection = 'general' | 'connection' | 'workspace' | 'notifications' | 'advanced';
Add workspace tab to the tabs array, positioned after 'connection':
{ 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:
import {
AdvancedSection,
ConnectionSection,
GeneralSection,
NotificationsSection,
WorkspaceSection,
} from './sections';
Add the workspace section render block in the content area, between connection and notifications:
{activeSection === 'workspace' && <WorkspaceSection />}
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.
<success_criteria>
- "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 </success_criteria>