agentleFS
pskoettpskoett/pmx-canvas/CLAUDE.md

pmx-canvas

CLAUDE.md30 starsChanged 9 days agoMITView on GitHub

Standalone spatial canvas workbench for coding agents. Infinite 2D canvas with nodes, edges, pan/zoom, minimap, and real-time updates — controlled through MCP, HTTP API, or a Bun-based JavaScript/TypeScript SDK. Extracted from PMX. The canvas is the agent's extended working memory: humans pin nodes to curate context, agents read that curation via MCP resources. Provide concise, focused responses. Skip non-essential context, and keep examples minimal. - No features beyond what was asked - No abstractions for single-use code - No "flexibility"…

The file

Show its rules and text

We split this CLAUDE.md into the rules it gives an agent

73 rules. About 0 are common, 37 are distinctive, and 36 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈37

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 36

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    Don't assume. Don't hide confusion.

    line 12
  2. Distinctive:

    Attack the premise: If two fixes based on the same assumption fail the same check, write down that assumption and test it directly before …

    line 18
  3. Distinctive:

    Redesign from first principles: When a new requirement exposes a mismatch in the existing design, consider how you would model it if that …

    line 19
  4. Distinctive:

    No features beyond what was asked

    line 24
  5. Distinctive:

    No abstractions for single-use code

    line 25
  6. Distinctive:

    No error handling for impossible scenarios

    line 27
  7. Distinctive:

    If 200 lines could be 50, rewrite it

    line 28
  8. Distinctive:

    When editing existing code:

    line 39
  9. Distinctive:

    Don't "improve" adjacent code, comments, or formatting

    line 40
  10. Distinctive:

    Don't refactor things that aren't broken

    line 41
  11. Distinctive:

    If you notice unrelated dead code, mention it — don't delete it

    line 43
  12. Distinctive:

    When your changes create orphans:

    line 45
  13. Very specific:

    Remove imports/variables/functions that YOUR changes made unused

    line 46
  14. Distinctive:

    Don't remove pre-existing dead code unless asked

    line 47
  15. Distinctive:

    Every mistake is a learning opportunity.

    line 70
  16. Distinctive:

    After ANY correction from the user: log the lesson

    line 72
  17. Distinctive:

    Prefer encoding recurring lessons in types, lint rules, boundary checks, or regression tests over adding more prose.

    line 73
  18. Very specific:

    Separate before serializing: Before adding a lock or queue, check whether writers can own separate files, keys, or state.

    line 81
  19. Distinctive:

    Keep these changes within the requested operation.

    line 82
  20. Distinctive:

    Before reporting or acting on a performance or evaluation result:

    line 85
  21. Distinctive:

    Keep a reproducible command or script.

    line 89
  22. Very specific:

    Do not introduce dynamic imports by default: Do not add await import(...) or similar dynamic-loading patterns unless the user explicitly …

    line 93
  23. Very specific:

    Do not use any casts or annotations: Avoid as any, : any, Promise<any>, or equivalent escape hatches.

    line 95
  24. Very specific:

    Do not add defensive noise by default: Do not add extra defensive checks or broad try/catch blocks unless they are necessary for a specific …

    line 96
  25. Very specific:

    SSE-created nodes must sync to server-side canvasState: When emitPrimaryWorkbenchEvent creates nodes on the client via SSE (workbench-open, …

    line 102
  26. Very specific:

    MCP tools map 1:1 to PmxCanvas methods: When adding a new canvas operation, add it to: (a) CanvasStateManager, (b) PmxCanvas class in …

    line 108
  27. Very specific:

    All generated files live under .pmx-canvas/ in the workspace root:

    line 164
  28. Very specific:

    Stop the server or flush/close the SDK before committing canvas.db.

    line 183
  29. Very specific:

    Use pmx_release_status (tag, npm, GitHub release and CI for a version) instead of polling npm or GitHub by hand.

    line 246
  30. Distinctive:

    Work happens on main: No feature or candidate branches.

    line 252
  31. Distinctive:

    New features get agent review: the oracle: When a new feature is done, and before asking the owner to push it, have the oracle (a reviewing …

    line 260
  32. Distinctive:

    The owner reviews only the high-blast-radius files: When a change touches one of these, show the owner that diff and wait for their review …

    line 264
  33. Very specific:

    Every push gets a CI subscription: After pushing, subscribe the thread to workflow completions for that exact commit …

    line 273
  34. Distinctive:

    Settle design choices before testing: Ask the owner for design and naming decisions in one batch before an acceptance run, not during it.

    line 276
  35. Very specific:

    Keep CHANGELOG.md entries simple and user-facing — short, plain sentences, no deep technical exposition, no exhaustive migration tables.

    line 281
  36. Distinctive:

    No nested sub-bullets, tool-by-tool mappings, or implementation detail.

    line 288
  37. Distinctive:

    Do not change Readme.md or replace its screenshots or demo video unless the user explicitly asks for that change or approves it first.

    line 296
  38. Distinctive:

    Keep the README a short product introduction and getting-started guide.

    line 301
  39. Distinctive:

    Use fictional content and data in README screenshots and demos, never real work boards.

    line 303
  40. Distinctive:

    Keep agent-skill setup as the recommended starting point.

    line 304
  41. Very specific:

    Use the pmx-canvas-testing skill for the repo-standard verification ladder, test command selection, and handoff expectations whenever you …

    line 309
  42. Very specific:

    Use the published-consumer-e2e skill when you need to validate PMX Canvas as an installed package in a clean temp consumer instead of the …

    line 312
  43. Very specific:

    Verify the full stack: Don't just check that code compiles — start the server, hit the endpoints, confirm the SPA loads.

    line 317
  44. Very specific:

    Assert the user-visible effect, not the server number: When a feature's value IS the rendered result — a viewport move, a node being …

    line 330
  45. Very specific:

    All server-side modules live in src/server/

    line 378
  46. Very specific:

    All client-side Preact components live in src/client/

    line 379
  47. Very specific:

    Add the type string to the union in src/server/canvas-state.ts (CanvasNodeState.type), plus canvas-provenance.ts (CanvasNodeType), …

    line 400
  48. Very specific:

    Create a renderer component in src/client/nodes/YourNode.tsx, to its artboard in docs/design/ (draw one first if none exists — see Design)

    line 404
  49. Very specific:

    Add the case to the render switches in src/client/canvas/CanvasViewport.tsx AND ExpandedNodeOverlay.tsx, plus the exhaustive records in …

    line 406
  50. Very specific:

    Add to NODE_TYPES in src/server/operations/ops/nodes.ts (MCP uses that same enum) and a defaultNodeSize case

    line 412
  51. Very specific:

    Update SKILL.md, readme.md, and docs/node-types.md with the new type

    line 414
  52. Very specific:

    Add the handler function in src/server/server.ts

    line 423
  53. Very specific:

    Add the route in the Bun.serve fetch handler

    line 424
  54. Very specific:

    Add the corresponding method to PmxCanvas class in src/server/index.ts

    line 425
  55. Very specific:

    Add the MCP tool in src/mcp/server.ts

    line 426
  56. Very specific:

    Update SKILL.md, readme.md, and CLI help text

    line 427
  57. Distinctive:

    Sandbox any iframe pointing at it: If every other iframe in the client is sandboxed, a new unsandboxed one is the bug.

    line 446
  58. Very specific:

    Create folder in skills/ with skill name (lowercase, hyphens)

    line 451
  59. Very specific:

    Create SKILL.md with YAML frontmatter:

    line 452
  60. Very specific:

    Add optional directories: scripts/, references/, assets/

    line 459
  61. Very specific:

    Ensure folder name matches name field

    line 460
  62. Distinctive:

    No README.md or other auxiliary files in skill folder

    line 467
  63. Very specific:

    Run bun run validate:agent-skills after changing any mirrored skill files

    line 469
  64. Distinctive:

    Keep only high-signal entries: unresolved blockers, recurring failures, or incidents that require durable guardrails

    line 474
  65. Distinctive:

    Remove one-off resolved noise after extracting reusable guidance

    line 475
  66. Distinctive:

    Keep active learnings concise and scannable

    line 476
  67. Very specific:

    Use skill-pipeline as the top-level router for non-trivial coding tasks

    line 485
  68. Distinctive:

    Treat pipeline depth as task-sized:

    line 489
  69. Very specific:

    Run pre-flight-check at session start when hooks are available.

    line 497
  70. Very specific:

    Use learning-aggregator and eval-creator for cross-session outer-loop improvement work

    line 498
  71. Distinctive:

    When using browser automation for UI investigation:

    line 507
  72. Distinctive:

    Use a visible browser window (headed mode) so the user can see what is happening

    line 508
  73. Distinctive:

    Do not run headless unless the user explicitly asks for it

    line 509

≈ “Common” is an estimate: at least 1 in 5 of the 26,980 CLAUDE.md files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
CLAUDE.md

PMX Canvas — Project Instructions

Standalone spatial canvas workbench for coding agents. Infinite 2D canvas with nodes, edges, pan/zoom, minimap, and real-time updates — controlled through MCP, HTTP API, or a Bun-based JavaScript/TypeScript SDK. Extracted from PMX.

The canvas is the agent's extended working memory: humans pin nodes to curate context, agents read that curation via MCP resources.

Provide concise, focused responses. Skip non-essential context, and keep examples minimal.

Core Principles

1. Think Before Coding

Don't assume. Don't hide confusion. Surface tradeoffs.

  • State assumptions explicitly — If uncertain, ask rather than guess
  • Present multiple interpretations — Don't pick silently when ambiguity exists
  • Push back when warranted — If a simpler approach exists, say so
  • Stop when confused — Name what's unclear and ask for clarification
  • Attack the premise. If two fixes based on the same assumption fail the same check, write down that assumption and test it directly before trying another fix. Use the smallest experiment that can disprove it; this is not permission for an unrelated redesign.
  • Redesign from first principles. When a new requirement exposes a mismatch in the existing design, consider how you would model it if that requirement had existed from the start. Prefer that design within the affected scope, without rewriting unrelated code.

2. Simplicity First

Minimum code that solves the problem. Nothing speculative.

  • No features beyond what was asked
  • No abstractions for single-use code
  • No "flexibility" or "configurability" that wasn't requested
  • No error handling for impossible scenarios
  • If 200 lines could be 50, rewrite it
  • No backwards compatibility. This project does not code for backwards compatibility: no legacy shims, deprecation aliases, dual code paths, or migration scaffolding for old clients/configs. When a behavior changes, change it cleanly and update everything that depends on it.

The test: Would a senior engineer say this is overcomplicated? If yes, simplify.

3. Surgical Changes

Touch only what you must. Clean up only your own mess.

When editing existing code:

  • Don't "improve" adjacent code, comments, or formatting
  • Don't refactor things that aren't broken
  • Match existing style, even if you'd do it differently
  • If you notice unrelated dead code, mention it — don't delete it

When your changes create orphans:

  • Remove imports/variables/functions that YOUR changes made unused
  • Don't remove pre-existing dead code unless asked

The test: Every changed line should trace directly to the user's request.

4. Goal-Driven Execution

Define success criteria. Loop until verified.

Transform imperative tasks into verifiable goals:

Instead of...Transform to...
"Add validation""Write tests for invalid inputs, then make them pass"
"Fix the bug""Write a test that reproduces it, then make it pass"
"Refactor X""Ensure tests pass before and after"

For multi-step tasks, state a brief plan:

  1. [Step] → verify: [check]
  2. [Step] → verify: [check]
  3. [Step] → verify: [check]

Strong success criteria let the agent loop independently. Weak criteria ("make it work") require constant clarification.

5. Learn and Improve

Every mistake is a learning opportunity. Log it, learn from it, prevent it.

  • After ANY correction from the user: log the lesson
  • Prefer encoding recurring lessons in types, lint rules, boundary checks, or regression tests over adding more prose. Use the smallest mechanism that catches the actual failure; do not add speculative guards.
  • Log to .learnings/ERRORS.md, LEARNINGS.md, or FEATURE_REQUESTS.md
  • Promote broadly applicable learnings to CLAUDE.md and AGENTS.md when automated enforcement cannot capture them; avoid duplicating what a check already enforces.

6. Design for Retries and Concurrent Writers

Apply this when the operation being changed can be retried, interrupted, or run concurrently.

  • Make retries idempotent. Retrying the same logical operation should converge on the intended state without duplicating side effects. Where repetition is intentional, distinguish a new operation from a retry. Verify interruption and retry at the relevant failure boundary.
  • Separate before serializing. Before adding a lock or queue, check whether writers can own separate files, keys, or state. Preserve genuinely shared state and its existing owner, including CanvasStateManager; serialize through that owner when separation would violate the domain.
  • Keep these changes within the requested operation. Do not introduce a general retry framework or repartition unrelated state.

7. Explain Measured Numbers

Before reporting or acting on a performance or evaluation result:

  • State what was measured, the baseline, workload, and conditions. Compare equivalent work and verify that outputs remain correct.
  • Identify what limits the result and check for confounders such as caching, warm-up, skipped work, or measurement overhead. Repeat enough to distinguish a real change from noise.
  • Keep a reproducible command or script. Report uncertainty and separate measured results from explanations that remain hypotheses; do not claim a speedup from a proxy that misses the user's workload.

TypeScript Guardrails

  1. Do not introduce dynamic imports by default: Do not add await import(...) or similar dynamic-loading patterns unless the user explicitly asks for them or the existing architecture requires them. The shared Mermaid renderer is an architecture exception: only diagram rendering loads Mermaid. The SPA splits its chunks; self-contained MCP documents defer initialization without external asset requests.
  2. Do not use any casts or annotations: Avoid as any, : any, Promise<any>, or equivalent escape hatches. Model the real type instead.
  3. Do not add defensive noise by default: Do not add extra defensive checks or broad try/catch blocks unless they are necessary for a specific runtime boundary, recovery path, or user-requested behavior.

Canvas Architecture Rules

  1. State lives in the server. CanvasStateManager in canvas-state.ts is the singleton source of truth. All mutations go through it. The browser is a renderer. State survives browser refresh.
  2. SSE-created nodes must sync to server-side canvasState. When emitPrimaryWorkbenchEvent creates nodes on the client via SSE (workbench-open, ext-app-open), also create them in the server-side canvasState singleton. Otherwise canvas_query { action: "layout" } returns 0 nodes and canvas-layout-update reconciliation deletes client-only nodes.
  3. Rebuild canvas bundle after client source changes. After modifying any file under src/client/, run bun run build before testing in the browser. The dist bundle is not auto-built; stale bundles silently hide new features. An already-open tab used to keep old JS through every dev rebuild — reloadIfServerUpgraded compared the package version only, which a rebuild+restart never bumps; an entire debugging arc (2026-08-24) chased "still broken" reports that were a stale tab faithfully reproducing already-fixed bugs. The connected frame now carries a served-bundle stamp and a mismatched tab reloads itself on (re)connect — but tabs opened before the stamp shipped, and any surface you cannot force-navigate, still need one manual hard reload. Before believing a UI bug report (yours or the user's), first establish WHICH bundle the reporting surface is actually running.
  4. Canvas edits happen in place. The web canvas is a live multi-node workspace. Flows should update the current session without evicting prior nodes (including prior document nodes). Agents must not describe the canvas as requiring reopen/replace for additional documents.
  5. MCP tools map 1:1 to PmxCanvas methods. When adding a new canvas operation, add it to: (a) CanvasStateManager, (b) PmxCanvas class in src/server/index.ts, (c) HTTP endpoint in src/server/server.ts, (d) MCP tool in src/mcp/server.ts. All four layers must stay in sync.
  6. Context pins are the bridge between human and agent. The human pins nodes in the browser, the agent reads canvas://pinned-context. This is the primary communication channel from human spatial curation to agent context. Preserve this flow.
  7. No HTTP server port assumptions. Default port is 4313 but can be changed via --port or PMX_WEB_CANVAS_PORT for server startup; when both are unset the server also falls back to PMX_CANVAS_PORT (0.4.0), so one variable can point the client and server at the same port. Amp orbs: the portal-assigned PORT env is honored after those (only when AMP_ORB is set), so an orb service command needs no port flag. PMX_CANVAS_PORT otherwise remains the agent CLI's client-side default target port. The server tries fallback ports if the preferred one is taken. PMX_CANVAS_WORKSPACE_ROOT pins the workspace root for both the MCP same-workspace lookup and the daemon startCanvasServer binds, overriding the launch cwd — set it when a host spawns --mcp from an incidental dir (e.g. ~/.copilot) so the canvas targets the real project. When the preferred port is held by a different-workspace daemon, the MCP server now attaches to it (inherits its workspace) instead of silently splitting to a fallback port; use PMX_CANVAS_ALLOW_WORKSPACE_SPLIT=1 to force a separate canvas.
  8. Mutation listeners are single-slot. canvasState.onMutation and setWorkItemsChangedListener hold exactly ONE callback each — registering a second consumer silently displaces the first. server.ts claims onMutation whenever a server starts, so whether a side effect fires depends on whether anything booted a server first. Fan out inside the existing registration; never add a competing one.
  9. The viewport is a SCREEN-space translate, not a world-space camera. The canvas renders matrix(scale, 0, 0, scale, x, y), so screen = world * scale + viewport. To put a node at a fixed screen margin, compute margin - node.position * scale — what fitCanvasView and the client's focusNode do. Writing node.position - margin / scale mixes conventions and lands the node off-screen at any scale ≠ 1 (it shipped twice: view.focus in 0.4.6, and fit's world-space padding, which collapses under floating chrome at fit zoom). Margins that must clear overlays are screen-space constants. Since the rail-chrome restructure the canvas region itself excludes the rail/top bar — screen-space math anchors on canvasArea() (src/client/canvas/canvas-area.ts), never window.innerWidth.
  10. json-render / graph viewers read their spec once, at document load. The iframe src is keyed on nodeId + ?v=specVersion, so any content update MUST bump specVersion (buildJsonRenderNodeUpdate does). Skip it and the URL is byte-identical, the iframe never reloads, and the node renders stale forever while the server data is correct — the live workboard shipped that way in 0.4.6.

Tech Stack

  • Runtime: Bun (build + serve)
  • UI: Preact + @preact/signals
  • Styling: CSS custom properties for the main canvas UI, plus a Tailwind CLI build for the json-render viewer bundle
  • Server: Bun.serve (HTTP + SSE)
  • MCP: @modelcontextprotocol/sdk (stdio transport)
  • Bundler: Bun bundler for client SPA
  • Dependencies: preact, @preact/signals, marked, @modelcontextprotocol/sdk, @modelcontextprotocol/ext-apps, zod

Build & Run

bun install                    # Install dependencies
bun run build                  # Build client SPA → dist/canvas/
bun run dev                    # Start server + open browser
bun run dev:demo               # Start with the showcase demo board
bun run start                  # Start headless (no browser)
pmx-canvas serve --daemon      # Start daemonized server with pid/log tracking
pmx-canvas serve status        # Check daemon health + pid state
pmx-canvas serve stop          # Stop daemonized server
pmx-canvas --mcp               # Run as MCP server
pmx-canvas --theme=daylight    # Start with the Daylight theme
bun run lint                   # Biome lint + format check (lint:fix to write)

Persistence

All generated files live under .pmx-canvas/ in the workspace root:

.pmx-canvas/
  canvas.db            # SQLite: every board, snapshots, context pins, and blobs — git-committable
  backups/             # `pmx-canvas backup` copies (canvas-<timestamp>.db), newest N kept
  exports/             # `pmx-canvas export` static board files (<board>-<timestamp>.html)
  artifacts/           # web-artifact HTML bundles
    .web-artifacts/    # reusable per-artifact build projects
  daemon-<port>.log    # daemon stdout/stderr (when started with `serve --daemon`)
  daemon-<port>.pid    # daemon pid file

State auto-saves every mutation (debounced 500ms) and auto-loads on server start. Pre-0.2 legacy layouts (.pmx-canvas/state.json, .pmx-canvas.json, snapshot dirs, blob files) are no longer imported as of 0.4.0 — restore them with a 0.3.x install first if needed.

  • Override DB path: PMX_CANVAS_DB_PATH env var
  • PMX_CANVAS_STATE_FILE remains only as a .db-path alias (non-.db values are ignored with a boot warning as of 0.4.0)
  • --demo only seeds when canvas is empty (won't clobber restored state)
  • State saves: viewport, nodes, edges, annotations, context pins, snapshots, and large node blobs
  • Stop the server or flush/close the SDK before committing canvas.db; shutdown checkpoints SQLite WAL data into the DB file.

Demo Board

src/server/demo-state.json is a generated fixture — never hand-edit it. Rebuild it with:

bun run scripts/generate-demo-board.ts

The generator builds the board through the real HTTP API, then rewrites live ids and timestamps to stable ones so the output is byte-deterministic (run it twice, get the same file). Anything it creates must go through the API — that is what keeps the fixture honest about what the product actually produces.

The fixture embeds each HTML primitive's generated markup plus the canvas-bound AX partition, so it goes stale the moment a renderer, primitive, or node type changes — silently, because counts and coverage assertions are just as happy with month-old markup. tests/unit/demo.test.ts guards this by rebuilding every primitive from the stored primitiveData and byte-comparing against the current renderer. Regenerate and re-run that test after touching any of the above.

Design

The design guide is docs/design.md; the drawings it refers to are the artboard sources in docs/design/ (*.dc.html, layout in canvas.json). Read both before changing anything a person sees: styles under src/client/theme/, chrome, node renderers, Home, edges.

  1. Build to the drawing, not the prose. Find the surface's artboard in the table in docs/design.md and take sizes, colours, spacing and copy from its source; values computed in its renderVals() script are part of the drawing. If no artboard covers the surface, draw it (or ask) before building; do not invent a look.
  2. Ship with the wave. docs/design.md pairs each design area with a vision move in numbered waves. Design ships with the function that produces its data: do not restyle a surface ahead of its wave, and do not ship a wave's function with the old styling.
  3. Meaning colours are fixed. Blue = in the agent's context (pinned). Violet = what the agent did (read, created, edited, suggested). Amber = needs a look (warning, stale, out of date) and nothing else. Relation inks never reuse these three. Every such mark also carries a glyph and a word.
  4. The drawings are the target; the code may lag. Until wave 0 lands, today's CSS still uses --c-warn for pins and attention and falls back to Arial on controls. Do not spread those patterns; move the surface you touch toward the drawing.
  5. Done means the checks in docs/design.md: the reference cases at 600, 1024 and 1920 px, all nine themes, the demo board regenerated, user-facing docs updated.
  6. One design home. Decisions and deviations go in docs/design.md; a drawing changed in the proposal canvas is re-exported into docs/design/ in the same change. No other design folders.

Themes

Nine themes: harbor (default), daylight, high-contrast, midnight, sepia, arctic, ember, forest, volt. The canonical registry is src/shared/themes.ts; per-theme CSS variable blocks live in src/client/theme/global.css + surface-theme.css (kept in sync by tests/unit/surface-theme-tokens.test.ts). Set via:

  • CLI: --theme=daylight
  • Env: PMX_CANVAS_THEME=daylight
  • Browser: rail theme picker (sun/moon button in the left tool rail opens the theme menu)

JSON viewers inherit the named canvas palette. External MCP apps receive its dark/light scheme via canvasThemeScheme (e.g. sepia → light, ember → dark) and the host's style variables.

Releasing

The full release recipe (pre-flight gates, version bump, tag → publish, smoke, common gotchas) lives in docs/RELEASE.md. The README intentionally does not document the release flow — it's an end-user-facing file and the release process is maintainer-only.

Use pmx_release_status (tag, npm, GitHub release and CI for a version) instead of polling npm or GitHub by hand. After publishing, set a thread schedule to report when the version is live on npm instead of waiting in the turn.

Handoffs Between Threads

  • Work happens on main. No feature or candidate branches.
  • Delegated work goes to threads; subagents only explore. Spawn a thread (create_thread) for work someone else owns: testing, acceptance runs, fixes, parallel streams. Use subagents only for read-only exploration, research and the oracle review.
  • Threads get unpushed work with download_thread_changes from the source thread, which copies its changed files. Name the source thread and its base commit in the prompt, and ask the thread to report back with send_thread_message. Never hand over patch files, hash manifests, split archives or /tmp checkouts, and never push to main just to share work.
  • New features get agent review: the oracle. When a new feature is done, and before asking the owner to push it, have the oracle (a reviewing subagent) review the feature's diff as a skeptic: give it the intent, not your conclusion. Fix or answer every finding, and report what it found alongside the acceptance results. Fixes, refactors and small changes do not need it.
  • The owner reviews only the high-blast-radius files. When a change touches one of these, show the owner that diff and wait for their review before pushing; everything else ships on evidence (plus the oracle review for new features):
    • src/server/canvas-db.ts: the SQLite schema and migrations of every user's canvas.db
    • src/server/canvas-state.ts: the single source of truth and its single-slot listeners
    • src/server/workbench-auth.ts: what counts as a human write
    • .github/workflows/: CI and npm publishing
    • any breaking change to the public contract in docs/api-stability.md (additive changes do not need it)
  • Every push gets a CI subscription. After pushing, subscribe the thread to workflow completions for that exact commit (amp.create_github_subscription) and report the result when it arrives, instead of polling or asking the owner to check.
  • Settle design choices before testing. Ask the owner for design and naming decisions in one batch before an acceptance run, not during it.

Changelog Style

Keep CHANGELOG.md entries simple and user-facing — short, plain sentences, no deep technical exposition, no exhaustive migration tables. Per release:

  • Optional Highlights — 3–5 one-line bullets of the most notable changes
  • Added / Changed / Fixed (and Breaking when needed) — each bullet is one plain sentence describing the change from the user's perspective (what they can now do or what behaves differently)
  • No nested sub-bullets, tool-by-tool mappings, or implementation detail; link to docs for anything long-form

Example bullet: "Added a /rename slash command to rename the current session directly from the composer."

README changes require approval

Do not change Readme.md or replace its screenshots or demo video unless the user explicitly asks for that change or approves it first. A feature, fix, or release is not permission to update the README. This rule overrides any checklist below that asks for a README update.

Keep the README a short product introduction and getting-started guide. Put detailed feature behavior, API contracts, and release notes in the relevant docs or changelog. Use fictional content and data in README screenshots and demos, never real work boards. Keep agent-skill setup as the recommended starting point; reserve repository cloning for development, not the main installation instructions.

Testing Conventions

Use the pmx-canvas-testing skill for the repo-standard verification ladder, test command selection, and handoff expectations whenever you change code in this project.

Use the published-consumer-e2e skill when you need to validate PMX Canvas as an installed package in a clean temp consumer instead of the repo dev path.

  1. Never dismiss failing tests. Investigate every failure before declaring success. A "pre-existing" failure still needs resolution or explicit acknowledgment.
  2. Verify the full stack. Don't just check that code compiles — start the server, hit the endpoints, confirm the SPA loads. In an orb the canvas is the canvas service in .amp/services.yaml (port 4313, with a portal); never start it as a shell background job:
    amp orb services ensure                  # start it; after `bun run build`: amp orb service restart canvas
    curl http://localhost:4313/health        # ok, workspace, version, bundleStamp
    curl http://localhost:4313/canvas/index.js -o /dev/null -w "%{http_code}"  # Should be 200
    The pmx_canvas_health tool (.amp/plugins/pmx-sdlc.ts) checks which checkout and bundle the server actually serves. Run it before reporting UI results or asking the owner to test.
  3. Test MCP server separately. The MCP server can be tested with bun run src/mcp/server.ts and sending JSON-RPC over stdin.
  4. Assert the user-visible effect, not the server number. When a feature's value IS the rendered result — a viewport move, a node being visible, iframe-backed content refreshing — assert the observable: the node's getBoundingClientRect() lands in the viewport, the iframe URL actually changed, the rendered text updated. A viewport tuple, a panned: true, or updated node.data is an intermediate, and asserting one locks in whatever convention the implementation happened to use. Three features shipped broken with green tests this way (view.focus, the live workboard, fit) — in two cases the wrong-convention assertions were added by the very commit that introduced the bug. See architecture rules 9 and 10 for the two traps this repo keeps hitting.
  5. Use the real canvas yourself during development. Automated tests are necessary, not acceptance. Before handing a UI feature to the user, rebuild, confirm the served bundle, and exercise the complete affected workflow in a headed browser on a populated board through the actual portal. Do not make the user the first person to discover rendering or interaction failures. Use real pointer and keyboard input, inspect screenshots and recordings, and fix what you observe rather than relying only on DOM assertions or passing test counts. For shared canvas/viewer changes, cover every affected node type, pan across multiple populated regions and back, repeatedly zoom out and in through fractional scales and fit-all, resize, expand/close, reload, and verify content, connections and drafts remain correct. Check the required widths and themes. Run relevant browser engines, including WebKit for iframe/scaling changes; record engine versions and distinguish engine checks from native-host acceptance. A fresh load at one zoom or a passing Chromium run does not establish correct zoom transitions or WebKit behavior. Report unavailable host checks and unresolved failures explicitly; do not call the feature accepted until the affected end-to-end flows have been exercised and inspected.
  6. Owner acceptance is a script, not a rehearsal. The owner acceptance guide lives in tests/acceptance/guide.ts (pass criteria, the tests that prove each row, the files that invalidate it). bun run acceptance reruns only rows whose files changed since their last pass and writes test-results/acceptance/report.md with a video per test. A design or naming change reruns the rows it touches, never the whole guide. When a guide row gains a criterion, add the test that proves it to the manifest; ownerChecks are for what an orb cannot see. The guide follows development: a user-facing change is not done until its row exists or an existing row gains its criterion and proving test, and every new src/ file joins a row (tests/unit/acceptance-guide.test.ts fails otherwise). At each release, retire rows the release made obsolete.
  7. This is not evidence: a passing unit test for a rendered result, a DOM assertion without a screenshot you inspected, a run against a bundle you did not confirm with pmx_canvas_health, an MCP handshake standing in for the host discovering tools, a clean rerun after an unexplained failure, or a summary from another thread. Evidence is the command, its decisive output, and the artifact, from the build you are reporting on.

API and Product Reference

See API and product reference for canvas types, MCP, AX primitives, spatial semantics, history, code graphs, and integration paths.

Conventions

  • All server-side modules live in src/server/
  • All client-side Preact components live in src/client/
  • The MCP server imports from src/server/index.ts — it does not duplicate state management
  • CSS uses custom properties (:root { --c-* }) — no Tailwind classes
  • Imports use .js extensions for Bun module resolution
  • The canvasState singleton is shared across HTTP handlers, MCP tools, and the SDK class
  • Client writes go through the bridge helpers (requestJson / requestOk / requestBestEffort in src/client/state/intent-bridge.ts), never a bare fetch: they carry the X-PMX-Workbench marker that tells the server the write is the human's. Without it the human's own click is booked as an anonymous api agent — it lights the External Steering indicator, shows as agent work in the session timeline, and is subject to the scope fence (context pins and annotations shipped that way; tests/client/context-pin-bar.test.tsx asserts the marker)
  • No native browser UI in client flows. window.prompt, window.alert, window.confirm, and title-attribute tooltips are silent no-ops in embedded browser panes (Claude, Copilot, and Codex all drive the workbench through one) — a flow depending on them "does nothing" there, with no error. This shipped three times. Use askText/TextPrompt (src/client/canvas/TextPrompt.tsx) for input, BarHint for hover hints, and an in-flow caption where an anchored tooltip would clip inside an overflow container.

Adding New Node Types

  1. Add the type string to the union in src/server/canvas-state.ts (CanvasNodeState.type), plus canvas-provenance.ts (CanvasNodeType), ax-interaction.ts (DEFAULT_NODE_AX_CAPABILITIES — exhaustive record), canvas-schema.ts (create schema), canvas-validation.ts (NODE_MIN_CREATE_SIZES floor)
  2. Create a renderer component in src/client/nodes/YourNode.tsx, to its artboard in docs/design/ (draw one first if none exists — see Design)
  3. Add the case to the render switches in src/client/canvas/CanvasViewport.tsx AND ExpandedNodeOverlay.tsx, plus the exhaustive records in state/node-factory.ts, types.ts (union, TYPE_LABELS, EXPANDABLE_TYPES), icons.tsx
  4. Add the type to isCanvasNodeType in src/client/state/sse-bridge.ts — this runtime guard silently DROPS unknown types during layout apply, so a missed entry renders nothing in the live workbench even though every unit/client test passes
  5. Add to NODE_TYPES in src/server/operations/ops/nodes.ts (MCP uses that same enum) and a defaultNodeSize case
  6. Update SKILL.md, readme.md, and docs/node-types.md with the new type
  7. Regenerate the demo board (bun run scripts/generate-demo-board.ts) and add the type to it — see Demo Board
  8. Open the type in a real browser before calling it done. Steps 3–5 can all be complete and every unit and client test green while the node renders nothing (see step 4). A DOM check on a live board is the only thing that catches it.

Adding New HTTP Endpoints

  1. Add the handler function in src/server/server.ts
  2. Add the route in the Bun.serve fetch handler
  3. Add the corresponding method to PmxCanvas class in src/server/index.ts
  4. Add the MCP tool in src/mcp/server.ts
  5. Update SKILL.md, readme.md, and CLI help text
  6. Document it in the contract docs — docs/http-api.md (and docs/mcp.md / docs/cli.md for a tool/action/command). docs/api-stability.md defines the public surface as what those files document, so an undocumented route is both invisible to agents and outside the stability contract. The same applies to counts and tables agents read as authoritative: the tool count, the composite action lists, the theme list, and the AX capability matrix in skills/pmx-canvas/references/full-reference.md all went stale mid-cycle at least once.

Routes that serve raw file bytes need four separate guards (the 0.4.7 file-bytes route shipped without any of them):

  • Confine on REAL paths — realpathSync the candidate and the workspace root before comparing. A lexical resolve()/relative() check stops .. and absolute escapes but not a symlink inside the workspace; realpath the root too, or a symlinked root (macOS /tmp) rejects legitimate files.
  • Derive Content-Type from the bytes, never from caller-writable node data, and send X-Content-Type-Options: nosniff plus a non-inline disposition for anything not deliberately rendered.
  • Stream (Bun.file) with a size ceiling and an isFile() check — a directory 500s and a FIFO hangs a server that has no request timeout.
  • Sandbox any iframe pointing at it. If every other iframe in the client is sandboxed, a new unsandboxed one is the bug.

Creating a New Skill

  1. Create folder in skills/ with skill name (lowercase, hyphens)
  2. Create SKILL.md with YAML frontmatter:
    ---
    name: skill-name
    description: What it does and when to use it.
    ---
  3. Add optional directories: scripts/, references/, assets/
  4. Ensure folder name matches name field

Validating Skills

  • Frontmatter has required name and description
  • name is lowercase, hyphens only, matches folder
  • description explains what AND when to use
  • No README.md or other auxiliary files in skill folder
  • Agent-facing pipeline skills live in .agents/skills/ and must be mirrored identically in .claude/skills/ and .opencode/skills/
  • Run bun run validate:agent-skills after changing any mirrored skill files

Error Hygiene

When maintaining .learnings/:

  1. Keep only high-signal entries: unresolved blockers, recurring failures, or incidents that require durable guardrails
  2. Remove one-off resolved noise after extracting reusable guidance
  3. Keep active learnings concise and scannable

Agent Skill Pipeline

  • The pipeline skills (skill-pipeline, verify-gate, simplify-and-harden, intent-framed-agent, …) are the owner's global Amp user skills, so they are available in every orb and checkout; load them by name
  • Local mirrors in .agents/skills/, .claude/skills/ and .opencode/skills/ are optional, gitignored dev tooling (Claude Code hooks in .claude/settings.json point at them). When present, keep them byte-identical; verify with bun run validate:agent-skills, which skips when they are absent
  • Use skill-pipeline as the top-level router for non-trivial coding tasks

How To Run It

Treat pipeline depth as task-sized:

  • Trivial tasks: no pipeline
  • Small tasks: run verify-gate then simplify-and-harden
  • Medium tasks: run intent-framed-agent, then verify-gate, then simplify-and-harden
  • Large or long-running tasks: run plan-interview, then intent-framed-agent with context-surfing, then verify-gate, then simplify-and-harden, then self-improvement
  • Batch tasks: run agent-teams-simplify-and-harden, then self-improvement
  • CI/headless review: use simplify-and-harden-ci and self-improvement-ci; use learning-aggregator-ci and eval-creator-ci for the outer loop
  • Run pre-flight-check at session start when hooks are available; Claude hooks also wire context-surfing handoff detection and self-improvement reminders
  • Use learning-aggregator and eval-creator for cross-session outer-loop improvement work

Version

  • Imported pipeline version manifest: .agents/skills/PIPELINE_VERSIONS.md
  • Canonical imported revision: 01ae6f8b3c9a0ab96e8ec87b27fdd88677696cde from https://github.com/pskoett/pskoett-ai-skills

Browser Automation Visibility Rule

When using browser automation for UI investigation:

  1. Use a visible browser window (headed mode) so the user can see what is happening
  2. Do not run headless unless the user explicitly asks for it

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.