agentleFS
nikzlabsnikzlabs/shipit/.agents/skills/client-architecture/SKILL.md

client-architecture

Skill9 starsChanged 17 days agoApache-2.0View on GitHub

ShipIt React client architecture: Zustand stores, communication hooks (useApi, useSessionWebSocket, useServerEvents, useMessageHandler), component inventory, data flow patterns. Load when working on frontend components, stores, hooks, or client state.

The file

Files
SKILL.md

Client Architecture

The client is a React 19 SPA built with Vite and Tailwind CSS v4. State management uses Zustand stores. Communication with the server uses three channels: HTTP for reads/mutations, per-session WebSocket for streaming and real-time interaction, and SSE for global broadcasts.

Entry Point

src/client/main.tsx renders the app inside a BrowserRouter with an ErrorBoundary. Two route patterns:

  • /session/:sessionId — session view
  • * — catch-all (home screen, /{slug}/new for new repo sessions)

src/client/App.tsx is the main component. It wires together all hooks, stores, and UI components. Layout is a three-panel design: sidebar (sessions), center (chat), right (preview/files/git/terminal).

State Management (Zustand)

The main stores (partial — ls src/client/stores/ for the full set):

StoreFileKey State
Sessionsession-store.tssessionId, messages[], isLoading, activity, sessions[], authUrl, queuedMessages[]
Gitgit-store.tscommits[], identity, turnDiff, lastCommitPair
Filefile-store.tstree[], viewingFile, viewingFileContent, docFiles[]
Previewpreview-store.tsstatus, selectedPort, installStatus, crashInfo
Terminalterminal-store.tsentries[], mode, shellStarted
PRpr-store.tsresult, status, descGenerating
Settingssettings-store.tspermissionMode, systemPromptContent, githubStatus, pendingFiles[]
UIui-store.tsrightTab, templates[], agentList, modelInfo, toast, features[]
Reporepo-store.tsrepos[], addRepoDialogOpen

Patterns

  • Stores are created with create<StateType>((set, get) => ({...})).
  • Each store has a reset() method for clearing session-specific state during session switching.
  • Components subscribe to individual fields via selectors: useSessionStore((s) => s.messages).
  • Stores are updated directly — no centralized dispatcher. Handlers call store.getState().setX(value).
  • App.tsx selects single rows, never a list that changes for other sessions (sessions, the PR store's per-session maps): each such change would re-render the whole app. setSessions keeps unchanged row objects (utils/session-rows.ts), so a one-row selector is stable. A reader of the whole list goes in a leaf component (AttentionNotifications, AllSessionsDialogWithRows) or in AppLayout, which gets the panels as finished elements. Guard: App.render-count.test.tsx.

Session Actions

src/client/stores/actions/session-actions.ts contains cross-store coordination:

  • resetSessionState() — clears session-specific stores (messages, git, files, preview, terminal, threads)
  • resumeSessionInternal(sessionId) — reset + fetch history
  • handleSessionResume(sessionId, navigate) — resume + navigate
  • newSession(navigate) — reset all + navigate home
  • fullResetAllStores() — nuclear reset (used after server full_reset_complete)

Communication Hooks

useWebSocket (base)

src/client/hooks/useWebSocket.ts — generic WebSocket hook.

  • Connects when URL is provided, disconnects when URL is null
  • Auto-reconnect with exponential backoff: 2s -> 4s -> 8s -> 16s -> 30s cap
  • Returns { send, lastMessage, status, reconnectAttempt, reconnect }
  • status: "connecting" | "open" | "closed"

useForegroundSignal

src/client/hooks/useForegroundSignal.ts — decides what counts as a background-to-foreground transition. Used by all three hooks that react to one: useWebSocket, useServerEvents, useConnectionSync.

  • Exists because the two long-lived connections force a fresh socket on resume (a backgrounded mobile socket reads OPEN while being dead), so the trigger cannot be "the connection looks unhealthy" — it has to be page lifecycle.
  • Window focus is not a foreground signal on its own. It also fires when focus returns from an iframe to the top-level document, which the preview iframe does on every load — that produced one forced reconnect per second on both channels, seen as preview flicker and a composer toggling disabled.
  • visibilitychange→visible / pageshow / online always reconnect; visibilitychange→hidden / pagehide / freeze record a transition; focus is classified against the preceding blur (document.hasFocus() is true when an iframe took focus, false when the window lost system focus) and only the provably-internal case is suppressed.
  • Coalesces one reactivation's event burst into a single reconnect (1s).
  • Never add a bare focus listener that treats focus as a resume — use this hook, so the surfaces cannot drift apart on the question again.

useSessionWebSocket

src/client/hooks/useSessionWebSocket.ts — wraps useWebSocket for per-session connections.

  • Connects to /ws/sessions/{sessionId}?agent={savedAgent} when sessionId is defined
  • Returns null URL (disconnects) when sessionId is undefined
  • Session switching triggers URL change -> old socket closes, new one opens

useApi

src/client/hooks/useApi.ts — HTTP client.

  • Methods: get(), post(), patch(), put(), del()
  • Returns typed responses: Promise<T>
  • Throws ApiError(status, message) on failure
  • Used in App.tsx callbacks and store async actions

useServerEvents

src/client/hooks/useServerEvents.ts — SSE connection for global broadcasts.

  • Connects to /api/events, always active
  • Handles: session_list, session_started, repo_list, repo_status, repo_warm_ready, auth_required, agent_list, active_runners, full_reset_complete
  • Updates session, repo, UI, and settings stores

useMessageHandler

src/client/hooks/useMessageHandler.ts — listens to lastMessage and hands each WsServerMessage to dispatchMessage in hooks/message-handlers/index.ts, which routes to one handler file per type and drops foreign-session messages (TRANSCRIPT_SCOPED_MESSAGES). It holds agent events back until historyLoaded.

useConnectionSync

src/client/hooks/useConnectionSync.ts — initialization on mount and WS connect.

  • On mount: GET /api/bootstrap -> populates session, repo, UI, settings stores
  • On WS open: GET /api/sessions/{id}/history -> loads messages, commits, threads
  • HTTP fallback: GET /api/sessions/{id}/preview-status (retries once after 3s if unknown)
  • Sends pending WS message if stored

Other Hooks

HookFilePurpose
useAutoFixuseAutoFix.tsMonitors preview errors, auto-sends fix requests to Claude (3 retries, 5s cooldown)
useKeyboardShortcutsuseKeyboardShortcuts.tsCtrl+F (search), ? (shortcuts overlay), Esc (interrupt)
useNotificationuseNotification.tsTab visibility tracking, browser notifications on agent finish
useThemeuseTheme.tsTheme selection from THEME_DEFS (localStorage)
useSearchuseSearch.tsCase-insensitive message search
useResizablePaneluseResizablePanel.tsDrag-to-resize split panels
usePreviewErrorsusePreviewErrors.tsCaptures errors from preview iframe
useMediaQueryuseMediaQuery.tsMobile detection

Components

Far more components live in src/client/components/ than are listed here. This is a partial orientation map, not an inventory; ls src/client/components/ for the real set.

Layout

  • SessionSidebar — session list with rename/archive, repo grouping
  • MobileTabBar — bottom tab navigation on mobile
  • ResizeHandle — drag handle between panels

Chat

  • MessageList — renders messages, tool calls, tool results, checkpoint dividers
  • MessageInput — text input + image upload + file autocomplete + permission mode selector
  • StreamingIndicator — activity label during Claude turns (thinking, writing, running)
  • QueueIndicator — shows queued message count
  • ToolResult — renders individual tool results (file diffs, bash output)
  • DiffBlock — syntax-highlighted diff display
  • TodoPanel — the agent's to-do list, folded from its task calls by task-list.ts (docs/045)
  • AskUserQuestion — renders permission/question prompts from Claude

Right Panel

  • PreviewFrame — iframe for dev server preview + port selector + error display
  • FileTree — workspace file browser with expand/collapse
  • GitHistory — commit timeline with diff viewer
  • DiffPanel — file-by-file diff with accept/reject actions
  • TerminalPanel — build logs display
  • InteractiveTerminal — xterm.js shell (lazy loaded)
  • DocsViewer — markdown file viewer

Modals & Overlays

  • AuthOverlay — Claude/GitHub authentication flows
  • DeploymentStatusRow (in PrLifecycleCard.tsx) — shows deploy status from GitHub Deployments API
  • Settings — git identity, system prompt, agent config
  • UsageModal — cost/token breakdown
  • KeyboardShortcutsOverlay — shortcut reference
  • OnboardingWizard — first-time setup

Home

  • HomeScreen — session list, import repo, templates
  • AddRepoDialog — import GitHub repo dialog

Data Flow: Sending a Message

User types in MessageInput -> handleSend() callback in App.tsx
  |
  +- If session exists:
  |    Add user message to session store
  |    Set isLoading
  |    WS send: { type: "send_message", text, sessionId, images?, files?, permissionMode? }
  |
  +- If no session (home page):
       POST /api/sessions { title }
       Store pending WS message
       Navigate to /session/{id}
       -> WS auto-connects -> useConnectionSync sends pending message
  |
  v
useMessageHandler receives WS responses:
  agent_event (assistant) -> append text to messages, update activity
  agent_event (tool_use)  -> show tool activity label
  agent_event (result)    -> mark complete, clear loading
  git_committed           -> update git store
  files_changed           -> refresh file tree
  preview_status          -> update preview store

Local Storage

KeyPurpose
shipit-themeSelected theme
vibe-permission-modeAuto/plan/normal permission mode
vibe-sidebar-collapsedSidebar collapsed state
vibe-agent-idPreferred agent (claude/codex)
vibe-panel-splitRight panel split ratio

Styling

Tailwind CSS v4 over semantic color tokens — ShipIt is multi-theme, not dark-mode-only. Concrete values live in per-theme CSS under src/client/themes/; never hardcode a palette value. See the design-language skill. Shared animations are in src/client/index.css. All components use utility classes directly — no CSS modules or styled-components.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.