docs: update BROWSER.md and TODO.md for project-local state

Replace /tmp paths with .gstack/, remove CONDUCTOR_PORT docs, document
random port selection and per-project isolation. Add server bundling TODO.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Garry Tan
2026-03-13 19:46:00 -05:00
parent b9125fd394
commit 5337ce8635
2 changed files with 16 additions and 23 deletions
+15 -23
View File
@@ -33,7 +33,7 @@ gstack's browser is a compiled CLI binary that talks to a persistent local Chrom
│ ▼ │
│ ┌──────────┐ HTTP POST ┌──────────────┐ │
│ │ browse │ ──────────────── │ Bun HTTP │ │
│ │ CLI │ localhost:9400 │ server │ │
│ │ CLI │ localhost:rand │ server │ │
│ │ │ Bearer token │ │ │
│ │ compiled │ ◄────────────── │ Playwright │──── Chromium │
│ │ binary │ plain text │ API calls │ (headless) │
@@ -46,7 +46,7 @@ gstack's browser is a compiled CLI binary that talks to a persistent local Chrom
### Lifecycle
1. **First call**: CLI checks `/tmp/browse-server.json` for a running server. None found — it spawns `bun run browse/src/server.ts` in the background. The server launches headless Chromium via Playwright, picks a port (9400-9410), generates a bearer token, writes the state file, and starts accepting HTTP requests. This takes ~3 seconds.
1. **First call**: CLI checks `.gstack/browse.json` (in the project root) for a running server. None found — it spawns `bun run browse/src/server.ts` in the background. The server launches headless Chromium via Playwright, picks a random port (10000-60000), generates a bearer token, writes the state file, and starts accepting HTTP requests. This takes ~3 seconds.
2. **Subsequent calls**: CLI reads the state file, sends an HTTP POST with the bearer token, prints the response. ~100-200ms round trip.
@@ -94,15 +94,15 @@ No DOM mutation. No injected scripts. Just Playwright's native accessibility API
### Authentication
Each server session generates a random UUID as a bearer token. The token is written to the state file (`/tmp/browse-server.json`) with chmod 600. Every HTTP request must include `Authorization: Bearer <token>`. This prevents other processes on the machine from controlling the browser.
Each server session generates a random UUID as a bearer token. The token is written to the state file (`.gstack/browse.json`) with chmod 600. Every HTTP request must include `Authorization: Bearer <token>`. This prevents other processes on the machine from controlling the browser.
### Console, network, and dialog capture
The server hooks into Playwright's `page.on('console')`, `page.on('response')`, and `page.on('dialog')` events. All entries are kept in O(1) circular buffers (50,000 capacity each) and flushed to disk asynchronously via `Bun.write()`:
- Console: `/tmp/browse-console.log`
- Network: `/tmp/browse-network.log`
- Dialog: `/tmp/browse-dialog.log`
- Console: `.gstack/browse-console.log`
- Network: `.gstack/browse-network.log`
- Dialog: `.gstack/browse-dialog.log`
The `console`, `network`, and `dialog` commands read from the in-memory buffers, not disk.
@@ -112,30 +112,22 @@ Dialogs (alert, confirm, prompt) are auto-accepted by default to prevent browser
### Multi-workspace support
Each workspace gets its own isolated browser instance with its own Chromium process, tabs, cookies, and logs.
Each workspace gets its own isolated browser instance with its own Chromium process, tabs, cookies, and logs. State is stored in `.gstack/` inside the project root (detected via `git rev-parse --show-toplevel`).
If `CONDUCTOR_PORT` is set (e.g., by [Conductor](https://conductor.dev)), the browse port is derived deterministically:
| Workspace | State file | Port |
|-----------|------------|------|
| `/code/project-a` | `/code/project-a/.gstack/browse.json` | random (10000-60000) |
| `/code/project-b` | `/code/project-b/.gstack/browse.json` | random (10000-60000) |
```
browse_port = CONDUCTOR_PORT - 45600
```
| Workspace | CONDUCTOR_PORT | Browse port | State file |
|-----------|---------------|-------------|------------|
| Workspace A | 55040 | 9440 | `/tmp/browse-server-9440.json` |
| Workspace B | 55041 | 9441 | `/tmp/browse-server-9441.json` |
| No Conductor | — | 9400 (scan) | `/tmp/browse-server.json` |
You can also set `BROWSE_PORT` directly.
No port collisions. No shared state. Each project is fully isolated.
### Environment variables
| Variable | Default | Description |
|----------|---------|-------------|
| `BROWSE_PORT` | 0 (auto-scan 9400-9410) | Fixed port for the HTTP server |
| `CONDUCTOR_PORT` | — | If set, browse port = this - 45600 |
| `BROWSE_PORT` | 0 (random 10000-60000) | Fixed port for the HTTP server (debug override) |
| `BROWSE_IDLE_TIMEOUT` | 1800000 (30 min) | Idle shutdown timeout in ms |
| `BROWSE_STATE_FILE` | `/tmp/browse-server.json` | Path to state file |
| `BROWSE_STATE_FILE` | `.gstack/browse.json` | Path to state file (CLI passes to server) |
| `BROWSE_SERVER_SCRIPT` | auto-detected | Path to server.ts |
### Performance
@@ -206,7 +198,7 @@ Tests spin up a local HTTP server (`browse/test/test-server.ts`) serving HTML fi
| File | Role |
|------|------|
| `browse/src/cli.ts` | Entry point. Reads `/tmp/browse-server.json`, sends HTTP to the server, prints response. |
| `browse/src/cli.ts` | Entry point. Reads `.gstack/browse.json`, sends HTTP to the server, prints response. |
| `browse/src/server.ts` | Bun HTTP server. Routes commands to the right handler. Manages idle timeout. |
| `browse/src/browser-manager.ts` | Chromium lifecycle — launch, tab management, ref map, crash detection. |
| `browse/src/snapshot.ts` | Parses accessibility tree, assigns `@e`/`@c` refs, builds Locator map. Handles `--diff`, `--annotate`, `-C`. |
+1
View File
@@ -77,6 +77,7 @@
- Pass/fail with evidence
## Phase 5: State & Sessions
- [ ] Bundle server.ts into compiled binary (eliminate resolveServerScript() fallback chain entirely) (P2, M)
- [ ] v20 encryption format support (AES-256-GCM) — future Chromium versions may change from v10
- [ ] Sessions (isolated browser instances with separate cookies/storage/history)
- [ ] State persistence (save/load cookies + localStorage to JSON files)