mirror of
https://github.com/grp06/openclaw-studio.git
synced 2026-08-14 00:47:51 +00:00
Add .agent directory to .gitignore, include readme image, and update README.md with enhanced project description and features. Remove outdated EXECPLAN files.
This commit is contained in:
@@ -1,375 +0,0 @@
|
||||
# True Multi-Agent Projects (Local Folders + Git Init + Per-Tile Agent IDs)
|
||||
|
||||
This ExecPlan is a living document. The sections `Progress`, `Surprises & Discoveries`, `Decision Log`, and `Outcomes & Retrospective` must be kept up to date as work proceeds.
|
||||
|
||||
Maintain this document in accordance with `.agent/PLANS.md`.
|
||||
|
||||
## Purpose / Big Picture
|
||||
|
||||
After this change, the web UI can create a “project” by name and the server will create a real folder in your home directory (for example `~/example-project`), initialize it as a Git repository, and create a basic `.gitignore` that prevents committing `.env` files.
|
||||
|
||||
Inside a project, the UI can create multiple “agent tiles”. Each tile will be a true, isolated Clawdbot agent by using a unique `agentId` in the session key (for example `agent:proj-example-project-coder-a1b2c3:main`). This gives each tile its own Clawdbot state directory subtree (`~/.clawdbot/agents/<agentId>/...`) and its own default workspace (`~/clawd-<agentId>`), so sessions, auth profiles, and transcripts do not collide between tiles.
|
||||
|
||||
You can see it working by:
|
||||
|
||||
1. Starting the Clawdbot gateway and this Next.js dev server.
|
||||
2. Creating a project named `example-project` in the UI.
|
||||
3. Verifying the folder `~/example-project` exists, contains a `.git/` directory, and `.gitignore` contains `.env`.
|
||||
4. Creating two tiles in that project (for example “Coder” and “Research” roles) and sending each a message.
|
||||
5. Verifying transcripts land in different directories:
|
||||
- `~/.clawdbot/agents/<agentId1>/sessions/*.jsonl`
|
||||
- `~/.clawdbot/agents/<agentId2>/sessions/*.jsonl`
|
||||
|
||||
## Progress
|
||||
|
||||
- [x] (2026-01-25 18:30Z) Read `.agent/PLANS.md` and audited current app structure (projects store, tiles, gateway client).
|
||||
- [x] (2026-01-25 18:55Z) Implemented project creation side effects (slugify, mkdir, `git init`, `.gitignore`) and updated the UI to accept only a project name.
|
||||
- [x] (2026-01-25 18:55Z) Implemented per-tile `agentId`/`role` storage with a v1→v2 store migration and v2 session key derivation.
|
||||
- [x] (2026-01-25 19:31Z) Added server-side tile creation endpoint with workspace bootstrap + auth profile copy and optional tile deletion endpoint.
|
||||
- [x] (2026-01-25 19:31Z) Wired UI tile creation/deletion to server endpoints and updated client API helpers.
|
||||
- [ ] (2026-01-25 19:38Z) Validate end-to-end (completed: `npm run lint`, `npm run build`; remaining: manual project/tile creation + gateway transcript verification).
|
||||
|
||||
## Surprises & Discoveries
|
||||
|
||||
- Observation: (none yet)
|
||||
Evidence: (none yet)
|
||||
|
||||
## Decision Log
|
||||
|
||||
- Decision: Do not patch Clawdbot’s `clawdbot.json` to add `agents.list` entries for every tile.
|
||||
Rationale: The gateway’s `config.patch`/`config.apply` schedules a restart, which is too disruptive for a UI that creates tiles frequently. For WebChat (`chat.send`) the gateway resolves `agentId` directly from the `sessionKey` prefix; using `agent:<agentId>:...` is sufficient to get isolated `agentDir` + session transcripts without touching config.
|
||||
Date/Author: 2026-01-25 / Codex
|
||||
|
||||
- Decision: Never delete project folders or agent directories from disk as part of “delete” actions.
|
||||
Rationale: Directory deletion is destructive and explicitly discouraged by the repo’s agent guidelines; the UI should only remove entries from its own store and leave cleanup as a separate, explicit operation.
|
||||
Date/Author: 2026-01-25 / Codex
|
||||
|
||||
- Decision: Generate client-side tile `agentId`s as `proj-<projectId>-<tileIdPrefix>` until server-side tile provisioning lands.
|
||||
Rationale: The UI still creates tiles locally during Milestone 2; this keeps per-tile session keys unique without blocking on the new tile endpoint.
|
||||
Date/Author: 2026-01-25 / Codex
|
||||
|
||||
## Outcomes & Retrospective
|
||||
|
||||
(Fill in once milestones land.)
|
||||
|
||||
## Context and Orientation
|
||||
|
||||
This repository is a local-only Next.js app that talks directly to a running Clawdbot Gateway over a WebSocket.
|
||||
|
||||
Key concepts (define these because names are overloaded):
|
||||
|
||||
- “Project”: In this app, a project is a record in a JSON store plus a real folder on disk in your home directory. After this change, creating a project also creates `~/<project-slug>` and initializes a Git repo there.
|
||||
- “Tile”: A draggable UI panel representing an interactive chat session. Today, tiles are “agents” only in the UI sense; they all use `agent:main:...` session keys.
|
||||
- “Agent” (Clawdbot agent): In Clawdbot, the agent is encoded in the session key prefix: `agent:<agentId>:<rest>`. The `<agentId>` is used to choose the agent’s state directory (`~/.clawdbot/agents/<agentId>/agent`) and session transcript directory (`~/.clawdbot/agents/<agentId>/sessions`). When we say “true multi-agent” in this plan, we mean “each tile uses a different `<agentId>` so Clawdbot’s state and transcripts are isolated per tile”.
|
||||
- “Gateway”: The Clawdbot process that exposes a WebSocket RPC interface. This UI uses it via `src/lib/gateway/GatewayClient.ts` and calls methods like `chat.send` and `sessions.patch`.
|
||||
|
||||
Current code layout (important files you must read before editing):
|
||||
|
||||
- Project store (server side):
|
||||
- `app/api/projects/store.ts` writes `~/.clawdbot/agent-canvas/projects.json`
|
||||
- `app/api/projects/route.ts` implements `GET/POST/PUT /api/projects`
|
||||
- `app/api/projects/[projectId]/route.ts` implements `DELETE /api/projects/:projectId`
|
||||
- Client-side state:
|
||||
- `src/state/store.tsx` holds `ProjectsStore` in React state and persists it back to the server via `PUT /api/projects`
|
||||
- UI + gateway wiring:
|
||||
- `app/page.tsx` sends chat via `client.call("chat.send", { sessionKey, ... })`
|
||||
- `src/lib/gateway/GatewayClient.ts` is the WebSocket RPC client
|
||||
|
||||
Important current behavior that must change:
|
||||
|
||||
- Tile session keys are currently hard-coded under the main agent:
|
||||
- `src/state/store.tsx` builds keys as `agent:main:proj-${projectId}-${tileId}`
|
||||
- This causes all tiles to share the same agentId (“main”), which is not true multi-agent isolation.
|
||||
|
||||
## Milestones
|
||||
|
||||
### Milestone 1: Projects create real folders and Git repos
|
||||
|
||||
At the end of this milestone, creating a project from the UI (by name) will create a new directory in `~/` (based on a slugified name), run `git init` inside it, and ensure `.gitignore` ignores `.env` files. The project record stored in `projects.json` will point at that directory via `repoPath`.
|
||||
|
||||
Define “slugified name” for this repo so it is deterministic and testable:
|
||||
|
||||
- Trim whitespace.
|
||||
- Lowercase.
|
||||
- Replace any run of characters that are not `a-z` or `0-9` with a single `-`.
|
||||
- Trim leading/trailing `-`.
|
||||
- If the result is empty, reject the request with HTTP 400 (“Project name produced an empty folder name”).
|
||||
|
||||
Define collision behavior explicitly so the endpoint is safe to retry and safe around existing folders:
|
||||
|
||||
- Preferred: if `~/<slug>` already exists, choose the first available suffix `~/<slug>-2`, `~/<slug>-3`, etc, and return a warning in the response that indicates the final path chosen.
|
||||
- Never delete or overwrite existing directories as part of “create project”.
|
||||
|
||||
Define the exact `.gitignore` rules this milestone must guarantee (append missing lines if the file exists; do not remove user lines):
|
||||
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
|
||||
You can prove it works by creating a project named `example-project` and running `ls -la ~/example-project` to see `.git/` and `.gitignore`, and `cat ~/example-project/.gitignore` to confirm `.env` is present.
|
||||
|
||||
### Milestone 2: Persisted tile schema supports per-tile agent IDs
|
||||
|
||||
At the end of this milestone, the persisted store schema moves from `ProjectsStore.version = 1` to `version = 2`. Each tile now has a required `agentId` and `role`, and the `sessionKey` becomes a derived value that always uses the agent id prefix form `agent:<agentId>:main`.
|
||||
|
||||
Migration rules from v1 must be written down and implemented exactly:
|
||||
|
||||
- If the stored file is missing or has `version: 1`, treat it as v1.
|
||||
- For each v1 tile:
|
||||
- Set `tile.agentId` by parsing `tile.sessionKey`:
|
||||
- If `tile.sessionKey` matches `agent:<something>:<rest>`, then `agentId = <something>`.
|
||||
- Otherwise set `agentId = "main"`.
|
||||
- Set `tile.role = "coding"` (legacy default).
|
||||
- Keep `tile.sessionKey` as-is for legacy tiles so existing sessions/transcripts still match.
|
||||
- For any new tile created under v2 rules:
|
||||
- Always set `tile.sessionKey = agent:<tile.agentId>:main`.
|
||||
|
||||
You can prove it works by loading an existing v1 store and seeing it automatically hydrate tiles with `agentId = "main"` (legacy) while new tiles get unique `agentId`s.
|
||||
|
||||
### Milestone 3: Creating a tile provisions a real Clawdbot agent workspace and auth copy
|
||||
|
||||
At the end of this milestone, creating a tile is no longer a client-only operation. The client will call a new server endpoint, and the server will both update the `projects.json` store and run filesystem side effects that make the new tile usable as an independent Clawdbot agent.
|
||||
|
||||
Add a new API route:
|
||||
|
||||
- `POST /api/projects/:projectId/tiles`
|
||||
|
||||
Request JSON:
|
||||
|
||||
{ "name": "Coder", "role": "coding" }
|
||||
|
||||
Response JSON:
|
||||
|
||||
{ "store": <ProjectsStore>, "tile": <ProjectTile>, "warnings": ["..."] }
|
||||
|
||||
Server-side behavior for `POST /api/projects/:projectId/tiles`:
|
||||
|
||||
- Load the current store via `loadStore()` and find the project by id.
|
||||
- Generate:
|
||||
- `tile.id = crypto.randomUUID()`
|
||||
- `tile.agentId` using a deterministic, safe id generator (specified in the Interfaces section) based on:
|
||||
- project folder slug (from `project.repoPath` basename)
|
||||
- role
|
||||
- a short random suffix (for uniqueness)
|
||||
- Set `tile.sessionKey = agent:<tile.agentId>:main`.
|
||||
- Add the tile to the project, save the store, and then run side effects:
|
||||
- Create the agent workspace folder at `~/clawd-<agentId>` (create directories if missing).
|
||||
- Attempt to create a symlink inside that workspace: `~/clawd-<agentId>/repo -> <project.repoPath>`.
|
||||
- If the symlink already exists, do nothing.
|
||||
- If the symlink creation fails, do not create an alternate symlink/fallback; the bootstrap files below already contain the absolute repo path.
|
||||
- Create these bootstrap files in the workspace if they do not exist:
|
||||
|
||||
BOOTSTRAP.md
|
||||
AGENTS.md
|
||||
SOUL.md
|
||||
|
||||
The minimum required content (write exactly this structure; customize role + repo path dynamically):
|
||||
|
||||
# BOOTSTRAP.md
|
||||
|
||||
Project repo: <absolute repo path>
|
||||
Role: <coding|research|marketing>
|
||||
|
||||
You are operating inside this project. Prefer working in ./repo (symlink) when it exists.
|
||||
If ./repo does not exist, operate directly in: <absolute repo path>
|
||||
|
||||
First action: run "ls" in the repo to confirm access.
|
||||
|
||||
- Copy auth profiles from the default agent into the new agent if the new agent has no auth file yet:
|
||||
- Resolve `stateDir = process.env.CLAWDBOT_STATE_DIR ?? "~/.clawdbot"`.
|
||||
- Resolve `sourceAgentId = process.env.CLAWDBOT_DEFAULT_AGENT_ID ?? "main"`.
|
||||
- Source: `<stateDir>/agents/<sourceAgentId>/agent/auth-profiles.json`
|
||||
- Destination: `<stateDir>/agents/<agentId>/agent/auth-profiles.json`
|
||||
- Never overwrite an existing destination file.
|
||||
- If the source does not exist, return a warning like: `No auth profiles found at <source>; agent may need login`.
|
||||
|
||||
You can prove it works by creating a tile and checking that `~/clawd-<agentId>/BOOTSTRAP.md` exists and `~/.clawdbot/agents/<agentId>/agent/auth-profiles.json` exists (assuming the source exists).
|
||||
|
||||
### Milestone 4: Chat uses per-tile session keys and produces isolated transcripts
|
||||
|
||||
At the end of this milestone, sending a message from a tile uses that tile’s per-tile session key (`agent:<agentId>:main`). This must result in distinct transcript files under each agent’s session directory.
|
||||
|
||||
You can prove it works by sending one message in two different tiles and then checking:
|
||||
|
||||
ls ~/.clawdbot/agents/<agentId1>/sessions
|
||||
ls ~/.clawdbot/agents/<agentId2>/sessions
|
||||
|
||||
Both should contain JSONL transcript files and the files should differ.
|
||||
|
||||
## Plan of Work
|
||||
|
||||
Implement this as a sequence of small, verifiable changes. Avoid modifying the Clawdbot repo; all changes are contained within this repository.
|
||||
|
||||
First, make project creation authoritative on the server: update `app/api/projects/route.ts` `POST` so it accepts a project name and performs filesystem creation (`~/<slug>`, `git init`, `.gitignore`). Update the UI form in `app/page.tsx` so the user provides only a project name; do not ask for a repo path in the UI anymore, since the server determines it.
|
||||
|
||||
Second, introduce a v2 store schema that supports per-tile `agentId` and `role`. Implement the migration in `app/api/projects/store.ts` inside `loadStore()`. Stop silently resetting the store on parse errors; instead, throw an error that returns HTTP 500 with a message pointing at the store path so the user can fix or delete it intentionally.
|
||||
|
||||
Third, add a dedicated server endpoint for creating tiles so you can run filesystem side effects at creation time. Implement:
|
||||
|
||||
- `app/api/projects/[projectId]/tiles/route.ts` with `POST` as defined in Milestone 3.
|
||||
- Optionally (recommended), implement `DELETE app/api/projects/[projectId]/tiles/[tileId]/route.ts` so tile deletion can also be server-authoritative (while still not deleting directories on disk).
|
||||
|
||||
This endpoint will generate the tile id, compute a safe `agentId`, create the workspace and bootstrap files, and copy auth profiles if possible. It must return the updated store (and the created tile) so the client can update state without racing the debounced `PUT /api/projects` persistence.
|
||||
|
||||
Fourth, update the client to use the per-tile `sessionKey` derived from `agentId` when calling `chat.send` and `sessions.patch`, and update the lookup helpers that match incoming `chat` events by `sessionKey`.
|
||||
|
||||
This requires edits in:
|
||||
|
||||
- `src/state/store.tsx` to remove client-only `createTile()` and replace it with a function that calls the new tile endpoint and dispatches `loadStore` with the returned store.
|
||||
- `app/page.tsx` to ensure any “new agent” action calls the updated store function, and that any message send uses the tile’s current `sessionKey`.
|
||||
|
||||
Finally, validate with a hands-on scenario and run `npm run lint` and `npm run build`.
|
||||
|
||||
## Concrete Steps
|
||||
|
||||
All commands in this section are run from the repository root unless otherwise stated.
|
||||
|
||||
1. Install deps:
|
||||
|
||||
npm install
|
||||
|
||||
2. Start the Clawdbot gateway in a separate terminal (this plan assumes it is already installed and configured on your machine):
|
||||
|
||||
clawdbot gateway run --bind loopback --port 18789 --force
|
||||
|
||||
3. Start the Next.js app:
|
||||
|
||||
npm run dev
|
||||
|
||||
4. Open the UI:
|
||||
|
||||
http://localhost:3000
|
||||
|
||||
5. Connect to the gateway (Connection panel):
|
||||
|
||||
Gateway URL: ws://127.0.0.1:18789
|
||||
Token: (enter if your gateway requires it)
|
||||
|
||||
6. Create a project named `example-project` and verify on disk:
|
||||
|
||||
ls -la ~/example-project
|
||||
cat ~/example-project/.gitignore
|
||||
|
||||
7. Create two tiles (roles: “coding” and “research”), send one message from each, and verify on disk:
|
||||
|
||||
ls ~/.clawdbot/agents
|
||||
ls ~/.clawdbot/agents/<agentId1>/sessions
|
||||
ls ~/.clawdbot/agents/<agentId2>/sessions
|
||||
|
||||
Expected: both session directories exist and each contains at least one `.jsonl` transcript file after sending messages.
|
||||
|
||||
## Validation and Acceptance
|
||||
|
||||
Acceptance is user-visible behavior:
|
||||
|
||||
- Creating a project named `example-project` results in a new folder at `~/example-project` with:
|
||||
- a `.git/` directory (Git repo initialized)
|
||||
- a `.gitignore` file containing rules that ignore `.env` files
|
||||
- Creating two tiles in a project produces two different `agentId`s and their sessions do not collide:
|
||||
- after sending messages, transcripts appear under two different directories in `~/.clawdbot/agents/<agentId>/sessions/`
|
||||
- Existing UI behavior remains functional:
|
||||
- The canvas still renders and tiles can be created/moved/resized.
|
||||
- `npm run lint` succeeds.
|
||||
- `npm run build` succeeds.
|
||||
|
||||
## Idempotence and Recovery
|
||||
|
||||
- Project creation must be safe to retry:
|
||||
- If the target directory already exists, the API should not delete or overwrite existing files; it should return a clear error or create a unique suffixed directory (document which approach you choose in the Decision Log).
|
||||
- If `git init` has already been run, do not reinitialize; treat it as success.
|
||||
- If `.gitignore` exists, only add missing ignore lines; do not delete user content.
|
||||
|
||||
- Tile creation must be safe to retry:
|
||||
- Never overwrite an existing `auth-profiles.json` in a destination agent dir.
|
||||
- Never overwrite existing workspace bootstrap files; only create missing ones.
|
||||
|
||||
If the store becomes invalid JSON, prefer failing with an actionable error rather than silently resetting it (silent reset loses data).
|
||||
|
||||
## Artifacts and Notes
|
||||
|
||||
When you land the implementation, include a short “evidence bundle” here as indented snippets:
|
||||
|
||||
- Output of: ls -la ~/example-project
|
||||
- Contents of: ~/example-project/.gitignore (relevant lines only)
|
||||
- Output of: ls ~/.clawdbot/agents/<agentId>/sessions
|
||||
- Output of: npm run lint
|
||||
- Output of: npm run build
|
||||
|
||||
## Interfaces and Dependencies
|
||||
|
||||
Avoid new dependencies unless they remove real complexity.
|
||||
|
||||
Implement with:
|
||||
|
||||
- Next.js App Router route handlers (`app/api/.../route.ts`) using `export const runtime = "nodejs";`
|
||||
- Node built-ins:
|
||||
- `fs` / `fs/promises` for filesystem operations
|
||||
- `path` and `os` for path construction
|
||||
- `child_process` (`spawnSync` or `execFile`) for running `git init`
|
||||
|
||||
New/updated types:
|
||||
|
||||
- In `src/lib/projects/types.ts`, update:
|
||||
|
||||
export type ProjectTile = {
|
||||
id: string;
|
||||
name: string;
|
||||
agentId: string;
|
||||
role: "coding" | "research" | "marketing";
|
||||
sessionKey: string;
|
||||
model?: string | null;
|
||||
thinkingLevel?: string | null;
|
||||
position: { x: number; y: number };
|
||||
size: { width: number; height: number };
|
||||
};
|
||||
|
||||
- In `src/lib/projects/types.ts`, update:
|
||||
|
||||
export type ProjectsStore = {
|
||||
version: 2;
|
||||
activeProjectId: string | null;
|
||||
projects: Project[];
|
||||
};
|
||||
|
||||
Create helper modules (names are suggestions; keep them small and focused):
|
||||
|
||||
- `src/lib/ids/slugify.ts` for turning project names into safe folder names.
|
||||
- `src/lib/ids/agentId.ts` for generating safe, <=64-char agent IDs.
|
||||
- `src/lib/fs/git.ts` for `git init` and `.gitignore` management.
|
||||
|
||||
Define these helpers precisely so two different implementers produce the same behavior:
|
||||
|
||||
- `slugifyProjectName(name: string): string`:
|
||||
- Trim whitespace.
|
||||
- Lowercase.
|
||||
- Replace any run of non-`[a-z0-9]` characters with `-`.
|
||||
- Trim leading/trailing `-`.
|
||||
- Return the result; if empty, throw an error that the caller converts into HTTP 400.
|
||||
|
||||
- `generateAgentId(params: { projectSlug: string; role: "coding" | "research" | "marketing"; seed: string }): string`:
|
||||
- Compute `base = "proj-" + projectSlug + "-" + role + "-" + seed`.
|
||||
- Normalize:
|
||||
- Lowercase.
|
||||
- Replace any run of characters not in `[a-z0-9_-]` with `-`.
|
||||
- Trim leading/trailing `-`.
|
||||
- If empty, fall back to `"proj-unknown-" + role + "-" + seed` (this is the only acceptable fallback; it prevents crashes if projectSlug is weird).
|
||||
- Enforce length:
|
||||
- If longer than 64 chars, truncate from the left side by trimming `projectSlug` first, keeping the suffix `-" + role + "-" + seed` intact, and ensuring the final string is <= 64.
|
||||
- The `seed` should be 6 chars derived from the tile UUID (for example `tileId.replaceAll("-", "").slice(0, 6)`), so collisions are extremely unlikely without needing a registry.
|
||||
|
||||
For the Git helper, explicitly specify required operations:
|
||||
|
||||
- `ensureGitRepo(dir: string): { warnings: string[] }`:
|
||||
- Create `dir` recursively if missing.
|
||||
- If `.git/` does not exist, run `git init` in that directory.
|
||||
- Ensure `.gitignore` contains these exact lines (append missing):
|
||||
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
|
||||
Plan change notes:
|
||||
|
||||
- (2026-01-25 18:30Z) Initial ExecPlan drafted based on current code audit.
|
||||
- (2026-01-25 18:55Z) Updated progress and decision log after implementing Milestones 1–2 changes in code.
|
||||
- (2026-01-25 19:31Z) Implemented Milestones 3–4 code paths and marked progress accordingly.
|
||||
@@ -1,199 +0,0 @@
|
||||
# Refactor Clawdbot Agent UI to Create-Next-App Best Practices
|
||||
|
||||
This ExecPlan is a living document. The sections `Progress`, `Surprises & Discoveries`, `Decision Log`, and `Outcomes & Retrospective` must be kept up to date as work proceeds.
|
||||
|
||||
Maintain this document in accordance with `.agent/PLANS.md`.
|
||||
|
||||
## Purpose / Big Picture
|
||||
|
||||
After this change, the Clawdbot Agent UI will follow the create-next-app best practices end to end: a `src/`-rooted App Router layout, a feature-first structure, a shared component system using shadcn/ui, validated environment variables, consistent logging helpers, and a proper quality gate stack (lint, typecheck, unit tests, e2e tests, and OpenTelemetry instrumentation). A new contributor should be able to locate UI, domain logic, and shared primitives quickly, and the project should ship with predictable tooling and test coverage that proves the UI still loads and core utilities behave as expected.
|
||||
|
||||
You can see it working by running `npm run dev` and loading the canvas UI as before, then running `npm run lint`, `npm run typecheck`, `npm run test`, and `npm run e2e` to confirm the new quality gates pass.
|
||||
|
||||
## Progress
|
||||
|
||||
- [x] (2026-01-27 00:00Z) Copied the ExecPlan requirements into `.agent/PLANS.md` and drafted this plan based on a repo audit.
|
||||
- [x] (2026-01-27 19:58Z) Establish the new `src/`-based structure, move files, and update imports to the `@/*` alias.
|
||||
- [x] (2026-01-27 20:09Z) Add shared infrastructure (shadcn/ui, env validation, logger, http helpers, instrumentation) and update code to use it.
|
||||
- [x] (2026-01-27 20:13Z) Add quality gates (Prettier integration, Vitest, Playwright) and create initial tests.
|
||||
- [x] (2026-01-27 20:23Z) Verify the refactor by running lint, typecheck, unit tests, e2e tests, and a dev-server smoke check.
|
||||
|
||||
## Surprises & Discoveries
|
||||
|
||||
- Observation: `tsc --noEmit` initially failed because `.next/types/validator.ts` referenced the old `/app` paths after the move.
|
||||
Evidence: `Cannot find module '../../app/page.js'` errors until a fresh `next build` regenerated `.next/types`.
|
||||
|
||||
- Observation: Playwright needed browser binaries installed before the e2e smoke test could launch.
|
||||
Evidence: `browserType.launch: Executable doesn't exist ... Please run: npx playwright install`.
|
||||
|
||||
- Observation: `next dev` failed when `src/app/globals.css` imported markdown styles from `../styles/markdown.css`.
|
||||
Evidence: `CssSyntaxError: ... Can't resolve '../styles/markdown.css' in '/Users/.../src/app'` until the styles moved under `src/app/styles`.
|
||||
|
||||
## Decision Log
|
||||
|
||||
- Decision: Move the App Router tree from `/app` to `/src/app` and update the `@/*` alias to point at `src/*`.
|
||||
Rationale: The create-next-app best practices expect a `src/` root and make internal imports consistent across server and client code.
|
||||
Date/Author: 2026-01-27 / Codex
|
||||
|
||||
- Decision: Introduce `src/features/canvas` for canvas UI and state, while keeping cross-cutting domain logic in `src/lib/*`.
|
||||
Rationale: The canvas UI is a single feature used by one route, so colocating its components and state improves navigability without overhauling shared domain code.
|
||||
Date/Author: 2026-01-27 / Codex
|
||||
|
||||
- Decision: Use shadcn/ui with the default “new-york” style and zinc palette, and keep Tailwind v4 as-is.
|
||||
Rationale: This aligns with the skill defaults and avoids introducing additional styling systems.
|
||||
Date/Author: 2026-01-27 / Codex
|
||||
|
||||
- Decision: Add minimal unit and e2e tests that exercise existing, stable behavior rather than inventing new UI flows.
|
||||
Rationale: The goal is to validate the refactor without forcing new product decisions.
|
||||
Date/Author: 2026-01-27 / Codex
|
||||
|
||||
- Decision: Mock `/api/projects` in the Playwright smoke test to force an empty store response.
|
||||
Rationale: The UI renders the empty-state copy only when there are no projects; mocking keeps the smoke test deterministic even if a developer has existing workspace data.
|
||||
Date/Author: 2026-01-27 / Codex
|
||||
|
||||
- Decision: Move markdown styles to `src/app/styles/markdown.css` and import them via `./styles/markdown.css` from `src/app/globals.css`.
|
||||
Rationale: Turbopack failed to resolve the previous `../styles/markdown.css` import during `next dev`; keeping the file under `src/app` keeps dev builds stable.
|
||||
Date/Author: 2026-01-27 / Codex
|
||||
|
||||
## Outcomes & Retrospective
|
||||
|
||||
(To be filled in once milestones are complete.)
|
||||
|
||||
## Context and Orientation
|
||||
|
||||
This repository is a Next.js 16 App Router UI with the router currently rooted at `/app` and shared code in `/src`. There is no `src/app` directory yet, imports often use deep relative paths (for example `../src/lib/...`), and there is no testing stack beyond ESLint. There is also no OpenTelemetry instrumentation and no shared UI primitives.
|
||||
|
||||
Key files and directories today:
|
||||
|
||||
The App Router lives in `app/` with `app/layout.tsx`, `app/page.tsx`, `app/globals.css`, and multiple API routes under `app/api/*` (for example `app/api/projects/route.ts` and `app/api/gateway/route.ts`). UI components live in `src/components/` and are tightly coupled to the canvas route. Client state and reducers are in `src/state/store.tsx`. Shared domain logic is already in `src/lib/` (for example `src/lib/gateway/`, `src/lib/projects/`, and `src/lib/clawdbot/`).
|
||||
|
||||
There is an existing ExecPlan at `.agent/EXECPLAN.md` that addresses different functionality. This plan does not depend on it and stands alone.
|
||||
|
||||
## Plan of Work
|
||||
|
||||
First, move the App Router and UI code to a `src/`-rooted structure that matches the create-next-app layout. This includes relocating the `app/` tree to `src/app`, placing canvas-specific UI and state under `src/features/canvas`, and ensuring all internal imports use the `@/*` alias. After the file moves, update `tsconfig.json` and any import paths so the app builds cleanly without deep relative paths.
|
||||
|
||||
Next, add the infrastructure expected by the skill: initialize shadcn/ui, add shared `src/components/ui` primitives, and introduce base `src/lib` helpers for environment validation, logging, HTTP helpers, and tracing. Update the existing code to use these helpers and centralize duplicated logic (notably the gateway config parsing used by `app/api/gateway/route.ts`). Add OpenTelemetry instrumentation via `src/instrumentation.ts` and ensure the service name matches the project.
|
||||
|
||||
Finally, add quality gates and tests. Integrate Prettier into the ESLint flat config, add `typecheck`, `test`, and `e2e` scripts, and configure Vitest and Playwright. Write initial unit tests for stable, pure utilities (for example `slugifyProjectName`) and a lightweight e2e smoke test that verifies the canvas UI loads. Run lint, typecheck, unit tests, e2e tests, and a dev-server smoke check to validate the refactor.
|
||||
|
||||
## Concrete Steps
|
||||
|
||||
Work from the repo root `/Users/georgepickett/clawdbot-agent-ui`.
|
||||
|
||||
1. Move the App Router to `src/app` and establish the feature structure using `git mv` so history is preserved.
|
||||
Example commands:
|
||||
git mv app src/app
|
||||
git mv src/components src/features/canvas/components
|
||||
git mv src/state src/features/canvas/state
|
||||
mkdir -p src/components/shared src/components/ui src/features src/hooks src/styles tests/unit tests/e2e
|
||||
|
||||
2. Update import paths to the new locations and to the `@/*` alias. Every import that currently starts with `../src/` or `../../src/` should be replaced with `@/` and point to the new structure. Make sure `src/app/page.tsx`, all API route files under `src/app/api/`, and all moved components and state modules compile cleanly.
|
||||
|
||||
3. Update `tsconfig.json` so the `@/*` alias maps to `./src/*`. Ensure all TypeScript path imports align with the new structure.
|
||||
|
||||
4. Initialize shadcn/ui and add a starter component. Run the CLI with the opinionated answers from the skill: style `new-york`, base color `zinc`, CSS variables `yes`, global CSS `src/app/globals.css`, components alias `@/components`, utils alias `@/lib/utils`, and UI alias `@/components/ui`. Then add the Button component.
|
||||
Example commands:
|
||||
npx shadcn@latest init
|
||||
npx shadcn@latest add button
|
||||
|
||||
5. Add shared `src/lib` helpers and wire them into the existing code.
|
||||
|
||||
Create `src/lib/env.ts` and validate environment variables using Zod. Include optional server variables used in config resolution (`MOLTBOT_STATE_DIR`, `CLAWDBOT_STATE_DIR`, `MOLTBOT_CONFIG_PATH`, `CLAWDBOT_CONFIG_PATH`) and an optional client variable (`NEXT_PUBLIC_GATEWAY_URL`). Use this module in server-side config resolution and in the client hook to default the gateway URL.
|
||||
|
||||
Create `src/lib/logger.ts` as a small wrapper over `console` that exposes `info`, `warn`, `error`, and `debug`, and replace direct `console.*` usage in the gateway client and API routes with the logger to keep logging consistent and easy to adjust.
|
||||
|
||||
Create `src/lib/http.ts` with a `fetchJson<T>(input, init)` helper that throws a useful error when responses are not ok. Update `src/lib/projects/client.ts` (and any other client fetchers) to use this helper.
|
||||
|
||||
Create `src/lib/tracing.ts` as the shared tracing helper, and add `src/instrumentation.ts` that calls `registerOTel` from `@vercel/otel` with `serviceName: "clawdbot-agent-ui"`.
|
||||
|
||||
6. Reduce duplicated gateway config logic by refactoring `src/app/api/gateway/route.ts` to rely on the existing `src/lib/clawdbot/config.ts` helpers (or introduce a small `src/lib/clawdbot/gateway.ts` helper if needed) so there is a single source of truth for state dir/config resolution.
|
||||
|
||||
7. Split the markdown styling into `src/styles/markdown.css` and import it from `src/app/globals.css`, leaving the Tailwind v4 `@import "tailwindcss";` line intact.
|
||||
|
||||
8. Add quality gates and testing configuration.
|
||||
|
||||
Install dependencies (npm is used by this repo):
|
||||
npm install zod @vercel/otel
|
||||
npm install -D prettier eslint-config-prettier vitest @testing-library/react @testing-library/jest-dom jsdom @playwright/test
|
||||
|
||||
Update `eslint.config.mjs` to include `eslint-config-prettier/flat` and keep the existing ignores. Update `package.json` scripts to:
|
||||
lint: eslint .
|
||||
typecheck: tsc --noEmit
|
||||
test: vitest
|
||||
e2e: playwright test
|
||||
|
||||
Create `vitest.config.ts` with a JSDOM environment and a setup file that imports `@testing-library/jest-dom`. Add at least two unit tests in `tests/unit/`:
|
||||
|
||||
- `tests/unit/slugifyProjectName.test.ts` should assert that `slugifyProjectName("My Project")` becomes `"my-project"` and that an all-symbol input throws with the current error message.
|
||||
- `tests/unit/fetchJson.test.ts` should stub `fetch` and assert that non-ok responses throw and ok responses return parsed JSON.
|
||||
|
||||
Create `playwright.config.ts` with a `webServer` that runs `npm run dev` on port 3000. Add `tests/e2e/canvas-smoke.spec.ts` that loads `/` and asserts the empty-state copy “Create a workspace to begin.” is visible.
|
||||
|
||||
9. Run verification commands and record outputs in the Progress section as you go.
|
||||
|
||||
## Validation and Acceptance
|
||||
|
||||
The refactor is accepted when the app still runs and all quality gates pass.
|
||||
|
||||
Run the following from `/Users/georgepickett/clawdbot-agent-ui`:
|
||||
|
||||
- `npm run lint` and expect no ESLint errors.
|
||||
- `npm run typecheck` and expect no TypeScript errors.
|
||||
- `npm run test` and expect all unit tests to pass (the new tests must fail before the implementation and pass after).
|
||||
- `npm run e2e` and expect the Playwright smoke test to pass.
|
||||
- `npm run dev`, open `http://localhost:3000`, and confirm the canvas UI loads and the empty-state message appears when no workspace is selected.
|
||||
|
||||
For each milestone, follow the verification workflow:
|
||||
|
||||
1. Tests to write: create the unit tests described above and confirm they fail before the helper implementations or refactors are complete.
|
||||
2. Implementation: perform the moves, refactors, and helper additions described in the Plan of Work.
|
||||
3. Verification: re-run the relevant tests and commands until they pass.
|
||||
4. Commit: after each milestone succeeds, commit the changes with a message like “Milestone 1: Move app to src and update imports”.
|
||||
|
||||
## Idempotence and Recovery
|
||||
|
||||
The file moves can be re-run safely with `git mv` and do not delete data. If a move goes to the wrong location, move it back and re-run the import updates. Dependency installs are safe to re-run; if conflicts occur, remove `node_modules` and run `npm install` again. If any tests or builds fail, revert to the last successful commit and re-apply the current milestone with smaller, verified steps.
|
||||
|
||||
## Artifacts and Notes
|
||||
|
||||
Include short transcripts of any failing test errors or build errors encountered during the refactor in this section so the next contributor can see what broke and why. Keep snippets concise and focused on the error and fix.
|
||||
|
||||
`npm run test` initially failed with:
|
||||
ReferenceError: expect is not defined
|
||||
at tests/setup.ts:1
|
||||
Fix: swap `@testing-library/jest-dom` import to `@testing-library/jest-dom/vitest` and restrict Vitest to `tests/unit/**`.
|
||||
|
||||
`npm run e2e` initially failed with:
|
||||
Error: browserType.launch: Executable doesn't exist at .../chromium_headless_shell...
|
||||
Fix: run `npx playwright install` to fetch browsers.
|
||||
|
||||
`npm run e2e` then failed with:
|
||||
Error: getByText('Create a workspace to begin.') ... element(s) not found
|
||||
Fix: mock `/api/projects` in the Playwright test to return an empty store.
|
||||
|
||||
`npm run dev` failed with:
|
||||
CssSyntaxError: ... Can't resolve '../styles/markdown.css' in '/Users/.../src/app'
|
||||
Fix: move markdown styles into `src/app/styles/markdown.css` and update the import to `./styles/markdown.css`.
|
||||
|
||||
## Interfaces and Dependencies
|
||||
|
||||
New dependencies to add include `zod` for environment validation, `@vercel/otel` for OpenTelemetry, `prettier` and `eslint-config-prettier` for formatting alignment, `vitest` plus React Testing Library and `jsdom` for unit tests, and `@playwright/test` for e2e testing. The shadcn/ui CLI will add its own dependencies (notably `class-variance-authority`, `tailwind-merge`, `clsx`, and `@radix-ui/react-slot`) when the Button component is installed.
|
||||
|
||||
Define these modules explicitly:
|
||||
|
||||
In `src/lib/env.ts`, export `env` as the parsed result of a Zod schema containing the optional server variables and the optional `NEXT_PUBLIC_GATEWAY_URL` string.
|
||||
|
||||
In `src/lib/logger.ts`, export a `logger` object with `info`, `warn`, `error`, and `debug` methods; each should delegate to the matching `console` method.
|
||||
|
||||
In `src/lib/http.ts`, export `fetchJson<T>(input: RequestInfo | URL, init?: RequestInit): Promise<T>` that throws an `Error` with the response body’s `error` field (if present) or a default message.
|
||||
|
||||
In `src/lib/tracing.ts`, export a `registerTracing()` function that calls `registerOTel` or is invoked by `src/instrumentation.ts`.
|
||||
|
||||
In `src/instrumentation.ts`, export a `register()` function that calls `registerOTel({ serviceName: "clawdbot-agent-ui" })`.
|
||||
|
||||
Plan update note (2026-01-27 19:58Z): Marked milestone 1 complete after moving the App Router to `src/app`, relocating canvas components/state under `src/features/canvas`, and switching imports to the `@/*` alias with the updated TypeScript path mapping.
|
||||
Plan update note (2026-01-27 20:09Z): Marked milestone 2 complete after adding shadcn/ui, env validation, logger/http/tracing helpers, gateway config reuse, and extracting markdown styles into `src/styles/markdown.css`.
|
||||
Plan update note (2026-01-27 20:13Z): Marked milestone 3 complete after wiring Prettier into ESLint, adding Vitest + Playwright configs, and creating initial unit and e2e tests.
|
||||
Plan update note (2026-01-27 20:23Z): Marked milestone 4 complete after running lint/typecheck/tests/e2e, installing Playwright browsers, and stabilizing the smoke test via an `/api/projects` mock.
|
||||
Plan update note (2026-01-27 20:38Z): Moved markdown styles under `src/app/styles` and updated `globals.css` import to fix `next dev` resolution errors.
|
||||
@@ -1,212 +0,0 @@
|
||||
# Refactor Canvas Zoom/Pan for a Figma-like Agent Workspace
|
||||
|
||||
This ExecPlan is a living document. The sections `Progress`, `Surprises & Discoveries`, `Decision Log`, and `Outcomes & Retrospective` must be kept up to date as work proceeds.
|
||||
|
||||
Maintain this document in accordance with `.agent/PLANS.md` (repository root).
|
||||
|
||||
## Purpose / Big Picture
|
||||
|
||||
After this change, the Agent UI canvas supports a “real canvas” interaction model that feels closer to Figma: zoom is anchored to the cursor (so the point under your mouse stays under your mouse while zooming), trackpad pinch zoom works, mouse wheel can zoom (without breaking trackpad two-finger pan), and canvas panning is smooth and predictable. Managing many agent tiles becomes practical because you can quickly zoom to inspect a tile, zoom out for overview, and use a minimap to navigate large layouts.
|
||||
|
||||
You can see it working by starting the dev server, creating/opening a workspace with multiple agent tiles, then pinch-zooming on a trackpad (the point under the cursor stays pinned), using a mouse wheel to zoom, two-finger scrolling to pan, dragging empty canvas space to pan, and using a minimap overlay to jump around when tiles are spread out.
|
||||
|
||||
## Progress
|
||||
|
||||
- [x] (2026-01-27 00:00Z) Audited current canvas transform/zoom behavior and drafted ExecPlan.
|
||||
- [x] (2026-01-27) Milestone 1: Add transform math utilities + unit tests that define cursor-anchored zoom behavior.
|
||||
- [x] (2026-01-27) Milestone 2: Implement wheel/pinch zoom + trackpad pan with rAF throttling; keep existing buttons working.
|
||||
- [x] (2026-01-27) Milestone 3: Add minimap (overview) and “zoom to fit” navigation helpers for large canvases.
|
||||
- [x] (2026-01-27) Milestone 4: Add Playwright interaction coverage and polish (performance + edge cases).
|
||||
|
||||
## Surprises & Discoveries
|
||||
|
||||
(To be filled in during implementation.)
|
||||
|
||||
## Decision Log
|
||||
|
||||
- Decision: Implement the interaction model without introducing a third-party canvas/graph library (no React Flow / no D3 zoom).
|
||||
Rationale: The current UI already has tile layout and drag/resize behavior; the biggest UX gap is transform math + input handling. Keeping it in-house minimizes dependency surface and keeps the behavior easy to tailor.
|
||||
Date/Author: 2026-01-27 / Codex
|
||||
|
||||
- Decision: Use “zoom to cursor” math and multiplicative zoom deltas (exponential scaling) rather than linear +/- 0.1 steps for wheel/pinch.
|
||||
Rationale: Cursor-anchored zoom is the core Figma-like affordance; multiplicative zoom feels consistent across zoom levels and matches common canvas UX expectations.
|
||||
Date/Author: 2026-01-27 / Codex
|
||||
|
||||
- Decision: Default input mapping: trackpad two-finger scroll pans; trackpad pinch (wheel event with ctrlKey) zooms; mouse wheel zooms when it appears to be a wheel (line-based deltas) and otherwise pans.
|
||||
Rationale: Browsers do not reliably distinguish “mouse wheel” vs “trackpad scroll” in a principled way. This heuristic keeps trackpad pan usable while still enabling mouse wheel zoom without requiring keyboard shortcuts.
|
||||
Date/Author: 2026-01-27 / Codex
|
||||
|
||||
- Decision: Trackpad pan subtracts wheel deltas from offsets (scroll down reveals lower world coordinates).
|
||||
Rationale: Matches standard scroll direction expectations while keeping zoom anchor math unchanged.
|
||||
Date/Author: 2026-01-27 / Codex
|
||||
|
||||
- Decision: `zoomToFit` accepts the current transform to preserve state when there are no tiles.
|
||||
Rationale: The helper needs a fallback transform; passing it in keeps the helper pure and avoids hidden defaults.
|
||||
Date/Author: 2026-01-27 / Codex
|
||||
|
||||
## Outcomes & Retrospective
|
||||
|
||||
- Outcome: Canvas interactions now support cursor-anchored zoom (wheel/pinch), smooth pan, minimap navigation, and zoom-to-fit, with shared transform math and coverage in unit/e2e tests.
|
||||
|
||||
## Context and Orientation
|
||||
|
||||
This repo is a Next.js App Router UI. The “canvas” is implemented as a full-screen `CanvasViewport` that renders multiple draggable/resizable “agent tiles”.
|
||||
|
||||
Relevant files include `src/features/canvas/components/CanvasViewport.tsx` (canvas surface + CSS transform), `src/features/canvas/components/AgentTile.tsx` (tile drag/resize; pointer deltas are divided by zoom), `src/features/canvas/components/HeaderBar.tsx` (zoom controls + zoom readout), `src/features/canvas/state/store.tsx` (the `CanvasTransform` state and `setCanvas` reducer action), and `src/app/page.tsx` (wires state to the viewport and header).
|
||||
|
||||
Current behavior (as of 2026-01-27) is that zoom only changes via header +/- buttons and is applied as CSS `translate(offsetX, offsetY) scale(zoom)` with `transformOrigin: "0 0"`. Panning is pointer-drag on empty canvas space only (no trackpad two-finger pan and no wheel support). Zoom is not cursor-anchored; it effectively zooms around the top-left origin of the inner container.
|
||||
|
||||
Definitions used in this plan: “world coordinates” are the coordinate space where tile positions and sizes are stored (tile `{ position: {x,y}, size: {width,height} }` in `AgentTile` data). “viewport/screen coordinates” are CSS pixels relative to the visible canvas viewport element. The transform maps world → screen as `screen = offset + zoom * world`, where `offset` is `{offsetX, offsetY}` in screen pixels.
|
||||
|
||||
This plan also uses a few browser/DOM terms. `requestAnimationFrame` (abbreviated “rAF” below) is a browser API that runs a callback before the next repaint; we use it to coalesce many rapid pointer/wheel events into at most one state update per frame. A “passive” event listener is one that cannot call `preventDefault()`; we must use a non-passive `wheel` listener so we can prevent the browser’s own page zoom behavior during trackpad pinch. A `WheelEvent`’s `deltaMode` describes whether `deltaX/deltaY` are in pixels (typical trackpads) or lines (typical mouse wheels).
|
||||
|
||||
## Plan of Work
|
||||
|
||||
This change is mostly about (1) getting transform math correct and testable, and (2) implementing input handling that feels intentional and smooth under real device behavior (mouse wheels, trackpads, pinch gestures). The work proceeds in small steps so that the core math is locked down with tests before wiring up user input.
|
||||
|
||||
Milestone 1 creates a small, pure “canvas transform math” module and unit tests that specify cursor-anchored zoom and viewport/world conversions. This is the foundation; everything else builds on it.
|
||||
|
||||
Milestone 2 wires wheel/pinch/pan into `CanvasViewport` using non-passive event listeners (so we can `preventDefault()` to stop browser-page zoom during pinch) and rAF throttling (so transform updates do not cause jank). Existing header zoom buttons are updated to use the same transform math (zooming around viewport center).
|
||||
|
||||
Milestone 3 adds an overview minimap that visualizes tile bounds and the current viewport rectangle, plus a “zoom to fit” action that frames all tiles with padding. This is the “manage many tiles” accelerator.
|
||||
|
||||
Milestone 4 adds Playwright coverage for the interaction contract and cleans up edge cases (clamping, empty canvas, huge deltas, selection behavior), keeping the implementation simple but robust.
|
||||
|
||||
## Concrete Steps
|
||||
|
||||
All commands below run from:
|
||||
|
||||
/Users/georgepickett/clawdbot-agent-ui
|
||||
|
||||
### Milestone 1: Transform Math + Unit Tests
|
||||
|
||||
Acceptance for this milestone is that a pure function can compute a new `CanvasTransform` that zooms at a given viewport point (cursor-anchored), preserving the world point under the cursor across zoom changes, and that conversions between screen and world coordinates are correct and covered by unit tests.
|
||||
|
||||
1. Create `src/features/canvas/lib/transform.ts` (new).
|
||||
|
||||
Implement these exported functions (keep them pure and small). `clampZoom(zoom: number): number` clamps to a chosen range (decide in this milestone; the tests encode the final decision). `screenToWorld(transform, screen)` computes `{ (screen.x - offsetX) / zoom, (screen.y - offsetY) / zoom }` and `worldToScreen(transform, world)` computes `{ offsetX + world.x * zoom, offsetY + world.y * zoom }`. `zoomAtScreenPoint(transform, nextZoomRaw, screenPoint)` computes the world point under `screenPoint` using `screenToWorld`, clamps `nextZoomRaw`, then sets offsets so the same world point maps back to `screenPoint` at the clamped zoom via `nextOffsetX = screenPoint.x - world.x * nextZoom` and `nextOffsetY = screenPoint.y - world.y * nextZoom`, returning `{ zoom: nextZoom, offsetX: nextOffsetX, offsetY: nextOffsetY }`.
|
||||
|
||||
Keep `CanvasTransform` imported from `src/features/canvas/state/store.tsx` (do not redefine the type). Choose a zoom clamp range appropriate for reading tile content; a reasonable starting point is `minZoom=0.25` and `maxZoom=3.0`, but the tests should encode the final decision.
|
||||
|
||||
2. Add unit tests in `tests/unit/canvasTransform.test.ts` (new).
|
||||
|
||||
Write these tests first and confirm they fail until you implement the functions. Add a round-trip test that asserts `worldToScreen` then `screenToWorld` are inverses within float tolerance (for example using `{ zoom: 1.5, offsetX: 120, offsetY: -80 }`). Add a cursor-anchored zoom test that asserts `zoomAtScreenPoint` preserves the world point under the cursor within tolerance. Add a clamp test that asserts values below/above min/max are clamped appropriately.
|
||||
|
||||
3. Run:
|
||||
|
||||
npm run test -- tests/unit/canvasTransform.test.ts
|
||||
|
||||
Expect all new tests to pass.
|
||||
|
||||
4. Commit:
|
||||
|
||||
git add -A
|
||||
git commit -m "Milestone 1: Add cursor-anchored canvas transform math"
|
||||
|
||||
### Milestone 2: Wheel/Pinch Zoom + Trackpad Pan (Smooth)
|
||||
|
||||
Acceptance for this milestone is that trackpad pinch zoom works (browser page zoom does not trigger while the cursor is over the canvas), mouse wheel zoom works, trackpad two-finger scroll pans the canvas (so you can navigate without drag), dragging empty canvas space still pans (existing behavior), and panning/zooming feels smooth (no stutter from excessive state updates).
|
||||
|
||||
1. Update `src/features/canvas/components/CanvasViewport.tsx` to handle wheel and pinch.
|
||||
|
||||
Attach a native `wheel` event listener to the viewport element with `{ passive: false }` so `preventDefault()` reliably works. On wheel events, compute `screenPoint` relative to the viewport element (use `getBoundingClientRect()` and `event.clientX/Y`). Decide whether the wheel event is zoom vs pan: treat `event.ctrlKey === true` as zoom (common trackpad-pinch signal), otherwise treat line-based deltas (`event.deltaMode`) as zoom (typical mouse wheel), and treat remaining pixel-based deltas as pan (typical trackpad scroll). When zooming, use multiplicative scaling (for example `nextZoom = transform.zoom * Math.exp(-event.deltaY * ZOOM_SENSITIVITY)`) and apply `zoomAtScreenPoint(transform, nextZoom, screenPoint)` from Milestone 1. When panning, update offsets by wheel deltas; verify the sign feels like direct manipulation and record the final choice in the Decision Log.
|
||||
|
||||
For smoothness, do not call `onUpdateTransform` on every raw wheel/pointer event. Throttle to animation frames by storing a pending transform update in a ref and scheduling a single `requestAnimationFrame` to apply the latest pending transform. Keep the existing pointer-drag pan, but apply the same rAF throttling to pointermove updates so panning stays smooth with many tiles.
|
||||
|
||||
2. Update `src/app/page.tsx` zoom handlers to use the new math and feel consistent.
|
||||
|
||||
Replace linear `zoom +/- 0.1` with multiplicative steps (for example `zoom *= 1.1` and `zoom /= 1.1`) using the same clamp. Anchor button-based zoom to the viewport center (not cursor) for predictability; ensure the handler computes a viewport-center screen point and calls `zoomAtScreenPoint` rather than directly patching zoom. Avoid creating a second transform path: all zoom changes (wheel/pinch/buttons) should go through the same math utility so behavior stays consistent.
|
||||
|
||||
3. Manually verify this behavior and record brief observations under `Artifacts and Notes`. Start the dev server (`npm run dev`), open the app, create/open a workspace, create several agent tiles, then pinch-zoom with a trackpad (verify browser page zoom does not happen while over the canvas and the point under the cursor remains pinned), two-finger scroll (verify pan), and mouse wheel (verify zoom, using a mouse rather than a trackpad).
|
||||
|
||||
4. Add or update Playwright coverage (keep it stable/deterministic).
|
||||
|
||||
Extend `tests/e2e/canvas-smoke.spec.ts` or add `tests/e2e/canvas-zoom-pan.spec.ts` (preferred) by mocking `/api/projects` to return one workspace with one tile positioned away from the origin.
|
||||
|
||||
Add one test that dispatches wheel events over the canvas surface and asserts the zoom percentage text changes. Add a second test that dispatches a trackpad-like wheel (pixel deltas, no ctrlKey) and verifies the tile’s screen position changes by comparing `boundingBox()` before and after; `boundingBox()` is Playwright’s API for reading an element’s rendered rectangle in CSS pixels. If Playwright wheel synthesis cannot reliably reproduce the intended wheel characteristics (especially `deltaMode`), focus on the zoom readout plus a clear bounding box change and document the limitation in `Artifacts and Notes`. If needed, add a stable `data-*` attribute on the canvas viewport element to query it reliably (for example `data-canvas-viewport`).
|
||||
|
||||
5. Run:
|
||||
|
||||
npm run test
|
||||
npm run e2e
|
||||
|
||||
6. Commit:
|
||||
|
||||
git add -A
|
||||
git commit -m "Milestone 2: Add wheel/pinch zoom and smooth pan"
|
||||
|
||||
### Milestone 3: Minimap + Zoom to Fit
|
||||
|
||||
Acceptance for this milestone is that a minimap appears when there is at least one tile, it shows tile rectangles and the current viewport rectangle, clicking (or dragging) in the minimap recenters the viewport to that location, and a “Zoom to Fit” action frames all tiles with padding.
|
||||
|
||||
1. Add `src/features/canvas/components/CanvasMinimap.tsx` (new).
|
||||
|
||||
Keep the minimap simple and SVG-based. It takes `tiles: AgentTile[]`, `transform: CanvasTransform`, `viewportSize: { width: number; height: number }` (measured from `CanvasViewport` via a `ResizeObserver`, a browser API that notifies you when an element’s size changes), and `onUpdateTransform(patch: Partial<CanvasTransform>): void`. It computes the world bounds of all tiles using tile position and size, adds padding in world units, then computes the current viewport world rectangle using the transform (`worldLeft = -offsetX / zoom`, `worldTop = -offsetY / zoom`, `worldWidth = viewportWidth / zoom`, `worldHeight = viewportHeight / zoom`). Render an SVG with a `viewBox` matching the content bounds; `viewBox` is the SVG coordinate system used to map world units into the minimap panel. Draw tile rects and the viewport rect. On click/drag in the minimap, convert minimap coordinates back to a world point and update offsets so that world point becomes the viewport center.
|
||||
|
||||
2. Wire minimap into `src/app/page.tsx` (or into `CanvasViewport` as an overlay).
|
||||
|
||||
Place it as a floating panel in a corner (for example bottom-right) with `pointer-events-auto` and a small footprint so it doesn’t interfere with tiles.
|
||||
|
||||
3. Add “Zoom to Fit” button to `src/features/canvas/components/HeaderBar.tsx`.
|
||||
|
||||
Implement a helper in `src/features/canvas/lib/transform.ts` named `zoomToFit(tiles: AgentTile[], viewportSize: { width: number; height: number }, paddingPx: number): CanvasTransform`. If there are no tiles it should return the current transform unchanged. Otherwise it should compute a zoom that fits all tile bounds within the viewport (minus padding), clamp that zoom, and compute offsets so the bounds are centered.
|
||||
|
||||
4. Tests:
|
||||
|
||||
Add `tests/unit/canvasZoomToFit.test.ts` (new) to assert that `zoomToFit` produces a transform where the fitted bounds map within the viewport with padding.
|
||||
|
||||
5. Run:
|
||||
|
||||
npm run test
|
||||
npm run e2e
|
||||
|
||||
6. Commit:
|
||||
|
||||
git add -A
|
||||
git commit -m "Milestone 3: Add minimap and zoom-to-fit"
|
||||
|
||||
### Milestone 4: Polish + Edge Cases
|
||||
|
||||
Acceptance for this milestone is that there are no regressions in tile drag/resize under zoom, canvas interactions remain responsive with many tiles, and edge cases are handled without mystery behavior (empty canvas, extreme wheel deltas, zoom clamping).
|
||||
|
||||
1. Verify tile interactions under zoom by dragging a tile while zoomed in and out (movement tracks cursor correctly) and resizing a tile while zoomed in and out (size changes are proportional and clamped).
|
||||
|
||||
2. Keep performance guardrails in place: ensure wheel/pan uses rAF throttling (no synchronous state update loops), add `will-change: transform;` to the scaled inner container if needed, and consider adding `overscroll-behavior: none;` and `touch-action: none;` to the canvas surface so browser scroll/zoom gestures do not fight the canvas.
|
||||
|
||||
3. Expand Playwright tests only as far as they remain deterministic.
|
||||
|
||||
4. Run final verification:
|
||||
|
||||
npm run lint
|
||||
npm run typecheck
|
||||
npm run test
|
||||
npm run e2e
|
||||
|
||||
5. Commit:
|
||||
|
||||
git add -A
|
||||
git commit -m "Milestone 4: Polish canvas interactions and add coverage"
|
||||
|
||||
## Validation and Acceptance
|
||||
|
||||
This work is accepted when, on a developer machine, the canvas supports cursor-anchored zoom (pinch or wheel) and the world point under the cursor remains stable while zooming; trackpad two-finger scroll pans (not zoom) while mouse wheel zoom works; existing header zoom controls still work and feel consistent with wheel/pinch zoom; a minimap provides an overview and navigation for canvases with many tiles; and `npm run test` and `npm run e2e` pass, with the new unit tests failing before implementation and passing after.
|
||||
|
||||
## Idempotence and Recovery
|
||||
|
||||
The transform refactor is safe to apply incrementally because it is additive first (pure math module + tests), then wiring changes. If input handling becomes confusing or flaky, revert to the last milestone commit and re-apply one interaction at a time (wheel zoom first, then trackpad pan, then minimap). Keep the old header zoom buttons functional throughout so the UI remains usable even while iterating.
|
||||
|
||||
## Artifacts and Notes
|
||||
|
||||
Record short evidence snippets here during implementation, such as unit test failure output that guided a math fix, a brief note on the final chosen zoom clamp range and why, and any device-specific observations (for example “Chrome pinch zoom sets ctrlKey=true on wheel events on macOS”).
|
||||
|
||||
- 2026-01-27: Added transform math utils + tests. Clamp range set to 0.25–3.0 to keep tiles readable while allowing overview. `npm run test -- tests/unit/canvasTransform.test.ts` passes.
|
||||
- 2026-01-27: Added wheel/pinch zoom + rAF throttling, updated header zoom anchoring, and added Playwright coverage. Playwright wheel events always surfaced as line-based deltas, so trackpad-pan simulation was unreliable; tests cover zoom readout changes and tile bounds updates instead. `npm run test` and `npm run e2e` pass.
|
||||
- 2026-01-27: Added SVG minimap + zoom-to-fit helper/button, plus `zoomToFit` unit tests. `npm run test` and `npm run e2e` pass.
|
||||
- 2026-01-27: Added overscroll/touch-action guardrails and `will-change: transform` on the scaled canvas content; lint/typecheck/test/e2e pass.
|
||||
- 2026-01-27: Switched canvas scaling to use CSS `zoom` when supported (fallback to transform scale) to keep zoomed text crisp.
|
||||
|
||||
## Interfaces and Dependencies
|
||||
|
||||
No new third-party dependencies are required for this plan. The core interfaces are `CanvasTransform` from `src/features/canvas/state/store.tsx`, new pure transform helpers in `src/features/canvas/lib/transform.ts`, and a minimap component in `src/features/canvas/components/CanvasMinimap.tsx`. The implementation must keep a single source of truth for transform math by routing all zoom changes (wheel/pinch/buttons/zoom-to-fit) through `src/features/canvas/lib/transform.ts`.
|
||||
|
||||
Plan creation note (2026-01-27 00:00Z): Created this ExecPlan after auditing current zoom/pan behavior in `CanvasViewport.tsx`, `AgentTile.tsx`, and `page.tsx`, and after researching common canvas UX conventions (cursor-anchored zoom, pinch zoom, scroll-to-pan, minimap + zoom-to-fit) to keep the plan self-contained and beginner-executable.
|
||||
@@ -1,174 +0,0 @@
|
||||
# Show Thinking Traces in Agent Chat Tiles
|
||||
|
||||
This ExecPlan is a living document. The sections `Progress`, `Surprises & Discoveries`, `Decision Log`, and `Outcomes & Retrospective` must be kept up to date as work proceeds.
|
||||
|
||||
Maintain this document in accordance with `.agent/PLANS.md` (repository root).
|
||||
|
||||
## Purpose / Big Picture
|
||||
|
||||
After this change, the Agent Canvas UI surfaces the model's "thinking" blocks alongside assistant replies. When a chat run streams, the tile shows a live thinking trace; when the run completes or when history is loaded, the thinking trace is preserved in the output timeline so the user can read it later. This makes the UI match the session log format (for example the `content[]` items with `type: "thinking"` in `~/.clawdbot/agents/.../sessions/*.jsonl`) and removes the current gap where reasoning is either hidden or truncated.
|
||||
|
||||
You can see it working by starting the dev server, sending a message to an agent with thinking enabled, and watching the tile output show a "Thinking" block before the assistant response. Reloading the page should still show those thinking blocks in history for that session.
|
||||
|
||||
## Progress
|
||||
|
||||
- [x] (2026-01-27 23:46Z) Milestone 1: Added `src/lib/text/extractThinking.ts` with extraction/formatting helpers and `tests/unit/extractThinking.test.ts`; unit tests pass.
|
||||
- [ ] (2026-01-27 23:55Z) Milestone 2: Wired thinking traces into live streaming, history loading, and tile rendering; traces collapse after completion with a toggleable summary; README updated (manual UI verification still pending).
|
||||
|
||||
## Surprises & Discoveries
|
||||
|
||||
- Observation: No surprises encountered while parsing thinking blocks or wiring UI rendering.
|
||||
Evidence: `npm run test -- tests/unit/extractThinking.test.ts` passed on first run.
|
||||
|
||||
## Decision Log
|
||||
|
||||
- Decision: Treat "thinking traces" as first-class output lines, rendered before the assistant reply and persisted in history.
|
||||
Rationale: The session logs include explicit thinking blocks; representing them as output lines preserves them after streaming ends and keeps the UI timeline coherent.
|
||||
Date/Author: 2026-01-27 / Codex
|
||||
|
||||
- Decision: Parse thinking from `message.content[]` items with `type: "thinking"`, with `<thinking>...</thinking>` tag parsing as back-compat.
|
||||
Rationale: The provided session log uses `content[]` with `type: "thinking"`, but older logs can embed thinking tags; supporting both matches existing Clawdbot UI behavior.
|
||||
Date/Author: 2026-01-27 / Codex
|
||||
|
||||
- Decision: Render live thinking with the raw markdown content, but store completed thinking as a formatted trace block in output history.
|
||||
Rationale: Live thinking should reflect the model output as-is, while history benefits from consistent styling and collapsible traces.
|
||||
Date/Author: 2026-01-27 / Codex
|
||||
|
||||
- Decision: Represent completed traces as a prefixed markdown string and render them as collapsed `<details>` blocks with a "Trace" summary.
|
||||
Rationale: Output lines remain simple strings for persistence, while the UI can detect and render a toggleable collapsed view without adding a new data model.
|
||||
Date/Author: 2026-01-27 / Codex
|
||||
|
||||
## Outcomes & Retrospective
|
||||
|
||||
- Outcome: Thinking extraction helpers and unit tests added; UI now surfaces thinking during streaming and persists it in history. Manual end-to-end verification remains to confirm runtime behavior.
|
||||
|
||||
## Context and Orientation
|
||||
|
||||
This repo is a Next.js App Router UI for the Clawdbot agent canvas. Chat traffic arrives over the gateway WebSocket and is handled in `src/app/page.tsx`, which updates tile state stored in `src/features/canvas/state/store.tsx`. Tiles render their outputs in `src/features/canvas/components/AgentTile.tsx` using `ReactMarkdown` and the `agent-markdown` styles in `src/app/styles/markdown.css`.
|
||||
|
||||
Relevant paths for this change:
|
||||
- `src/app/page.tsx`: Gateway event handlers for `chat` and `agent`, history loading (`chat.history`), and the current thinking trace extraction helpers.
|
||||
- `src/features/canvas/components/AgentTile.tsx`: Renders the tile output area and the inline "thinking" block.
|
||||
- `src/lib/text/extractText.ts`: Extracts assistant/user text and strips `<thinking>` tags from assistant responses.
|
||||
- `tests/unit/*`: Vitest unit tests; new parsing utilities should be tested here.
|
||||
|
||||
The provided session log at `/Users/georgepickett/.clawdbot/agents/proj-clawdbot-agent-ui-agent-5aab/sessions/b9773235-f5b1-46b4-8eb6-86bbd312828b.jsonl` shows the exact thinking payload shape: assistant `message.content[]` includes `{ type: "thinking", thinking: "...", thinkingSignature: "..." }` ahead of tool calls and text.
|
||||
|
||||
## Plan of Work
|
||||
|
||||
The work has two milestones. First, create a small parsing utility that can extract thinking text from the same message shapes found in the session log and format it for display, backed by unit tests. Second, wire that utility into the live chat stream handler and the history loader so thinking traces show during streaming and remain visible in the output timeline after completion. The tile rendering should display the live thinking trace as markdown (not just a truncated first line) and the output timeline should include formatted thinking blocks before assistant replies. Update the README with a short note about thinking traces so the behavior is documented.
|
||||
|
||||
## Concrete Steps
|
||||
|
||||
All commands below run from:
|
||||
|
||||
/Users/georgepickett/clawdbot-agent-ui
|
||||
|
||||
### Milestone 1: Thinking extraction + tests
|
||||
|
||||
Acceptance for this milestone is that we can extract thinking text from the message shapes in the session log (content arrays with `type: "thinking"`) and from embedded `<thinking>` tags, and that the behavior is covered by unit tests.
|
||||
|
||||
1. Add a new helper in `src/lib/text/extractThinking.ts`.
|
||||
|
||||
Implement and export:
|
||||
- `extractThinking(message: unknown): string | null` -- returns concatenated thinking text when `message.content` is an array of `{ type: "thinking", thinking: string }` entries, or when raw text contains `<thinking>...</thinking>` tags. Return `null` for empty/whitespace-only results.
|
||||
- `formatThinkingMarkdown(text: string): string` -- returns a markdown block that visually separates thinking from normal output (for example: a "Thinking:" label followed by italicized non-empty lines). Keep it deterministic so tests can assert exact output.
|
||||
|
||||
The helper should not mutate inputs and should not rely on DOM APIs.
|
||||
|
||||
2. Add unit tests in `tests/unit/extractThinking.test.ts` (new).
|
||||
|
||||
Write tests first and confirm they fail before implementation. Cover these cases:
|
||||
- Extracts a single thinking block from `content[]` and returns trimmed text.
|
||||
- Extracts multiple thinking blocks and joins them with `\n` in order.
|
||||
- Extracts thinking from a string containing `<thinking>...</thinking>` tags.
|
||||
- Returns `null` when no thinking exists or when the thinking text is only whitespace.
|
||||
- `formatThinkingMarkdown` produces the expected labeled/italicized markdown for multi-line thinking.
|
||||
|
||||
3. Run:
|
||||
|
||||
npm run test -- tests/unit/extractThinking.test.ts
|
||||
|
||||
Expect the new tests to pass.
|
||||
|
||||
4. Commit:
|
||||
|
||||
git add -A
|
||||
git commit -m "Milestone 1: Add thinking extraction helpers"
|
||||
|
||||
### Milestone 2: UI wiring + history + docs
|
||||
|
||||
Acceptance for this milestone is that thinking traces are visible while a run streams, persisted in the output timeline after completion, and included when loading chat history. The output should show the thinking block before the assistant reply.
|
||||
|
||||
1. Update `src/app/page.tsx` to use the new helpers.
|
||||
|
||||
- Replace `formatThinkingTrace`/`extractThinkingTrace` with calls to `extractThinking` and `formatThinkingMarkdown`.
|
||||
- In the chat event handler (`event.event === "chat"`):
|
||||
- When `payload.state === "delta"`, if a thinking block is present, set `tile.thinkingTrace` to the raw thinking text (not truncated). Keep the tile status as `running`.
|
||||
- When `payload.state === "final"`, extract thinking from the final message (or use any pending `tile.thinkingTrace`), format it with `formatThinkingMarkdown`, and `appendOutput` it before appending the assistant's final text. Then clear `thinkingTrace` and `streamText` as today.
|
||||
- In `buildHistoryLines`, for each assistant message, extract thinking and insert the formatted thinking markdown line before the assistant response line in the returned `lines` array. Keep user lines unchanged.
|
||||
|
||||
2. Update `src/features/canvas/components/AgentTile.tsx` to render live thinking as markdown.
|
||||
|
||||
- Render the `thinkingTrace` block using `ReactMarkdown` so multi-line thinking and markdown formatting appear correctly.
|
||||
- Keep the existing visual styling (amber block) but remove truncation logic; the content should be the full thinking trace as sent by the model.
|
||||
|
||||
3. Update `README.md` with a short section explaining that thinking traces are displayed when the model sends `content[]` entries of type `thinking` or `<thinking>` blocks, and that the thinking level selector controls whether those traces appear.
|
||||
|
||||
4. Manual verification:
|
||||
|
||||
- Run `npm run dev`.
|
||||
- Open the UI, select an agent tile, set thinking to `low` or `medium`, and send a short message.
|
||||
- Confirm that while the run is streaming, a "thinking" block appears in the tile output, and once the response completes the thinking block remains in the output history above the assistant reply.
|
||||
- Reload the page and confirm the thinking block persists via history loading.
|
||||
|
||||
5. Commit:
|
||||
|
||||
git add -A
|
||||
git commit -m "Milestone 2: Surface thinking traces in chat tiles"
|
||||
|
||||
## Validation and Acceptance
|
||||
|
||||
Run unit tests and verify an end-to-end chat:
|
||||
|
||||
- Unit tests: `npm run test -- tests/unit/extractThinking.test.ts` should pass.
|
||||
- Manual UI check: start the dev server and confirm thinking blocks appear live and persist after completion and reload.
|
||||
|
||||
The change is accepted when an agent run shows the thinking trace as a distinct block before the assistant message, and history reloads show the same thinking traces from `chat.history`.
|
||||
|
||||
## Idempotence and Recovery
|
||||
|
||||
All steps are safe to rerun. If a test or manual check fails, revert the latest commit, adjust the helper or UI wiring, and rerun the same commands. No persistent data migrations are required; only UI rendering changes and parsing utilities are added.
|
||||
|
||||
## Artifacts and Notes
|
||||
|
||||
- Example thinking payload (from session log):
|
||||
|
||||
{ "type": "thinking", "thinking": "**Running initial repo listing**", "thinkingSignature": "..." }
|
||||
|
||||
- Expected output ordering in a tile after completion:
|
||||
|
||||
_Running initial repo listing_
|
||||
|
||||
<assistant response>
|
||||
|
||||
- Test run:
|
||||
|
||||
npm run test -- tests/unit/extractThinking.test.ts
|
||||
PASS tests/unit/extractThinking.test.ts (6 tests)
|
||||
|
||||
Plan update note: 2026-01-27 -- added wheel handling so selected tile output scrolls without page scroll; changed completed traces to render as a single collapsible "Thinking" block and updated README wording.
|
||||
|
||||
## Interfaces and Dependencies
|
||||
|
||||
- `src/lib/text/extractThinking.ts`
|
||||
- `extractThinking(message: unknown): string | null`
|
||||
- `formatThinkingMarkdown(text: string): string`
|
||||
|
||||
- `src/app/page.tsx`
|
||||
- Use `extractThinking` and `formatThinkingMarkdown` in `buildHistoryLines` and chat event handling.
|
||||
|
||||
- `src/features/canvas/components/AgentTile.tsx`
|
||||
- Render `thinkingTrace` via `ReactMarkdown` inside the existing styled block.
|
||||
|
||||
No new external dependencies are required.
|
||||
@@ -1,97 +0,0 @@
|
||||
# Replace the custom canvas with the ReactFlow-based canvas used in crabwalk
|
||||
|
||||
This ExecPlan is a living document. The sections `Progress`, `Surprises & Discoveries`, `Decision Log`, and `Outcomes & Retrospective` must be kept up to date as work proceeds.
|
||||
|
||||
This plan is governed by `/Users/georgepickett/clawdbot-agent-ui/.agent/PLANS.md` and must be maintained in accordance with it.
|
||||
|
||||
## Purpose / Big Picture
|
||||
|
||||
The current canvas pan/zoom behavior feels inconsistent and “buggy” because it is custom, uses CSS zoom, and depends on device-specific wheel semantics. After this change, the canvas will behave like the crabwalk monitor canvas: smooth, predictable pan/zoom, reliable zoom-to-fit, and consistent minimap/controls interactions. A user will be able to zoom with the mouse wheel, drag the background to pan, resize tiles cleanly, and see the zoom readout update accurately. This can be verified by running the app, interacting with the canvas, and running the updated Playwright tests that simulate wheel zoom, drag pan, and tile resize.
|
||||
|
||||
## Progress
|
||||
|
||||
- [x] (2026-01-27 00:00Z) Drafted ExecPlan based on the crabwalk ReactFlow implementation.
|
||||
- [x] (2026-01-28 00:00Z) Replace the custom canvas with ReactFlow and keep zoom readout, tile drag, and tile resize working.
|
||||
- [x] (2026-01-28 00:00Z) Update automated tests to match the new canvas behavior and verify zoom, pan, and resize.
|
||||
|
||||
## Surprises & Discoveries
|
||||
|
||||
- Observation: crabwalk’s monitor canvas uses `@xyflow/react` (ReactFlow) with `Controls`, `MiniMap`, and `Background`, and relies on its built-in pan/zoom behavior rather than custom math. The package exports `NodeResizer`, which we can use for resize handles inside custom nodes.
|
||||
Evidence: `/Users/georgepickett/crabwalk/src/components/monitor/ActionGraph.tsx` plus `/Users/georgepickett/crabwalk/node_modules/@xyflow/react/dist/esm/additional-components/NodeResizer/types.d.ts`.
|
||||
- Observation: ReactFlow uses a named `ReactFlow` export (no default), and forcing selection via node props can cause update loops; letting ReactFlow own selection and syncing via callbacks avoided the runtime error.
|
||||
Evidence: `/Users/georgepickett/clawdbot-agent-ui/src/features/canvas/components/CanvasFlow.tsx` runtime error overlay during early test runs.
|
||||
|
||||
## Decision Log
|
||||
|
||||
- Decision: Use `@xyflow/react` (same library and defaults as crabwalk) for the canvas engine, and wire our tiles into custom ReactFlow nodes with `NodeResizer` for resizing.
|
||||
Rationale: This directly copies crabwalk’s proven pan/zoom behavior and removes our custom CSS-zoom transform path, which is the likely source of inconsistent UX.
|
||||
Date/Author: 2026-01-27, Codex.
|
||||
|
||||
## Outcomes & Retrospective
|
||||
|
||||
ReactFlow now drives the canvas with built-in pan/zoom, minimap, and controls; tile drag and resize update project state, and the header zoom readout stays in sync. The e2e suite now covers wheel zoom, background pan, and NodeResizer-based resizing on the new canvas.
|
||||
|
||||
## Context and Orientation
|
||||
|
||||
The current canvas lives in `/Users/georgepickett/clawdbot-agent-ui/src/app/page.tsx`, with the rendering handled by `/Users/georgepickett/clawdbot-agent-ui/src/features/canvas/components/CanvasViewport.tsx` and `/Users/georgepickett/clawdbot-agent-ui/src/features/canvas/components/CanvasMinimap.tsx`. The transform math lives in `/Users/georgepickett/clawdbot-agent-ui/src/features/canvas/lib/transform.ts`, and the tile UI is in `/Users/georgepickett/clawdbot-agent-ui/src/features/canvas/components/AgentTile.tsx`. The canvas zoom/offset state is stored in `/Users/georgepickett/clawdbot-agent-ui/src/features/canvas/state/store.tsx` as `CanvasTransform` with `zoom`, `offsetX`, and `offsetY`. The crabwalk canvas we want to copy is implemented with ReactFlow in `/Users/georgepickett/crabwalk/src/components/monitor/ActionGraph.tsx` using `ReactFlow`, `Controls`, `MiniMap`, and `Background`, with pan/zoom handled by the library and no custom CSS zoom logic.
|
||||
|
||||
In this repo, tile positions and sizes are stored in the project state and persisted via `/Users/georgepickett/clawdbot-agent-ui/src/lib/projects/client`. Any new canvas implementation must continue to update tile position and size in state so existing persistence and UI flows remain intact. The zoom readout in the header (`/Users/georgepickett/clawdbot-agent-ui/src/features/canvas/components/HeaderBar.tsx`) is currently derived from `state.canvas.zoom`, so the new canvas must keep that value updated as the user pans and zooms.
|
||||
|
||||
## Plan of Work
|
||||
|
||||
First, add ReactFlow (`@xyflow/react`) to the UI dependencies and import its base styles so the canvas engine renders and handles pointer interactions correctly. Next, replace the custom `CanvasViewport` and `CanvasMinimap` with a new `CanvasFlow` component that wraps a `ReactFlow` instance, matching crabwalk’s configuration (`Controls`, `MiniMap`, `Background`, `fitView`, `minZoom`, `maxZoom`). Then adapt the tile UI to render as a custom ReactFlow node: remove the absolute positioning from `AgentTile` and instead drive width and height via the ReactFlow node style, while adding a `NodeResizer` to keep tile resizing functional. Wire node drag and resize updates to dispatch tile position/size updates into the canvas store. Finally, update the zoom controls and readout to use the ReactFlow viewport state (x, y, zoom), and update or add Playwright tests to verify wheel zoom, drag pan, and resize behavior on the new canvas.
|
||||
|
||||
## Concrete Steps
|
||||
|
||||
Work in `/Users/georgepickett/clawdbot-agent-ui`.
|
||||
|
||||
1) Add ReactFlow as a dependency and include its stylesheet. Update `package.json` to include `@xyflow/react` (use the same major version as crabwalk, `^12.10.0`), then add an import of `@xyflow/react/dist/style.css` in a global entry such as `/Users/georgepickett/clawdbot-agent-ui/src/app/globals.css` or a new canvas component that is guaranteed to be loaded on the client.
|
||||
|
||||
2) Create a new canvas component, for example `/Users/georgepickett/clawdbot-agent-ui/src/features/canvas/components/CanvasFlow.tsx`, that wraps `ReactFlowProvider` and `ReactFlow` and exposes the same outward props as the current `CanvasViewport`, plus a new `onInit` callback to pass the `ReactFlowInstance` back up to `/Users/georgepickett/clawdbot-agent-ui/src/app/page.tsx`. Use `nodeTypes` to register a custom node component (see next step). Configure `ReactFlow` similarly to crabwalk: `fitView`, `fitViewOptions={{ padding: 0.2 }}`, `minZoom={0.1}`, `maxZoom={2}`, and include `Background`, `Controls`, and `MiniMap` so the core interaction model matches crabwalk.
|
||||
|
||||
3) Add a custom node component for tiles, for example `/Users/georgepickett/clawdbot-agent-ui/src/features/canvas/components/AgentTileNode.tsx`. This component should render the existing `AgentTile` UI, but without absolute left/top positioning. Use the `NodeResizer` from `@xyflow/react` inside this component to provide resize handles. Configure `NodeResizer` with `minWidth` and `minHeight` matching the existing `MIN_SIZE` (560 x 440), and wire `onResizeEnd` to dispatch a tile size update into the store using the new width and height provided by `NodeResizer`. Ensure the root element still includes `data-tile` so existing tests can find it.
|
||||
|
||||
4) Refactor `/Users/georgepickett/clawdbot-agent-ui/src/features/canvas/components/AgentTile.tsx` so it no longer sets `left` and `top` inline styles. It should instead rely on the ReactFlow node wrapper for position and only set `width` and `height` based on `tile.size`. Remove the custom drag handlers (`handleDragStart`) so ReactFlow is the only drag system, and add a stable drag handle element on the tile header (for example by adding a `data-drag-handle` attribute) so ReactFlow can be configured to drag only from the header without interfering with text inputs.
|
||||
|
||||
5) Replace usage of `CanvasViewport` and `CanvasMinimap` in `/Users/georgepickett/clawdbot-agent-ui/src/app/page.tsx` with the new `CanvasFlow` component. Keep the existing `viewportRef` for size measurement but wire `CanvasFlow` to expose the ReactFlow instance via `onInit`. Update the zoom handlers (`handleZoomIn`, `handleZoomOut`, `handleZoomReset`, `handleZoomToFit`) to call ReactFlow’s `zoomIn`, `zoomOut`, `setViewport`, and `fitView` respectively, and then update the canvas store’s `zoom`, `offsetX`, and `offsetY` using `useOnViewportChange` (or `onMove`) so the header readout remains accurate.
|
||||
|
||||
6) Update `CanvasFlow` to keep store state and ReactFlow in sync. Use `onNodesChange` (and/or `onNodeDragStop`) to dispatch tile position updates when a node finishes dragging, `onNodeClick` or `onSelectionChange` to update the selected tile, and `onPaneClick` to clear selection. Use `onMove` or `useOnViewportChange` to call `onUpdateTransform({ zoom, offsetX: x, offsetY: y })` whenever the viewport changes, so `state.canvas` continues to drive readouts and placement calculations.
|
||||
|
||||
7) Update or add Playwright tests to reflect the new canvas behavior. The existing tests in `/Users/georgepickett/clawdbot-agent-ui/tests/e2e/canvas-zoom-pan.spec.ts` should continue to verify that a wheel event changes the zoom readout and affects tile bounds, but adjust event payloads or selectors if ReactFlow introduces different DOM structure. Add a new test that drags the canvas background to pan and asserts that a tile’s bounding box moves relative to the viewport. Add a resize test that drags a NodeResizer handle and asserts the tile bounding box width/height changes. Keep these tests focused on user-visible behavior rather than implementation details.
|
||||
|
||||
## Validation and Acceptance
|
||||
|
||||
Acceptance is met when the canvas behaves like crabwalk’s: wheel zoom is smooth and consistent, drag-to-pan works across devices, tiles drag and resize without jitter, the minimap and controls are functional, and the zoom readout updates as the viewport changes.
|
||||
|
||||
For each milestone, follow this verification workflow.
|
||||
|
||||
Milestone 1: ReactFlow canvas with draggable tiles and viewport syncing.
|
||||
|
||||
1. Tests to write: Update `/Users/georgepickett/clawdbot-agent-ui/tests/e2e/canvas-zoom-pan.spec.ts` to assert the zoom readout changes after a wheel event on the canvas root, and add a new test `pan-drag-shifts-tiles` in the same file that drags the canvas background and verifies the tile’s bounding box moves relative to its previous position.
|
||||
2. Implementation: Add `@xyflow/react`, create `CanvasFlow`, hook it into `page.tsx`, and refactor `AgentTile` to be usable in a ReactFlow node. Use `onMove` (or `useOnViewportChange`) to keep `state.canvas` in sync.
|
||||
3. Verification: Run `npm run e2e -- tests/e2e/canvas-zoom-pan.spec.ts` and confirm the new tests fail before changes and pass after the ReactFlow integration is complete.
|
||||
4. Commit: After tests pass, commit with the message `Milestone 1: ReactFlow canvas and viewport sync`.
|
||||
|
||||
Milestone 2: Tile resizing via NodeResizer and persistence.
|
||||
|
||||
1. Tests to write: Add `resize-handle-updates-tile-size` to `/Users/georgepickett/clawdbot-agent-ui/tests/e2e/canvas-zoom-pan.spec.ts` (or a new `canvas-resize.spec.ts`) that drags a resize handle on a tile and asserts its bounding box width and height change by at least a small threshold.
|
||||
2. Implementation: Add `AgentTileNode` with `NodeResizer`, wire `onResizeEnd` to update tile size in the store, and ensure tile size persists on re-render by mapping node width/height from `tile.size`.
|
||||
3. Verification: Run `npm run e2e -- tests/e2e/canvas-zoom-pan.spec.ts` (or the new file) and confirm the resize test fails before the change and passes after.
|
||||
4. Commit: After tests pass, commit with the message `Milestone 2: Tile resizing with NodeResizer`.
|
||||
|
||||
## Idempotence and Recovery
|
||||
|
||||
These steps are safe to run multiple times because dependency changes are additive and all code edits are deterministic. If ReactFlow integration causes regressions, revert to the previous commit boundary at the end of each milestone, or temporarily re-enable `CanvasViewport` by restoring its usage in `page.tsx`. If a test becomes flaky due to interaction timing, increase Playwright’s drag step delays or wait conditions rather than disabling the test.
|
||||
|
||||
## Artifacts and Notes
|
||||
|
||||
When validating, capture short command outputs in this section (for example, the final lines of `npm run e2e` showing the pass/fail summary). Keep any terminal transcripts short and focused on confirming success.
|
||||
|
||||
- `npm run e2e -- tests/e2e/canvas-zoom-pan.spec.ts`
|
||||
- 4 passed (4.1s)
|
||||
|
||||
## Interfaces and Dependencies
|
||||
|
||||
Use `@xyflow/react` for the canvas engine, including `ReactFlow`, `ReactFlowProvider`, `Controls`, `MiniMap`, `Background`, `NodeResizer`, and `useOnViewportChange` or `onMove` events to observe viewport changes. The custom node component (`AgentTileNode`) should accept `data` containing the `AgentTile` plus callbacks for selection, move, resize, rename, and send actions. The `CanvasFlow` component should accept `tiles`, `selectedTileId`, and callback props mirroring the current `CanvasViewport` API, and should expose a `ReactFlowInstance` through `onInit` so the header can trigger `zoomIn`, `zoomOut`, `setViewport`, and `fitView`.
|
||||
|
||||
Changes made: Initial plan drafted based on crabwalk’s ReactFlow canvas and the current clawdbot-agent-ui canvas structure.
|
||||
@@ -1,141 +0,0 @@
|
||||
# Avatar-first agent tiles with options menu
|
||||
|
||||
This ExecPlan is a living document. The sections `Progress`, `Surprises & Discoveries`, `Decision Log`, and `Outcomes & Retrospective` must be kept up to date as work proceeds.
|
||||
|
||||
This plan must be maintained in accordance with the repository planning guide at `.agent/PLANS.md`.
|
||||
|
||||
## Purpose / Big Picture
|
||||
|
||||
After this change, creating a new agent shows a compact, avatar-first tile: the agent name appears above a circular avatar, and a single “Send a command” input sits below. The transcript window stays hidden until the first message is sent; then the conversation and thinking traces appear below the avatar. Model and thinking controls move into a small gear options panel, and the running/idle status plus delete action move out of the main view to reduce clutter. A user can start a new agent and immediately see a face and a single input box, and after sending a command they see the transcript panel appear beneath the avatar.
|
||||
|
||||
## Progress
|
||||
|
||||
- [x] (2026-01-28 00:00Z) Authored initial ExecPlan for avatar-first tiles and options menu.
|
||||
- [x] (2026-01-28 17:35Z) Milestone 1: Add Multiavatar dependency, avatar helpers, and unit tests.
|
||||
- [x] (2026-01-28 18:05Z) Milestone 2: Restructure AgentTile layout, add options menu, update default tile sizing, and add e2e coverage.
|
||||
|
||||
## Surprises & Discoveries
|
||||
|
||||
- Observation: Next dev server (Turbopack) could not resolve the symlinked `@multiavatar/multiavatar` package from `file:../Multiavatar`.
|
||||
Evidence: Next dev overlay showed “Module not found: Can't resolve '@multiavatar/multiavatar'” when loading the canvas.
|
||||
|
||||
## Decision Log
|
||||
|
||||
- Decision: Use Multiavatar seeded by `agentId` and strip the built-in environment circle so the UI owns the circular container.
|
||||
Rationale: `agentId` stays stable across renames, and a single UI circle avoids double backgrounds.
|
||||
Date/Author: 2026-01-28 / Codex
|
||||
|
||||
- Decision: Hide the transcript panel until there is output or streaming text.
|
||||
Rationale: This matches the “face + input only” requirement for new agents and avoids a large empty chat window.
|
||||
Date/Author: 2026-01-28 / Codex
|
||||
|
||||
- Decision: Make model/thinking selectors available in a gear options panel and relocate status and delete actions there.
|
||||
Rationale: The user requested an options menu and a rethink of the status/delete placement; there is no backend stop/run toggle today, so status stays read-only.
|
||||
Date/Author: 2026-01-28 / Codex
|
||||
|
||||
- Decision: Switch to the GitHub-hosted `@multiavatar/multiavatar` package and import from `@multiavatar/multiavatar/esm`.
|
||||
Rationale: Eliminates the local file dependency while keeping a resolvable ESM entry for Next/Turbopack.
|
||||
Date/Author: 2026-01-28 / Codex
|
||||
|
||||
## Outcomes & Retrospective
|
||||
|
||||
Implemented avatar-first tiles with a gear options panel, added Multiavatar helper/test coverage, and validated behavior with Playwright. Remaining follow-ups are optional UX tweaks (e.g., options panel behavior) based on user feedback.
|
||||
|
||||
## Context and Orientation
|
||||
|
||||
The agent canvas UI renders tiles via `src/features/canvas/components/AgentTile.tsx`, which is wrapped by `src/features/canvas/components/AgentTileNode.tsx` and placed on the canvas in `src/features/canvas/components/CanvasFlow.tsx`. Tile runtime state lives in `src/features/canvas/state/store.tsx` and is created from the persisted `ProjectTile` data defined in `src/lib/projects/types.ts`. New tiles are created by the API route `src/app/api/projects/[projectId]/tiles/route.ts`, which also defines the default tile size and name. The top-level page uses `src/app/page.tsx` to connect the canvas to gateway events and to send messages; it appends user and assistant output lines to each tile.
|
||||
|
||||
Avatar generation code is sourced from the GitHub-hosted `@multiavatar/multiavatar` package and wrapped by `src/lib/avatars/multiavatar.ts` to produce an SVG data URL for an `<img>` tag.
|
||||
|
||||
Playwright e2e tests live under `tests/e2e/`, and Vitest unit tests live under `tests/unit/`. Existing e2e tests already mock `/api/projects` to supply tiles.
|
||||
|
||||
## Plan of Work
|
||||
|
||||
First, add the Multiavatar dependency via GitHub, then create a small avatar helper module that turns a seed string into an SVG string and a safe data URL. Add a unit test to validate that the helper returns valid SVG and a data URL prefix so the build breaks early if the dependency is not wired correctly.
|
||||
|
||||
Next, refactor `AgentTile` into an avatar-first layout: name above avatar, input below, and a transcript window that only renders once there is output or streaming. Add a gear button that reveals model and thinking selectors plus the status indicator and delete action. Use simple, accessible HTML (button + popover or details/summary) and add minimal `aria-label`/`data-` hooks for Playwright. Update the minimum tile size and the API’s default tile size to match the compact layout. Finally, add a Playwright test that verifies the avatar, input, and options panel are present for a new tile and that the transcript panel is hidden initially.
|
||||
|
||||
## Concrete Steps
|
||||
|
||||
Work from the repo root `/Users/georgepickett/clawdbot-agent-ui`.
|
||||
|
||||
1. Ensure the Multiavatar build exists by checking `../Multiavatar/dist/esm/index.js`. If the file is missing, run `npm install` and `npm run build` inside `/Users/georgepickett/Multiavatar` to generate `dist/`.
|
||||
|
||||
2. Add the local dependency to `package.json` as `"@multiavatar/multiavatar": "file:../Multiavatar"`, then run `npm install` to update `package-lock.json`.
|
||||
|
||||
3. Create `src/lib/avatars/multiavatar.ts` with two exported functions: `buildAvatarSvg(seed: string): string` and `buildAvatarDataUrl(seed: string): string`. Validate that `seed` is a non-empty string and throw a clear error if not. Call `multiavatar(seed, true)` (no environment circle) and wrap the SVG in a `data:image/svg+xml;utf8,` URL with `encodeURIComponent`.
|
||||
|
||||
4. Add `tests/unit/multiavatar.test.ts` that asserts:
|
||||
- `buildAvatarSvg("Agent A")` starts with `<svg` and contains `</svg>`.
|
||||
- `buildAvatarDataUrl("Agent A")` starts with `data:image/svg+xml;utf8,` and includes an encoded `<svg` fragment.
|
||||
|
||||
5. Add a small presentational component `src/features/canvas/components/AgentAvatar.tsx` that accepts `seed`, `name`, and optional `size` props, uses `buildAvatarDataUrl`, and renders an `<img>` inside a circular container (`rounded-full`, `overflow-hidden`). Use `alt` text like `Avatar for ${name}` for testability.
|
||||
|
||||
6. Refactor `src/features/canvas/components/AgentTile.tsx`:
|
||||
- Replace the current header row and transcript-first layout with a vertical stack: name input (centered), avatar, input row, then transcript panel.
|
||||
- Keep the name editable with the same onBlur/onKeyDown logic, but style it as a centered label above the avatar.
|
||||
- Introduce a gear button (using `lucide-react` settings icon) that opens an options panel containing the Model selector, Thinking selector, a read-only status indicator, and a “Delete agent” button. This panel should be accessible (keyboard focusable) and should not interfere with tile dragging.
|
||||
- Remove the always-visible “No output yet.” message; instead, render the transcript panel only when there is output (`tile.outputLines.length > 0`), streaming text, or active thinking.
|
||||
- Keep existing output rendering logic, including thinking traces and streamed text, but position it in the new panel below the input.
|
||||
|
||||
7. Update `MIN_TILE_SIZE` in `AgentTile.tsx` and the default `size` in `src/app/api/projects/[projectId]/tiles/route.ts` to match the compact layout (for example, around 420x520). Ensure the new minimum still allows the transcript to be visible when present.
|
||||
|
||||
8. Add a Playwright test `tests/e2e/agent-tile-avatar.spec.ts` that:
|
||||
- Mocks `/api/projects` to return a store with one tile and no output lines.
|
||||
- Navigates to `/` and asserts the avatar image with alt text is visible and the “Send a command” input exists.
|
||||
- Opens the gear options panel and asserts the Model and Thinking selectors are visible.
|
||||
- Asserts that the transcript panel is not present when output lines are empty (use a `data-testid` on the transcript container if needed).
|
||||
|
||||
9. Run unit and e2e tests: `npm test` and `npm run e2e`. If Playwright requires the dev server, use the same workflow as existing e2e tests.
|
||||
|
||||
## Validation and Acceptance
|
||||
|
||||
The change is accepted when:
|
||||
|
||||
- Creating a new agent renders a tile with a centered name above a circular avatar and an input labeled “Send a command,” and no transcript window is visible until a message is sent.
|
||||
- After sending a command, the transcript window appears below the avatar and contains the user message and assistant output, with thinking traces formatted as before.
|
||||
- A gear options panel exists on the tile, containing Model and Thinking controls, a status indicator, and a delete action.
|
||||
- The avatar is generated from Multiavatar using a stable seed and displays consistently across reloads.
|
||||
|
||||
Milestone 1 verification workflow:
|
||||
|
||||
1. Tests to write: `tests/unit/multiavatar.test.ts` with assertions for `buildAvatarSvg` and `buildAvatarDataUrl`.
|
||||
2. Implementation: add the local dependency, implement `src/lib/avatars/multiavatar.ts`.
|
||||
3. Verification: run `npm test -- tests/unit/multiavatar.test.ts` and confirm it passes after failing before.
|
||||
4. Commit: `git commit -m "Milestone 1: add avatar helper and tests"`.
|
||||
|
||||
Milestone 2 verification workflow:
|
||||
|
||||
1. Tests to write: `tests/e2e/agent-tile-avatar.spec.ts` to assert avatar, input, options panel, and hidden transcript.
|
||||
2. Implementation: refactor `AgentTile`, add `AgentAvatar`, update tile sizes, and add necessary `aria-label`/`data-testid` hooks.
|
||||
3. Verification: run `npm run e2e -- tests/e2e/agent-tile-avatar.spec.ts` and confirm it passes; run `npm test` to ensure unit tests still pass.
|
||||
4. Commit: `git commit -m "Milestone 2: avatar-first tile layout and options menu"`.
|
||||
|
||||
## Idempotence and Recovery
|
||||
|
||||
All steps are safe to repeat. If `npm install` fails after adding the file dependency, remove the entry from `package.json`, run `npm install` to return to a clean state, then re-add the dependency once the Multiavatar build is confirmed. If UI changes cause layout regressions, revert the specific commit for the milestone and reapply the plan with adjusted sizes or layout choices.
|
||||
|
||||
## Artifacts and Notes
|
||||
|
||||
Expected unit test output excerpt:
|
||||
|
||||
✓ buildAvatarSvg returns svg
|
||||
✓ buildAvatarDataUrl returns data url
|
||||
|
||||
Expected e2e assertions:
|
||||
|
||||
- Avatar image is visible in the tile.
|
||||
- “Send a command” input is visible.
|
||||
- Options menu reveals Model and Thinking selectors.
|
||||
- Transcript container is absent when there is no output.
|
||||
|
||||
## Interfaces and Dependencies
|
||||
|
||||
Use the local `@multiavatar/multiavatar` package from `../Multiavatar` via a file dependency. Define a small helper module at `src/lib/avatars/multiavatar.ts` with:
|
||||
|
||||
export function buildAvatarSvg(seed: string): string
|
||||
export function buildAvatarDataUrl(seed: string): string
|
||||
|
||||
`buildAvatarSvg` must throw a descriptive error if `seed` is empty. `buildAvatarDataUrl` must return a `data:image/svg+xml;utf8,` URL that can be used in an `<img>` tag. The `AgentAvatar` component must accept `seed` and `name` props and render an image with `alt="Avatar for ${name}"`.
|
||||
|
||||
Plan update (2026-01-28): Documented the switch to the GitHub-hosted Multiavatar package and updated progress to reflect completed milestones.
|
||||
-186
@@ -1,186 +0,0 @@
|
||||
# Codex Execution Plans (ExecPlans):
|
||||
|
||||
This document describes the requirements for an execution plan ("ExecPlan"), a design document that a coding agent can follow to deliver a working feature or system change. Treat the reader as a complete beginner to this repository: they have only the current working tree and the single ExecPlan file you provide. There is no memory of prior plans and no external context.
|
||||
|
||||
## How to use ExecPlans and PLANS.md
|
||||
|
||||
When authoring an executable specification (ExecPlan), follow PLANS.md _to the letter_. If it is not in your context, refresh your memory by reading the entire PLANS.md file. Be thorough in reading (and re-reading) source material to produce an accurate specification. When creating a spec, start from the skeleton and flesh it out as you do your research.
|
||||
|
||||
When implementing an executable specification (ExecPlan), do not prompt the user for "next steps"; simply proceed to the next milestone. Keep all sections up to date, add or split entries in the list at every stopping point to affirmatively state the progress made and next steps. Resolve ambiguities autonomously. For each milestone, write failing tests first (when tests are specified), implement until all tests pass, then commit the verified changes before proceeding to the next milestone. If the repo uses Beads, use `br ready` to select work and update issue status as you progress.
|
||||
|
||||
When discussing an executable specification (ExecPlan), record decisions in a log in the spec for posterity; it should be unambiguously clear why any change to the specification was made. ExecPlans are living documents, and it should always be possible to restart from _only_ the ExecPlan and no other work.
|
||||
|
||||
When researching a design with challenging requirements or significant unknowns, use milestones to implement proof of concepts, "toy implementations", etc., that allow validating whether the user's proposal is feasible. Read the source code of libraries by finding or acquiring them, research deeply, and include prototypes to guide a fuller implementation.
|
||||
|
||||
## Requirements
|
||||
|
||||
NON-NEGOTIABLE REQUIREMENTS:
|
||||
|
||||
* Every ExecPlan must be fully self-contained. Self-contained means that in its current form it contains all knowledge and instructions needed for a novice to succeed.
|
||||
* Every ExecPlan is a living document. Contributors are required to revise it as progress is made, as discoveries occur, and as design decisions are finalized. Each revision must remain fully self-contained.
|
||||
* Every ExecPlan must enable a complete novice to implement the feature end-to-end without prior knowledge of this repo.
|
||||
* Every ExecPlan must produce a demonstrably working behavior, not merely code changes to "meet a definition".
|
||||
* Every ExecPlan must define every term of art in plain language or do not use it.
|
||||
|
||||
Purpose and intent come first. Begin by explaining, in a few sentences, why the work matters from a user's perspective: what someone can do after this change that they could not do before, and how to see it working. Then guide the reader through the exact steps to achieve that outcome, including what to edit, what to run, and what they should observe.
|
||||
|
||||
The agent executing your plan can list files, read files, search, run the project, and run tests. It does not know any prior context and cannot infer what you meant from earlier milestones. Repeat any assumption you rely on. Do not point to external blogs or docs; if knowledge is required, embed it in the plan itself in your own words. If an ExecPlan builds upon a prior ExecPlan and that file is checked in, incorporate it by reference. If it is not, you must include all relevant context from that plan.
|
||||
|
||||
## Formatting
|
||||
|
||||
Format and envelope are simple and strict. Each ExecPlan must be one single fenced code block labeled as `md` that begins and ends with triple backticks. Do not nest additional triple-backtick code fences inside; when you need to show commands, transcripts, diffs, or code, present them as indented blocks within that single fence. Use indentation for clarity rather than code fences inside an ExecPlan to avoid prematurely closing the ExecPlan's code fence. Use two newlines after every heading, use # and ## and so on, and correct syntax for ordered and unordered lists.
|
||||
|
||||
When writing an ExecPlan to a Markdown (.md) file where the content of the file *is only* the single ExecPlan, you should omit the triple backticks.
|
||||
|
||||
Write in plain prose. Prefer sentences over lists. Avoid checklists, tables, and long enumerations unless brevity would obscure meaning. Checklists are permitted only in the `Progress` section, where they are mandatory. Narrative sections must remain prose-first.
|
||||
|
||||
## Guidelines
|
||||
|
||||
Self-containment and plain language are paramount. If you introduce a phrase that is not ordinary English ("daemon", "middleware", "RPC gateway", "filter graph"), define it immediately and remind the reader how it manifests in this repository (for example, by naming the files or commands where it appears). Do not say "as defined previously" or "according to the architecture doc." Include the needed explanation here, even if you repeat yourself.
|
||||
|
||||
Avoid common failure modes. Do not rely on undefined jargon. Do not describe "the letter of a feature" so narrowly that the resulting code compiles but does nothing meaningful. Do not outsource key decisions to the reader. When ambiguity exists, resolve it in the plan itself and explain why you chose that path. Err on the side of over-explaining user-visible effects and under-specifying incidental implementation details.
|
||||
|
||||
Anchor the plan with observable outcomes. State what the user can do after implementation, the commands to run, and the outputs they should see. Acceptance should be phrased as behavior a human can verify ("after starting the server, navigating to [http://localhost:8080/health](http://localhost:8080/health) returns HTTP 200 with body OK") rather than internal attributes ("added a HealthCheck struct"). If a change is internal, explain how its impact can still be demonstrated (for example, by running tests that fail before and pass after, and by showing a scenario that uses the new behavior).
|
||||
|
||||
Specify repository context explicitly. Name files with full repository-relative paths, name functions and modules precisely, and describe where new files should be created. If touching multiple areas, include a short orientation paragraph that explains how those parts fit together so a novice can navigate confidently. When running commands, show the working directory and exact command line. When outcomes depend on environment, state the assumptions and provide alternatives when reasonable.
|
||||
|
||||
Be idempotent and safe. Write the steps so they can be run multiple times without causing damage or drift. If a step can fail halfway, include how to retry or adapt. If a migration or destructive operation is necessary, spell out backups or safe fallbacks. Prefer additive, testable changes that can be validated as you go.
|
||||
|
||||
Validation is not optional. Include instructions to run tests, to start the system if applicable, and to observe it doing something useful. Describe comprehensive testing for any new features or capabilities. Include expected outputs and error messages so a novice can tell success from failure. Where possible, show how to prove that the change is effective beyond compilation (for example, through a small end-to-end scenario, a CLI invocation, or an HTTP request/response transcript). State the exact test commands appropriate to the project’s toolchain and how to interpret their results.
|
||||
|
||||
When specifying tests, prefer a test-first approach: describe which tests to write and what they should assert before describing the implementation. This allows the implementing agent to write failing tests first, then implement until the tests pass. Specify the test file paths, test function names, and the exact assertions expected. If the project has an existing test structure, follow its conventions.
|
||||
|
||||
Capture evidence. When your steps produce terminal output, short diffs, or logs, include them inside the single fenced block as indented examples. Keep them concise and focused on what proves success. If you need to include a patch, prefer file-scoped diffs or small excerpts that a reader can recreate by following your instructions rather than pasting large blobs.
|
||||
|
||||
## Milestones
|
||||
|
||||
Milestones are narrative, not bureaucracy. If you break the work into milestones, introduce each with a brief paragraph that describes the scope, what will exist at the end of the milestone that did not exist before, the commands to run, and the acceptance you expect to observe. Keep it readable as a story: goal, work, result, proof. Progress and milestones are distinct: milestones tell the story, progress tracks granular work. Both must exist. Never abbreviate a milestone merely for the sake of brevity, do not leave out details that could be crucial to a future implementation.
|
||||
|
||||
Each milestone must be independently verifiable and incrementally implement the overall goal of the execution plan.
|
||||
|
||||
## Verification and Test-Driven Milestones
|
||||
|
||||
Every milestone must include built-in verification steps that allow the implementing agent to confirm correctness without human intervention. Prefer test-driven development: write failing tests that define the milestone's acceptance criteria before writing the implementation. The milestone is not complete until all tests pass.
|
||||
|
||||
When designing a milestone, follow this verification pattern:
|
||||
|
||||
1. Define the acceptance criteria as concrete, observable behaviors.
|
||||
2. Write tests (unit, integration, or end-to-end as appropriate) that exercise these behaviors. Run them to confirm they fail for the expected reasons.
|
||||
3. Implement the feature or change.
|
||||
4. Run the tests again. The milestone is complete only when all tests pass and any other validation steps succeed.
|
||||
5. After all tests pass, the implementing agent is permitted (and encouraged) to commit the changes with a clear commit message describing the milestone completed.
|
||||
|
||||
If tests are not feasible for a particular milestone (e.g., infrastructure setup, configuration changes, or exploratory prototypes), specify alternative verification steps: commands to run, outputs to observe, or states to confirm. The key requirement is that the agent can autonomously verify success without asking for human confirmation.
|
||||
|
||||
Commits should be frequent and atomic. Each milestone that passes verification should be committed before proceeding to the next. This creates a clean history of incremental progress and allows safe rollback if later milestones encounter issues. The commit message should reference the milestone and summarize what was achieved.
|
||||
|
||||
## Issue Tracking with Beads
|
||||
|
||||
ExecPlans integrate with Beads (`br`) for local issue tracking. When a repo has Beads initialized (`.beads/` directory exists), use it to track milestones as issues.
|
||||
|
||||
When authoring an ExecPlan, create a Beads issue for each milestone: `br create "Milestone N: <title>" --type task --priority <0-4> --description "<scope and acceptance criteria>"`. Use `br dep add <child> <parent>` to express milestone dependencies. Record the issue IDs in the Progress section.
|
||||
|
||||
When implementing, use `br ready --json` to select the next unblocked milestone. Claim it with `br update <id> --status in_progress`. After verification passes, close it with `br close <id> --reason "Tests pass, committed"`. Run `br sync --flush-only` before committing to include the issue state in git history.
|
||||
|
||||
If Beads is not initialized or the user has not requested issue tracking, skip these steps.
|
||||
|
||||
## Living plans and design decisions
|
||||
|
||||
* ExecPlans are living documents. As you make key design decisions, update the plan to record both the decision and the thinking behind it. Record all decisions in the `Decision Log` section.
|
||||
* ExecPlans must contain and maintain a `Progress` section, a `Surprises & Discoveries` section, a `Decision Log`, and an `Outcomes & Retrospective` section. These are not optional.
|
||||
* When you discover optimizer behavior, performance tradeoffs, unexpected bugs, or inverse/unapply semantics that shaped your approach, capture those observations in the `Surprises & Discoveries` section with short evidence snippets (test output is ideal).
|
||||
* If you change course mid-implementation, document why in the `Decision Log` and reflect the implications in `Progress`. Plans are guides for the next contributor as much as checklists for you.
|
||||
* At completion of a major task or the full plan, write an `Outcomes & Retrospective` entry summarizing what was achieved, what remains, and lessons learned.
|
||||
|
||||
# Prototyping milestones and parallel implementations
|
||||
|
||||
It is acceptable—-and often encouraged—-to include explicit prototyping milestones when they de-risk a larger change. Examples: adding a low-level operator to a dependency to validate feasibility, or exploring two composition orders while measuring optimizer effects. Keep prototypes additive and testable. Clearly label the scope as “prototyping”; describe how to run and observe results; and state the criteria for promoting or discarding the prototype.
|
||||
|
||||
Prefer additive code changes followed by subtractions that keep tests passing. Parallel implementations (e.g., keeping an adapter alongside an older path during migration) are fine when they reduce risk or enable tests to continue passing during a large migration. Describe how to validate both paths and how to retire one safely with tests. When working with multiple new libraries or feature areas, consider creating spikes that evaluate the feasibility of these features _independently_ of one another, proving that the external library performs as expected and implements the features we need in isolation.
|
||||
|
||||
## Skeleton of a Good ExecPlan
|
||||
|
||||
# <Short, action-oriented description>
|
||||
|
||||
This ExecPlan is a living document. The sections `Progress`, `Surprises & Discoveries`, `Decision Log`, and `Outcomes & Retrospective` must be kept up to date as work proceeds.
|
||||
|
||||
If PLANS.md file is checked into the repo, reference the path to that file here from the repository root and note that this document must be maintained in accordance with PLANS.md.
|
||||
|
||||
## Purpose / Big Picture
|
||||
|
||||
Explain in a few sentences what someone gains after this change and how they can see it working. State the user-visible behavior you will enable.
|
||||
|
||||
## Progress
|
||||
|
||||
Use a list with checkboxes to summarize granular steps. Every stopping point must be documented here, even if it requires splitting a partially completed task into two (“done” vs. “remaining”). This section must always reflect the actual current state of the work.
|
||||
|
||||
- [x] (2025-10-01 13:00Z) Example completed step. [BEAD-001]
|
||||
- [ ] Example incomplete step. [BEAD-002]
|
||||
- [ ] Example partially completed step (completed: X; remaining: Y). [BEAD-003]
|
||||
|
||||
Use timestamps to measure rates of progress. If using Beads, include the issue ID in brackets after each step.
|
||||
|
||||
## Surprises & Discoveries
|
||||
|
||||
Document unexpected behaviors, bugs, optimizations, or insights discovered during implementation. Provide concise evidence.
|
||||
|
||||
- Observation: …
|
||||
Evidence: …
|
||||
|
||||
## Decision Log
|
||||
|
||||
Record every decision made while working on the plan in the format:
|
||||
|
||||
- Decision: …
|
||||
Rationale: …
|
||||
Date/Author: …
|
||||
|
||||
## Outcomes & Retrospective
|
||||
|
||||
Summarize outcomes, gaps, and lessons learned at major milestones or at completion. Compare the result against the original purpose.
|
||||
|
||||
## Context and Orientation
|
||||
|
||||
Describe the current state relevant to this task as if the reader knows nothing. Name the key files and modules by full path. Define any non-obvious term you will use. Do not refer to prior plans.
|
||||
|
||||
## Plan of Work
|
||||
|
||||
Describe, in prose, the sequence of edits and additions. For each edit, name the file and location (function, module) and what to insert or change. Keep it concrete and minimal.
|
||||
|
||||
## Concrete Steps
|
||||
|
||||
State the exact commands to run and where to run them (working directory). When a command generates output, show a short expected transcript so the reader can compare. This section must be updated as work proceeds.
|
||||
|
||||
## Validation and Acceptance
|
||||
|
||||
Describe how to start or exercise the system and what to observe. Phrase acceptance as behavior, with specific inputs and outputs. If tests are involved, say "run <project’s test command> and expect <N> passed; the new test <name> fails before the change and passes after>".
|
||||
|
||||
For each milestone, specify the verification workflow:
|
||||
1. Tests to write: List the test file paths, test function names, and assertions. These tests should be written first and must fail before implementation.
|
||||
2. Implementation: Describe the changes to make.
|
||||
3. Verification: Run the tests. The milestone is complete only when all tests pass.
|
||||
4. Commit: After verification passes, commit the changes with a message referencing the milestone.
|
||||
|
||||
Example: "Write test_user_creation in tests/test_users.py that asserts a 201 response with user ID. Run pytest tests/test_users.py -k test_user_creation and confirm it fails. Implement the endpoint. Run the test again and confirm it passes. Commit with message 'Milestone 1: Add user creation endpoint'."
|
||||
|
||||
## Idempotence and Recovery
|
||||
|
||||
If steps can be repeated safely, say so. If a step is risky, provide a safe retry or rollback path. Keep the environment clean after completion.
|
||||
|
||||
## Artifacts and Notes
|
||||
|
||||
Include the most important transcripts, diffs, or snippets as indented examples. Keep them concise and focused on what proves success.
|
||||
|
||||
## Interfaces and Dependencies
|
||||
|
||||
Be prescriptive. Name the libraries, modules, and services to use and why. Specify the types, traits/interfaces, and function signatures that must exist at the end of the milestone. Prefer stable names and paths such as `crate::module::function` or `package.submodule.Interface`. E.g.:
|
||||
|
||||
In crates/foo/planner.rs, define:
|
||||
|
||||
pub trait Planner {
|
||||
fn plan(&self, observed: &Observed) -> Vec<Action>;
|
||||
}
|
||||
|
||||
If you follow the guidance above, a single, stateless agent -- or a human novice -- can read your ExecPlan from top to bottom and produce a working, observable result. That is the bar: SELF-CONTAINED, SELF-SUFFICIENT, NOVICE-GUIDING, OUTCOME-FOCUSED.
|
||||
|
||||
When you revise a plan, you must ensure your changes are comprehensively reflected across all sections, including the living document sections, and you must write a note at the bottom of the plan describing the change and the reason why. ExecPlans must describe not just the what but the why for almost everything.
|
||||
@@ -1,5 +0,0 @@
|
||||
GatewayClient (src/lib/gateway/GatewayClient.ts) performs a one-shot connect and the hook in src/lib/gateway/useGatewayConnection.ts only auto-connects once, so any transient gateway failure requires a manual refresh. The UI in src/app/page.tsx only gates actions on the connection status and does not provide a structured retry UX.
|
||||
|
||||
Implement a reconnect strategy with exponential backoff and jitter, add a retrying status and lastError fields, and respect GatewayResponseError.retryAfterMs when present. Update HeaderBar or page UI to show connection state, last error, and a retry or cancel action; optionally persist gatewayUrl/token edits in local storage or via a small API endpoint so users do not retype on reload.
|
||||
|
||||
Acceptance criteria: on socket close, the client retries with backoff until connected or explicitly disconnected; the UI displays next retry timing and lets the user cancel; tests with fake timers validate backoff and stop behavior. Open question: should auto-reconnect be disabled after auth failures, and how do we detect that from gateway responses.
|
||||
@@ -1,5 +0,0 @@
|
||||
README.md is empty and USER.md contains only a preference, while critical operational details live in code, including config search paths in src/lib/clawdbot/config.ts, gateway defaults in src/lib/clawdbot/gateway.ts, env variables in src/lib/env.ts, and local state in src/app/api/projects/store.ts. This makes onboarding and troubleshooting slow and error-prone.
|
||||
|
||||
Write a concise README with setup steps, dev and test commands from package.json, required env/config, gateway expectations, and where workspace state is stored. Add a short troubleshooting section for missing config and gateway errors; optionally consolidate USER.md into README if it is not used elsewhere to reduce scattered docs.
|
||||
|
||||
Acceptance criteria: README explains how to run the UI with a local gateway, lists required env/config paths, and documents common failure modes with fixes. Open question: should docs standardize on .moltbot vs .clawdbot naming and which path is preferred for new installs.
|
||||
@@ -1,5 +0,0 @@
|
||||
The projects store is persisted as JSON at ~/.clawdbot/agent-canvas/projects.json (src/app/api/projects/store.ts) with manual parsing and minimal validation, and the UI auto-saves every 250ms in src/features/canvas/state/store.tsx without handling save failures. This setup risks silent corruption or data loss when parsing fails or concurrent writes occur.
|
||||
|
||||
Introduce a zod schema for ProjectsStore and tile structures, perform validation on load and save, and switch to atomic writes (write temp then rename) with a backup copy when parsing fails. Surface explicit error codes from src/app/api/projects/route.ts, and update the client store to pause autosave and show a blocking error with retry guidance. Touch src/app/api/projects/store.ts, src/app/api/projects/route.ts, src/lib/projects/types.ts, src/lib/http.ts, and src/features/canvas/state/store.tsx.
|
||||
|
||||
Acceptance criteria: invalid store files produce actionable errors while preserving the last good file; saves are atomic and verified; the UI shows a clear failure state and does not silently overwrite data; tests cover migration and validation. Open question: how should concurrent writes from multiple tabs/processes be resolved, and do we need a version conflict policy.
|
||||
@@ -1,5 +0,0 @@
|
||||
Test coverage is minimal: tests/unit only includes fetchJson and slugifyProjectName, and tests/e2e has a single canvas smoke test, while the main chat flow and polling logic live in src/app/page.tsx and the state machine in src/features/canvas/state/store.tsx. This leaves key behaviors (gateway errors, chat history reconciliation, and autosave) unverified.
|
||||
|
||||
Add unit tests for the canvas reducer actions, store migration/validation, and GatewayClient request/response handling. Expand Playwright e2e to cover create workspace, add tile, send a message with a mocked gateway, and error states; reuse tests/setup.ts for common mocks and fixtures.
|
||||
|
||||
Acceptance criteria: tests cover both success and failure cases for store load/save and gateway errors, and e2e runs deterministically with a mocked gateway. Open question: should the gateway be mocked via a local WebSocket test server or an in-browser stub to keep tests fast and reliable.
|
||||
@@ -1,5 +0,0 @@
|
||||
Model options and thinking levels are hard-coded in the UI (src/features/canvas/components/AgentTile.tsx) while the runtime settings are pushed via sessions.patch in src/app/page.tsx without any capability validation. This creates a mismatch risk when the gateway or config supports different models or disallows thinking settings, leading to confusing failures that only surface as output lines.
|
||||
|
||||
Add a gateway capabilities endpoint (for example /api/gateway/capabilities) that reports supported models and thinking levels, backed by config or a lightweight gateway handshake, and thread that data into the canvas UI. Update src/app/page.tsx to load capabilities once, store them in state, and make AgentTile render options dynamically with disabled or hidden unsupported options; add a fallback to show a "custom model" input if a tile already has a model not in the list.
|
||||
|
||||
Acceptance criteria: the model/thinking dropdowns always reflect the gateway capability response; unsupported selections are blocked with a clear inline message; and the UI still renders existing tiles with custom model values. Open question: should capabilities come from config only or a live gateway handshake, and how do we cache them to avoid blocking initial render.
|
||||
@@ -1,5 +0,0 @@
|
||||
State directory resolution is inconsistent: config lookup in src/lib/clawdbot/config.ts searches both ~/.moltbot and ~/.clawdbot, but agent workspace paths in src/lib/projects/agentWorkspace.ts and agent state paths in src/app/api/projects/[projectId]/tiles/route.ts and src/app/api/projects/[projectId]/tiles/[tileId]/route.ts are hard-coded to ~/.clawdbot. This can split state across directories depending on environment variables, which makes onboarding and cleanup confusing.
|
||||
|
||||
Extract a single state-dir resolver (for example in src/lib/clawdbot/stateDir.ts) that applies the same env and fallback rules as loadClawdbotConfig, then update all filesystem paths that rely on ~/.clawdbot to use it. Touch src/lib/projects/agentWorkspace.ts, src/app/api/projects/[projectId]/tiles/route.ts, src/app/api/projects/[projectId]/tiles/[tileId]/route.ts, and any other locations that build agent paths from CLAWDBOT_STATE_DIR; add a small unit test to lock down path resolution ordering.
|
||||
|
||||
Acceptance criteria: all agent workspace and state paths resolve from the same function and honor MOLTBOT_STATE_DIR/CLAWDBOT_STATE_DIR consistently; documentation reflects the single source of truth; and tests cover env override precedence. Open question: should MOLTBOT_STATE_DIR always win over CLAWDBOT_STATE_DIR, or do we need a migration path for existing users.
|
||||
@@ -1,5 +0,0 @@
|
||||
Deleting a tile triggers server-side directory removal in src/app/api/projects/[projectId]/tiles/[tileId]/route.ts via fs.rmSync, but the UI delete button in src/features/canvas/components/AgentTile.tsx does not confirm or summarize what will be removed. This is a high-risk action because it can delete both the agent workspace and agent state directories without any reversible path.
|
||||
|
||||
Add a confirmation flow that shows the exact directories slated for deletion and require a typed confirmation for tiles with existing workspaces; consider adding a server-side archive mode that moves directories to a trash location under the state dir instead of deleting outright. Update the delete endpoint to support a mode flag (archive vs delete), return the resolved paths in its response, and update the client to opt into archive by default with an explicit "permanently delete" option.
|
||||
|
||||
Acceptance criteria: tile deletion cannot proceed without an explicit confirmation, the API returns the paths that were archived/deleted, and the UI exposes a reversible archive workflow. Open question: should archives auto-expire or require a manual cleanup command, and where should that live in the UI.
|
||||
@@ -45,3 +45,6 @@ next-env.d.ts
|
||||
playwright-report
|
||||
test-results
|
||||
.playwright-home
|
||||
|
||||
# agent state
|
||||
/.agent
|
||||
|
||||
@@ -1,6 +1,17 @@
|
||||
# moltbot-agent-ui
|
||||
|
||||
Agent Canvas UI for Moltbot. This is a local-first Next.js UI that talks directly to the Moltbot gateway and stores workspace state on disk.
|
||||

|
||||
|
||||
Agent Canvas UI for Moltbot — a visual command center for multi-agent orchestration.
|
||||
|
||||
The terminal is great for running a command. It’s not great at being a *home* for a team of agents: multiple threads of work, long-running tasks, shared context, files that evolve, and the constant question of “what’s running where?” This project is built on the belief that the future of multi-agent orchestration won’t live exclusively in the terminal — it needs a UI that makes complex work feel obvious.
|
||||
|
||||
`moltbot-agent-ui` is a local-first Next.js app that connects to the Moltbot gateway, streams tool output live, and keeps workspace state on disk. The goal is simple: make multi-agent work friendly and fun to use, while staying ultra powerful when you need to go deep.
|
||||
|
||||
If you’re building or running agent workflows, this is the place to:
|
||||
- See work happening across agents at a glance
|
||||
- Keep context and artifacts (AGENTS.md, MEMORY.md, etc.) close to the work
|
||||
- Drive real projects forward without losing the plot
|
||||
|
||||
## Features
|
||||
- Multi-agent canvas for managing local workspaces
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 207 KiB |
Reference in New Issue
Block a user