# Build, test & contribute

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

Handbook / Contribute · Checked against the source on 2026-10-05 · https://www.hbcode.ai/docs/contributing

## 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
```

| Task | Start command or project, and what it proves |
| --- | --- |
| Website | pnpm dev:website. Documentation and marketing UI in a browser. |
| Desktop frontend | pnpm -C desktop dev. React layout and frontend behavior. Native calls require a Tauri runtime. |
| Native desktop | pnpm -C desktop tauri:dev. The actual WebView, Rust commands, sidecar, and local files. |
| Claude agent host | pnpm -C desktop/agent-host build. The TypeScript host compiles to desktop/agent-host/dist. tauri dev runs this build before the frontend starts. |
| Server | pnpm dev:server after local setup. Configured HTTP and relay service behavior. |
| iOS | mobile/ios/HBCode.xcodeproj, HBCode scheme. Native companion UI and, with a configured desktop, remote control. |
| Android | mobile/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”.

- [The vendored sidecar](https://www.hbcode.ai/docs/codex-app-server.md#sidecar): tauri:dev builds the Codex child from the vendored source first. This section explains the steps and the verify command.
- [The Claude agent host](https://www.hbcode.ai/docs/claude-agent-sdk.md): The host process, its protocol with the desktop, and the run loop.

## Where the desktop code lives

**The shared chat layer reaches an agent only through agents/dispatch, and the two adapters never import each other**

A 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.

- **Shared chat layer** (desktop/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.
- **Dispatch** (desktop/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.
- **Adapters** (agents/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 catalog** (desktop/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 host** (desktop/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.

| Path | What it owns and what it may import |
| --- | --- |
| chat | Sessions, 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/dispatch | The choice of adapter for a session: send, runs, timeline, approvals, conversations, and model selection. May import: both adapters and chat. |
| agents/codex | The 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/claude | The 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_tools | The 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_api | RPC handlers for the phones. May import: chat and agents::dispatch. The Codex profile and usage-limit handlers call agents::codex. |
| desktop/agent-host | The Claude agent host in TypeScript, with its own tests. It imports no Rust code. The protocol is in src/protocol.ts. |

- [How Codex and Claude share one chat layer](https://www.hbcode.ai/docs/agents.md#layers): What the chat layer shares and what each adapter adds.

## 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 surface | Repository commands |
| --- | --- |
| Desktop TypeScript | pnpm -C desktop typecheck, pnpm -C desktop lint, pnpm -C desktop test |
| Desktop Rust | pnpm desktop:check, then focused Rust tests for the changed service. |
| Claude agent host | pnpm -C desktop/agent-host typecheck, pnpm -C desktop/agent-host test |
| Server Rust | pnpm server:check, then focused cargo tests and configured service tests as needed. |
| Shared RPC | pnpm check:rpc-contract, plus the affected client and server tests. |
| Codex storage boundary | pnpm check:codex-storage-boundary |
| Website | pnpm -C website typecheck, pnpm -C website lint, pnpm -C website test, pnpm -C website build |
| File, function, and folder size | pnpm check:max-lines, pnpm check:max-function-lines, pnpm check:max-folder-files |
| Unused code on the desktop | pnpm check:unused runs knip, cargo machete, and clippy with the dead-code and unused warnings. It is not part of check:all. |
| Cross-boundary review | pnpm 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. |
| Everything | pnpm 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.
