# Agents Overview This project is wired around three layers: domain (pure logic), application state (React hooks orchestrating the domain), and UI (components). Use this document as a quick guide for extending or reusing the codebase. ## Current Agents (Roles) - **Domain** (`domain/`): Models and pure helpers. Examples: - `models.ts` defines Host/SSHKey/Snippet/Workspace entities. - `host.ts` handles distro normalization and host sanitization. - `workspace.ts` contains workspace tree operations (split/insert/prune/sizing). - **Application State** (`application/state/`): Hooks that own state and persistence boundaries. - `useSettingsState` handles theme, accent color, terminal themes, sync config (localStorage). - `useVaultState` owns hosts/keys/snippets/custom groups and import/export, persisting to storage. - `useSessionState` owns terminal sessions, workspace lifecycle, drag/split logic. - **Infrastructure** (`infrastructure/`): External edges and configuration. - `config/` holds defaults, storage keys, terminal themes. - `persistence/localStorageAdapter.ts` abstracts localStorage read/write. - `services/` contains networked services (Gemini AI, GitHub Gist sync). - **UI** (`components/`, `App.tsx`): Presentation; depends on hooks and domain helpers only. ## How Things Talk - UI calls application hooks → hooks call domain helpers → persistence/config via infrastructure adapters. - `App.tsx` wires hooks to components; no business logic should live in components beyond view glue. - Local storage keys are centralized in `infrastructure/config/storageKeys.ts`; avoid ad-hoc `localStorage` calls elsewhere. ## Extending the System 1) **New domain logic**: Add pure functions/types under `domain/`; avoid side effects. 2) **New stateful behavior**: Wrap it in a hook under `application/state/`; keep external I/O behind adapters. 3) **New integrations**: Create adapters under `infrastructure/services/` (or `persistence/`); expose typed functions. 4) **UI changes**: Consume hook outputs/handlers; do not bypass state hooks for persistence or domain logic. ## Data & Storage - Persisted keys: see `storageKeys.ts`. Use `localStorageAdapter` for all reads/writes. - Seed data: `config/defaultData.ts`; terminal themes: `config/terminalThemes.ts`. - **Temporary files**: All temporary files (e.g., SFTP downloaded files for external editing) must be written to Netcatty's dedicated temp directory via `tempDirBridge.getTempFilePath(fileName)`. Do not write directly to `os.tmpdir()`. This ensures proper cleanup and user visibility in Settings > System. ## Testing & Safety - Favor unit tests for domain helpers (e.g., `workspace.ts`, `host.ts`) and hook-level tests for application state. - When changing storage keys or schema, provide migration or backward-compat handling. - Keep components dumb: if a prop list grows large, consider deriving a smaller view model in the hook. ## Coding Conventions - Keep logic pure in domain; side effects belong to application/infrastructure layers. - Prefer composition over deep prop drilling; lift shared state into hooks. - Avoid direct network/fetch in components; add a service/adaptor first. - Maintain ASCII-only unless required by existing file content. --- ## Aside Panel Design System VaultView subpages (Hosts, Keychain, Port Forwarding, Snippets, Known Hosts) share a unified aside panel design system via reusable components in `components/ui/aside-panel.tsx`. ### Core Components Import from `./ui/aside-panel`: ```tsx import { AsidePanel, AsidePanelHeader, AsidePanelContent, AsidePanelFooter, AsideActionMenu, AsideActionMenuItem } from "./ui/aside-panel"; ``` ### Basic Usage ```tsx Duplicate Delete } > {/* Your scrollable content here */} ``` Note: When `title` prop is provided, AsidePanel automatically renders the header. Do NOT use `AsidePanelHeader` directly inside AsidePanel - this would cause duplicate headers. ### Component Props **AsidePanel** - `open: boolean` - Controls panel visibility - `onClose: () => void` - Close button handler - `title?: string` - Header title (header only renders if title is provided) - `subtitle?: string` - Secondary text below title - `showBackButton?: boolean` - Show back arrow (for sub-panels) - `onBack?: () => void` - Back button handler - `actions?: ReactNode` - Right-side actions (buttons or AsideActionMenu) - `width?: string` - Panel width (default: "w-[380px]") - `children: ReactNode` - Panel content **AsidePanelContent** - `children: ReactNode` - Content wrapped in ScrollArea with `space-y-4` gap - `className?: string` - Additional CSS classes **AsidePanelFooter** - `children: ReactNode` - Footer content (usually buttons) - `className?: string` - Additional CSS classes **AsideActionMenu / AsideActionMenuItem** - Popover-based dropdown menu for header actions - `variant="destructive"` for delete actions (red text) ### Design Specifications - Position: `absolute right-0 top-0 bottom-0` (relative to parent container with `relative` positioning) - Width: `w-[380px]` (configurable via `width` prop) - Background: `bg-background` (solid, no backdrop-blur) - Border: `border-l border-border/60` - Z-index: `z-30` - Header: `shrink-0` to prevent scrolling, close button uses X icon - Content: `flex-1 overflow-hidden` with internal ScrollArea and `space-y-4` gap - **Important**: Parent container must have `relative` positioning for the panel to position correctly ### Panel Navigation Patterns - **Main panels**: Close with X icon, no back button - **Sub-panels (stacked)**: ArrowLeft (←) back button + X close button - Use panel stack state for nested navigation: `panelStack: PanelMode[]` - `popPanel()` returns to previous panel, `closePanel()` closes all panels ### SelectHostPanel Integration For host selection, use `SelectHostPanel` component with: - Breadcrumb navigation in content area (not header) - `multiSelect` prop for multiple host selection - `selectedHostIds` array for controlled selection - Sort dropdown and tag filter for large host lists - Uses `absolute` positioning (not `fixed`) - parent needs `relative` ### Migration from Manual Implementation Replace manual panel structure: ```tsx // OLD: Manual implementation
{/* header content */}
{/* content */}
// NEW: Using AsidePanel components (header via props) {/* content */} ``` ### Important Positioning Notes - AsidePanel uses `absolute` positioning with `top-0 bottom-0 right-0` - The panel positions relative to its nearest positioned ancestor - For correct alignment with the top of the page: - Render AsidePanel at the root level of your section (e.g., VaultView root div) - Do NOT render AsidePanel inside a scrollable content area or nested containers - The parent container should be `absolute inset-0` or have `relative` positioning