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.
In this repository15 other files it gives its agents.
- CLAUDE.mdCLAUDE.md
- add-endpointSkill
- android-buildSkill
- deployment-architectureSkill
- design-languageSkill
- docs-navigatorSkill
- dogfooding-shipitSkill
- frontend-design:frontend-designSkill
- git-architectureSkill
- prompt-architectureSkill
- server-architectureSkill
- session-containersSkill
- session-lifecycleSkill
- session-processesSkill
- testing-and-qualitySkill
The file
Files
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}/newfor 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):
| Store | File | Key State |
| Session | session-store.ts | sessionId, messages[], isLoading, activity, sessions[], authUrl, queuedMessages[] |
| Git | git-store.ts | commits[], identity, turnDiff, lastCommitPair |
| File | file-store.ts | tree[], viewingFile, viewingFileContent, docFiles[] |
| Preview | preview-store.ts | status, selectedPort, installStatus, crashInfo |
| Terminal | terminal-store.ts | entries[], mode, shellStarted |
| PR | pr-store.ts | result, status, descGenerating |
| Settings | settings-store.ts | permissionMode, systemPromptContent, githubStatus, pendingFiles[] |
| UI | ui-store.ts | rightTab, templates[], agentList, modelInfo, toast, features[] |
| Repo | repo-store.ts | repos[], 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.tsxselects 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.setSessionskeeps 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 inAppLayout, 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 historyhandleSessionResume(sessionId, navigate)— resume + navigatenewSession(navigate)— reset all + navigate homefullResetAllStores()— 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
focusis 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/onlinealways reconnect;visibilitychange→hidden /pagehide/freezerecord a transition;focusis classified against the precedingblur(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
focuslistener 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
| Hook | File | Purpose |
useAutoFix | useAutoFix.ts | Monitors preview errors, auto-sends fix requests to Claude (3 retries, 5s cooldown) |
useKeyboardShortcuts | useKeyboardShortcuts.ts | Ctrl+F (search), ? (shortcuts overlay), Esc (interrupt) |
useNotification | useNotification.ts | Tab visibility tracking, browser notifications on agent finish |
useTheme | useTheme.ts | Theme selection from THEME_DEFS (localStorage) |
useSearch | useSearch.ts | Case-insensitive message search |
useResizablePanel | useResizablePanel.ts | Drag-to-resize split panels |
usePreviewErrors | usePreviewErrors.ts | Captures errors from preview iframe |
useMediaQuery | useMediaQuery.ts | Mobile 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 groupingMobileTabBar— bottom tab navigation on mobileResizeHandle— drag handle between panels
Chat
MessageList— renders messages, tool calls, tool results, checkpoint dividersMessageInput— text input + image upload + file autocomplete + permission mode selectorStreamingIndicator— activity label during Claude turns (thinking, writing, running)QueueIndicator— shows queued message countToolResult— renders individual tool results (file diffs, bash output)DiffBlock— syntax-highlighted diff displayTodoPanel— the agent's to-do list, folded from its task calls bytask-list.ts(docs/045)AskUserQuestion— renders permission/question prompts from Claude
Right Panel
PreviewFrame— iframe for dev server preview + port selector + error displayFileTree— workspace file browser with expand/collapseGitHistory— commit timeline with diff viewerDiffPanel— file-by-file diff with accept/reject actionsTerminalPanel— build logs displayInteractiveTerminal— xterm.js shell (lazy loaded)DocsViewer— markdown file viewer
Modals & Overlays
AuthOverlay— Claude/GitHub authentication flowsDeploymentStatusRow(inPrLifecycleCard.tsx) — shows deploy status from GitHub Deployments APISettings— git identity, system prompt, agent configUsageModal— cost/token breakdownKeyboardShortcutsOverlay— shortcut referenceOnboardingWizard— first-time setup
Home
HomeScreen— session list, import repo, templatesAddRepoDialog— 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 storeLocal Storage
| Key | Purpose |
shipit-theme | Selected theme |
vibe-permission-mode | Auto/plan/normal permission mode |
vibe-sidebar-collapsed | Sidebar collapsed state |
vibe-agent-id | Preferred agent (claude/codex) |
vibe-panel-split | Right 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.
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
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.

