Skip to article
HB CodeDocsGet the app

HandbookContribute

Build, test & contribute

This chapter shows how to build from a source checkout, make one change, and run the checks for each boundary.

Checked against the source on 5 October 2026

On this page

Start only the components that your change needs

Use a source checkout with its lockfiles and read AGENTS.md for the current repository contract. The root package manager is pnpm 10.34.5, and the root package.json pins vulnerable transitive versions through pnpm overrides and patched dependencies, which is why --frozen-lockfile fails after a dependency change until the lockfile is regenerated. Install a compatible Node runtime, stable Rust for native work, and the target platform’s native compiler tools. iOS work requires macOS and Xcode. No root command starts every component together. Run only the surfaces that your change needs.

Install JavaScript dependencies from the repository root
pnpm install --frozen-lockfile
TaskStart command or project, and what it proves
Websitepnpm dev:website. Documentation and marketing UI in a browser.
Desktop frontendpnpm -C desktop dev. React layout and frontend behavior. Native calls require a Tauri runtime.
Native desktoppnpm -C desktop tauri:dev. The actual WebView, Rust commands, sidecar, and local files.
Claude agent hostpnpm -C desktop/agent-host build. The TypeScript host compiles to desktop/agent-host/dist. tauri dev runs this build before the frontend starts.
Serverpnpm dev:server after local setup. Configured HTTP and relay service behavior.
iOSmobile/ios/HBCode.xcodeproj, HBCode scheme. Native companion UI and, with a configured desktop, remote control.
Androidmobile/android in Android Studio. The Compose companion and, with a configured desktop, remote control.

The Claude agent host is the pnpm workspace package @hbcode/claude-host in desktop/agent-host. It pins the Claude Agent SDK at 0.3.283. A debug build of the desktop starts node from PATH with desktop/agent-host/dist/src/entry.js and takes the claude binary from the SDK platform package in node_modules. Without the installed package the Claude runtime fails with “Install the Claude host dependencies before running desktop”. A release build runs pnpm -C agent-host package-runtime first and reads node, host/entry.mjs, and claude from the claude folder of the app resources. A missing host script or claude binary fails with “The Claude runtime is not packaged”.

Where the desktop code lives

The shared chat layer reaches an agent only through agents/dispatch, and the two adapters never import each other
The shared chat layer reaches an agent only through agents/dispatch, and the two adapters never import each otherA map of desktop/src-tauri/src, which is one Rust crate. The Tauri commands and the phone RPC handlers call the chat layer. The chat layer calls agents/dispatch, and dispatch calls the Codex adapter or the Claude adapter. Each adapter imports the contracts of the chat layer and the shared tool catalog in services/agent_tools. No import goes between the two adapters. Under the crate, a process boundary separates two child programs: the Codex app-server and the Claude agent host, which is a separate Node package in desktop/agent-host.
desktop/src-tauri/src · one Rust crate
commands/Tauri commands for the desktop window
remote_api/RPC from the phones
chat/Agent-neutral: sessions, outbox, runs, timeline rows, approvals, contracts
agents/dispatchpicks the adapter for a session
agents/codexCodex adapter
agents/claudeClaude adapter
imports the chat contracts
imports the chat contracts
no import in either direction
services/agent_toolsone tool catalog for both agents
process boundary
codex-app-serversidecar binary, started as a child
desktop/agent-hostNode package, started as a child
stdin and stdout
stdin and stdout
  • Shared chat layerdesktop/src-tauri/src/chat

    Eleven folders: approvals, contracts, entrypoints, operation_ledger, outbox, presentation, runs, send, session_messages, sessions, and timeline. Its only import from agents is agents::dispatch.

  • Dispatchdesktop/src-tauri/src/agents/dispatch

    Six modules: approvals, conversations, runs, selection, send, and timeline. Each function reads the agent of the session and calls the matching adapter.

  • Adaptersagents/codex · agents/claude

    Each adapter runs one agent and maps it to the chat contracts. Account and usage commands in commands and remote_api call an adapter directly, because they belong to one agent.

  • Tool catalogdesktop/src-tauri/src/services/agent_tools

    Both adapters call it to list the tools and to run a tool call. It runs the explainer video worker on the agent of the session, with the Codex app-server or the Claude agent host, and uses chat::session_messages for messages between sessions.

  • Claude agent hostdesktop/agent-host · @hbcode/claude-host

    TypeScript with the Claude Agent SDK 0.3.283. It shares no code with the crate. The Claude adapter starts it as a child and talks to it on stdin and stdout.

The module comments state the rule. chat/mod.rs calls the chat layer agent-neutral. agents/dispatch/mod.rs says that the chat layer reaches agents::codex and agents::claude only through this module. The imports follow it: every reference from chat to agents goes to agents::dispatch, and neither adapter imports the other. No script in check:quality-boundaries enforces this rule, so a reviewer must check it.

The first six paths in the table are inside desktop/src-tauri/src.

PathWhat it owns and what it may import
chatSessions, the outbox, runs, the live overlay, timeline rows and their presentation, approvals, session messages, and the chat entry points. May import: agents::dispatch. Never an adapter.
agents/dispatchThe choice of adapter for a session: send, runs, timeline, approvals, conversations, and model selection. May import: both adapters and chat.
agents/codexThe app-server child, threads, turns, goals, history, Codex auth profiles, and usage limits. May import: chat, services/agent_tools, and agents::dispatch for the session record. Never agents::claude.
agents/claudeThe host process, runs, the history projection, permissions, subagents, and Claude accounts. May import: chat, services/agent_tools, and agents::dispatch for the session record. Never agents::codex.
services/agent_toolsThe tool catalog and the tool handlers for both agents. May import: chat, agents::dispatch for the session record and the run, and both adapters for the two engines of the worker core.
remote_apiRPC handlers for the phones. May import: chat and agents::dispatch. The Codex profile and usage-limit handlers call agents::codex.
desktop/agent-hostThe Claude agent host in TypeScript, with its own tests. It imports no Rust code. The protocol is in src/protocol.ts.

Build the native companions

  1. iPhone: open mobile/ios/HBCode.xcodeproj, select the HBCode scheme, resolve the local Core, VibeUI, and VibeUIFormatting packages, and configure the Auth0 values from mobile/ios/README.md. Run on an iOS 18 simulator or a device with your signing team.
  2. Android: open mobile/android in Android Studio, let Gradle sync, and configure the Auth0 and Firebase values from mobile/android/README.md. The app targets Android 8.0 and later.
  3. For a live control test on either platform, connect to a configured desktop with the same account and region and read a known file before you try a mutation.
Discover local destinations, then use one of them for builds and tests
xcodebuild -showdestinations \
  -project mobile/ios/HBCode.xcodeproj \
  -scheme "HB Code"

The HB Code scheme runs VibeUITests through the test plan mobile/ios/HBCode.xctestplan. The plan sets diagnosticCollectionPolicy to Never, so xcodebuild collects no simulator diagnostics at the end of a test run and does not wait for them.

Simulators and emulators cover layout, decoding, and state transitions. They prove nothing about physical push delivery, store purchases, or a signed release’s provider login, so test those on real services and a real device before you call a release ready.

Follow one operation through the code

Example: trace outbox submission from contract to storage
rg -n "chatOutboxSubmit" rpc-contract.v1.json desktop/src-tauri/src/remote_api server/src mobile/ios
rg -n "submit_outbox_request" desktop/src-tauri/src/chat/outbox
rg -n "save_workspace_chat_outbox_with_operation_identity" desktop/src-tauri/src
  1. Describe the user action, expected result, and a failure case before editing.
  2. Find the transport DTO and the domain service that owns the operation. Read both, because an adapter alone does not define behavior.
  3. Change the smallest owning layer. Preserve stable operation, session, and timeline identities.
  4. If you change a shared RPC, update the manifest, desktop, server, iOS, and Android consumers together. The manifest pins what they share: page sizes 12 and 100, 40 rows for an anchored window, timeouts of 15, 60, 115, 60, and 20 seconds, the initial-timeline retry policy, and the mutations that require an idempotency key.
  5. Add a focused regression test at the failure boundary. For visual or lifecycle behavior, also inspect the running surface.
  6. Update the relevant technical chapter and its source references with the code change.

Choose checks that match the change

Changed surfaceRepository commands
Desktop TypeScriptpnpm -C desktop typecheck, pnpm -C desktop lint, pnpm -C desktop test
Desktop Rustpnpm desktop:check, then focused Rust tests for the changed service.
Claude agent hostpnpm -C desktop/agent-host typecheck, pnpm -C desktop/agent-host test
Server Rustpnpm server:check, then focused cargo tests and configured service tests as needed.
Shared RPCpnpm check:rpc-contract, plus the affected client and server tests.
Codex storage boundarypnpm check:codex-storage-boundary
Websitepnpm -C website typecheck, pnpm -C website lint, pnpm -C website test, pnpm -C website build
File, function, and folder sizepnpm check:max-lines, pnpm check:max-function-lines, pnpm check:max-folder-files
Unused code on the desktoppnpm check:unused runs knip, cargo machete, and clippy with the dead-code and unused warnings. It is not part of check:all.
Cross-boundary reviewpnpm check:quality-boundaries chains nine checks: the three size checks, the Tauri command surface, the RPC contract, the IntelliJ palette parity, the skill corpus, the Codex storage boundary, and a duplicate scan of changed code with a minimum of 18 lines.
Everythingpnpm check:all runs check:quality-boundaries, server:check, desktop:check, the desktop lint and format check, and the typecheck of every workspace package.

A typecheck proves type consistency. It does not prove delivery correctness, and a relay unit test does not prove a native login. State which checks ran, which need external services, and what you inspected in the live product.

Make the change easy to review

Keep one canonical runtime path. Remove obsolete branches instead of adding silent compatibility behavior. Keep components modular, use the dependencies already present, and retain unrelated work in a dirty checkout. The repository caps files at 550 lines and functions at 100 lines. The baseline files .max-lines-baseline and .max-function-lines-baseline record existing debt and may only shrink. AGENTS.md sets a target of five authored source or test files per folder, and check:max-folder-files fails above seven. Split a growing folder by feature or reader task. A numbered, alphabetic, part, misc, or more folder does not satisfy the rule.

A contribution should explain the trigger, the resulting behavior, the affected contract, and the validation evidence, with a screenshot for a visible change and a bounded trace for a delivery defect. Never include private session transcripts, auth files, or machine-specific paths.