# Handoff — 2026-09-17 — ACP web client (ticket 0004) ## State The full v1 works and is verified: `make run-mock` serves the panel on :8888 with the bundled demo agent (smoke-tested live over a real WebSocket: hello → prompt → thought/plan/tool calls → permission → turn_ended). All test suites green (`make test`, plus `go test -race`), quality gate PASS, bilingual docs written, `.memory/` created. Two commits on branch `feat/acp-webapp` (`main` holds only the initial repo state); a final commit with docs/memory/module-rename is proposed but not yet made — the user decides. ## In flight — READ THIS FIRST (updated at session end) **Open bug: the UI shows "offline" in the user's real sbx sandbox** (`sbx create claude . --template k33g/ori:0.0.0 --kit ./kits/ori -p 5555:8888`; page loads, so ori runs and HTTP forwards — the /ws WebSocket is what fails or drops). Already established: - ori's WS origin handling is CORRECT: curl handshakes against the image give 101 for `Origin: http://localhost:5555` (and any localhost:*), 403 for foreign origins. Node-based smoke tests send no Origin header, so they never exercised this. - sbx port publishing is raw L4 (gvisor MapPorts in `sandboxes/sandboxd/pkg/server/backend_dockernext.go`), so the upgrade should traverse. - Three discriminating diagnostics were given to the user, ANSWERS PENDING: (1) `sbx exec ori tail -20 /home/agent/.ori.log` (an origin rejection logs "websocket accept failed"); (2) curl WS handshake from INSIDE the sandbox to :8888/ws; (3) same from the host through :5555. 101/101 → look at the browser console; inside-101/forward-not-101 → the forward blocks upgrades; inside-403 → origin, ask the exact browser URL. If (1) shows nothing at all, check `/var/log/sbx-kit-startup.log`. - Possible follow-up angle if 101/101: default publish protocol is tcp4 (IPv4-only host listener) while browsers try ::1 first for localhost — page fallback works, some WS stacks may not; retry publishing with explicit `/tcp` (dual-stack) or have the browser use http://127.0.0.1:5555. ## In flight (rest) Nothing half-written. The final commit of docs + `.memory/` + the module rename is the only pending mechanical action. ## Workspace panel (added later in the session) The user requested and approved a workspace evolution, delivered complete: `internal/files` + `internal/terminal` on the backend; file tree, Monaco preview/editor, Markdown/AsciiDoc rendering, xterm.js terminal on the front; `execute` tool output in monospace. Gate PASS again, 46 Go + 66 front tests. Server redeployed live with it. New traps recorded in "Watch out for". ## Next steps 1. Let the user try the UI in a real browser (in the sandbox this needs `sbx ports --publish 8888:8888/tcp` on the host; the server already binds `0.0.0.0`). 2. Try against real Claude Code (`make run`) — needs claude auth in the environment; never exercised here. 3. Tickets 0002 (architecture diagram — largely satisfied by `docs/diagrams/packages.drawio`, consider closing) and 0003 (how to run outside the sandbox — the docs' how-to pages are most of the answer). 4. Natural evolutions, in design order: render non-text content blocks (images/resources), session modes (`current_mode_update` is currently ignored), multi-session/tabs via the `bridge.Prompter` seam, file preview / file list panes (the ticket names them as future needs). ## Open questions / blockers - No git remote is configured; where this repo is hosted (rickub.com?) is unknown here. - Whether `make run` works with the user's Claude auth has not been verified in this sandbox. ## Verified against real Claude Code (later in the session) `make run` failed at first inside this sandbox: **Claude Code refuses to launch inside another Claude Code session** (the `CLAUDECODE` env var is inherited from the sandbox's own session). Workaround that works: `env -u CLAUDECODE ./bin/ori`. With that, the full chain was verified live: real session created (UUID id), prompt over WebSocket, streamed answer, `turn_ended end_turn`. Decision pending: whether to strip `CLAUDECODE` in `spawnProcess`, in the Makefile, or only document it. Also noted: the adapter package was renamed `@zed-industries/claude-code-acp` → `@agentclientprotocol/claude-agent-acp` (old name still works, deprecated) — consider updating `config.DefaultAgentCommand`. ## Watch out for - **The adapter inherits the user's `~/.claude/settings.json` default model.** A `/model` toggle in another session saved `"claude-fable-5[1m]"` (the 1M-context variant), which the adapter's session cannot access → every prompt failed with -32603 "issue with the selected model (fable[1m])". Fix in force: launch ori with `ANTHROPIC_MODEL=claude-fable-5` (plain id) so the adapter is decoupled from the UI's saved default. - **`~/.npm` is a full 488MB dedicated volume** — npm installs of big packages fail with ENOSPC; the cache now lives at `~/.npm-big` via `NPM_CONFIG_CACHE` in `/etc/sandbox-persistent.sh`. - **monaco-editor 0.56 exports map**: import workers as `monaco-editor/editor/editor.worker.js?worker` (no `esm/vs/` prefix — the map adds it); `?worker` imports need `"vite/client"` in tsconfig types. - **biome 1.9 suppressions**: `lint/a11y/noStaticElementInteractions` does not exist in 1.9 (suppression = parse error); a suppression for a JSX attribute must sit on the line directly above that attribute, inside the opening tag. - **@asciidoctor/core v4 API**: `convert` is a NAMED export and returns a **Promise** — there is no default export and no `asciidoctor()` factory (that was v3). Never mock this module in the only test covering it: the real-render test in `AsciiDocView.test.tsx` exists precisely because a mocked test let a production breakage through. - **Material icons**: served from `/material-icons` (copied from `vscode-material-icons` into `ui/public/` by `ui/scripts/copy-icons.mjs`, hooked on npm `predev`/`prebuild`, gitignored). A fresh checkout gets them on the first `make build`/`make dev`. - **Module path is `rickub.com/bots-garden/ori`** — do not "fix" it back to github; the user explicitly corrected this mid-session. - `~/.qlty` is a dedicated 488MB volume; the qlty tool cache lives at `~/.qlty-cache` on the main disk behind a symlink. If that symlink disappears (sandbox recreation), qlty installs will fail with ENOSPC again; recreate it before running the quality skill. - The coder acp-go-sdk does NOT auto-inject the connection into an agent implementation: call `SetAgentConnection` manually (bit us once; the mock in `internal/agent/agent_test.go` and `mockagent.Run` both do it). - qlty's `return-statements` smell counts returns inside nested closures as part of the enclosing function — extract named functions, don't fight it with inline funcs. - `qlty fmt` (biome) reformats TS/CSS/JSON to tabs; run it rather than hand-formatting. - Frontend tests: jsdom lacks `scrollIntoView` (call it optionally) and store dispatches outside React need `act()`. - Vitest is pinned to major 3 in `ui/package.json` (`^3.0.0`) — fine today, but remember it's a floor choice made for install determinism, not a hard requirement.