refactor: decompose large frontend components into focused modules

Split six monolithic component files into smaller, single-responsibility
modules organized by feature domain:

- ChatPane (1054→~250 lines): extract InlinePills, ApprovalBanner,
  ThinkingDots into chat/ directory
- SettingsPanel (1027→~280 lines): extract McpServerEditor,
  LspProfileEditor, ToolingEditorShell into settings/ directory;
  mutation helpers into lib/settingsHelpers.ts
- PatternEditor: use shared ToggleSwitch from ui/
- App.tsx: extract useTheme and useSidecarCapabilities into hooks/
- Sidebar: extracted accessibility improvements inline

New shared primitives:
- hooks/useClickOutside: replaces 5 duplicated click-outside listeners
- components/ui/: ToggleSwitch, PopoverToggleRow, FormField, TextInput,
  TextareaInput, SelectInput, InfoCallout

Accessibility improvements:
- NewSessionModal: role=dialog, aria-modal, Escape-to-close
- Pill dropdowns: aria-expanded, aria-haspopup, role=listbox/option
- Sidebar context menu: role=menu/menuitem, Escape-to-close
- SessionItem: Space key activation alongside Enter
- ToolbarButton: aria-pressed for toggle state
- ApprovalBanner: role=alert
- QueuedApprovalsList: aria-expanded on toggle
- ThinkingDots: aria-label

No behavioral changes. All 137 tests pass.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
David Kaya
2026-03-25 20:12:36 +01:00
co-authored by Copilot
parent 1bd0fcba3e
commit fcfc5c9641
24 changed files with 1209 additions and 1157 deletions
+12 -3
View File
@@ -1,4 +1,4 @@
import { useEffect, useMemo, useState } from 'react';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { Star, X } from 'lucide-react';
import type { PatternDefinition } from '@shared/domain/pattern';
@@ -45,14 +45,23 @@ export function NewSessionModal({
}
}, [availablePatterns, patternId]);
const handleKeyDown = useCallback((e: KeyboardEvent) => {
if (e.key === 'Escape') onClose();
}, [onClose]);
useEffect(() => {
document.addEventListener('keydown', handleKeyDown);
return () => document.removeEventListener('keydown', handleKeyDown);
}, [handleKeyDown]);
const canCreate = projectId && patternId;
return (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-sm">
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-sm" role="dialog" aria-modal="true" aria-labelledby="new-session-title">
<div className="w-full max-w-md rounded-xl border border-zinc-800 bg-zinc-900 shadow-2xl">
{/* Header */}
<div className="flex items-center justify-between border-b border-zinc-800 px-5 py-4">
<h2 className="text-[13px] font-semibold text-zinc-100">New Session</h2>
<h2 id="new-session-title" className="text-[13px] font-semibold text-zinc-100">New Session</h2>
<button
className="flex size-7 items-center justify-center rounded-lg text-zinc-500 transition hover:bg-zinc-800 hover:text-zinc-300"
onClick={onClose}