agent-ecosystem/.planning/phases/04-workspace-ui/04-02-PLAN.md
matt fa62433219 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 <noreply@anthropic.com>
2026-02-12 01:59:55 +00:00

11 KiB

phase plan type wave depends_on files_modified autonomous must_haves
04-workspace-ui 02 execute 2
04-01
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
true
truths artifacts key_links
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
path provides min_lines
src/renderer/components/settings/sections/WorkspaceSection.tsx CRUD UI for SSH connection profiles following NotificationTriggerSettings pattern 100
path provides
src/renderer/components/settings/sections/index.ts Barrel export including WorkspaceSection
path provides
src/renderer/components/settings/SettingsTabs.tsx New 'workspace' tab option in settings tabs
path provides
src/renderer/components/settings/SettingsView.tsx WorkspaceSection rendered when workspace tab active
from to via pattern
src/renderer/components/settings/sections/WorkspaceSection.tsx window.electronAPI.config config.get() reads profiles, config.update('ssh', ...) writes profiles config.get|config.update.*ssh
from to via pattern
src/renderer/components/settings/sections/WorkspaceSection.tsx window.electronAPI.context.list Refreshes available contexts after profile save/delete context.list|fetchAvailableContexts
from to via pattern
src/renderer/components/settings/SettingsView.tsx src/renderer/components/settings/sections/WorkspaceSection.tsx Conditionally renders WorkspaceSection when activeSection === 'workspace' <WorkspaceSection
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.

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

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.

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()

<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>
After completion, create `.planning/phases/04-workspace-ui/04-02-SUMMARY.md`