The previous 'stabilize useMutation results' fix addressed one cause
of /shell/resize being spammed (the mutation object identity), but
not the actual root cause: a closed loop between xterm's onResize
callback, ResizeObserver, and the backend.
The full loop:
1. ResizeObserver fires
2. fitAddon.fit() runs
3. fit() calls terminal.resize(cols, rows)
4. terminal.onResize(cb) fires the listener
5. cb calls resize.mutate() — POSTs to /shell/resize
6. server calls pty.Setsize
7. bash receives SIGWINCH and redraws the prompt
8. the new output writes to the terminal, which can shift a
scrollbar / padding by a few pixels
9. ResizeObserver fires again
-> repeat
Fixes applied to TerminalPanel's terminal-init useEffect:
- Drop the terminal.onResize subscription entirely. Per the xterm
+ FitAddon pattern, the right way to push size to the backend
is: from the ResizeObserver path, call fit(), then read
terminal.cols/rows synchronously and call resize.mutate().
- Dedupe by (cols, rows) — if the size hasn't changed since the
last reported size, skip the mutation.
- Dedupe the ResizeObserver callback by container
getBoundingClientRect — sub-pixel changes (e.g. a scrollbar
appearing) won't trigger a redundant fit().
- Depend on the stable sendResize function (the destructured
mutate from useMutation) rather than the whole resize object,
so unrelated render churn can't tear down and re-create the
terminal.
- Run an initial fit() at mount so the backend learns the real
size before the user starts typing.
The 100ms debounce was a band-aid that didn't actually break the
loop — it only slowed it down.
codespace-web
Frontend IDE shell for the codespace Go backend.
Scope
This package is the codespace web IDE. It connects to the Go backend:
the file explorer and editor call the file APIs, the toolbar drives the
process start/stop/restart endpoints, and the terminal streams the
running OpenCode process over the WebSocket endpoint
/api/workspaces/:id/process/ws.
Commands
| Command | Purpose |
|---|---|
pnpm install |
Install dependencies |
pnpm dev |
Start the Vite dev server (proxies /api and /healthz to http://localhost:8080) |
pnpm lint |
Run ESLint |
pnpm build |
Type-check and build for production |
Stack
- Vite + React 19 + TypeScript
- Tailwind CSS v4
- react-router v7
- Zustand for local UI state
- TanStack Query for API state
@monaco-editor/reacteditor wrapper@xterm/xterm+@xterm/addon-fitterminal wrapperreact-resizable-panelsfor layout splitslucide-reactfor icons
Non-goals
- Authentication
- i18n
- Tests (no test framework until business logic is added)