pmx-canvas
AGENTS.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"…
Where files are headingNew AGENTS.md files have more rules about keeping secrets out of the code, and fewer about code style.
Of the AGENTS.md files we read, the 31,542 first seen in the last six months, against the older ones: the kind of rule that gained most share, and the kind that lost most. About files like this in general, not this one, and an estimate, since a kind is a pattern in the words.
In this repository20 other files it gives its agents.
- CLAUDE.mdCLAUDE.md
- .github/copilot-instructions.mdCopilot instructions
- control-session-orchestratorSkill
- data-analysisSkill
- doc-coauthoringSkill
- frontend-designSkill
- json-render-codegenSkill
- json-render-coreSkill
- json-render-inkSkill
- json-render-mcpSkill
- json-render-reactSkill
- json-render-shadcnSkill
- playwright-cliSkill
- pmx-canvas-orchestrationSkill
- pmx-canvasSkill
- pmx-canvas-testingSkill
- published-consumer-e2eSkill
- tufte-vizSkill
- web-artifacts-builderSkill
- web-design-guidelinesSkill
The file
Show its rules and text
We split this AGENTS.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
- Distinctive: line 12
Don't assume. Don't hide confusion.
- Distinctive: line 18
Attack the premise: If two fixes based on the same assumption fail the same check, write down that assumption and test it directly before …
- Distinctive: line 19
Redesign from first principles: When a new requirement exposes a mismatch in the existing design, consider how you would model it if that …
- Distinctive: line 24
No features beyond what was asked
- Distinctive: line 25
No abstractions for single-use code
- Distinctive: line 27
No error handling for impossible scenarios
- Distinctive: line 28
If 200 lines could be 50, rewrite it
- Distinctive: line 39
When editing existing code:
- Distinctive: line 40
Don't "improve" adjacent code, comments, or formatting
- Distinctive: line 41
Don't refactor things that aren't broken
- Distinctive: line 43
If you notice unrelated dead code, mention it — don't delete it
- Distinctive: line 45
When your changes create orphans:
- Very specific: line 46
Remove imports/variables/functions that YOUR changes made unused
- Distinctive: line 47
Don't remove pre-existing dead code unless asked
- Distinctive: line 70
Every mistake is a learning opportunity.
- Distinctive: line 72
After ANY correction from the user: log the lesson
- Distinctive: line 73
Prefer encoding recurring lessons in types, lint rules, boundary checks, or regression tests over adding more prose.
- Very specific: line 81
Separate before serializing: Before adding a lock or queue, check whether writers can own separate files, keys, or state.
- Distinctive: line 82
Keep these changes within the requested operation.
- Distinctive: line 85
Before reporting or acting on a performance or evaluation result:
- Distinctive: line 89
Keep a reproducible command or script.
- Very specific: line 93
Do not introduce dynamic imports by default: Do not add await import(...) or similar dynamic-loading patterns unless the user explicitly …
- Very specific: line 95
Do not use
anycasts or annotations: Avoid as any, : any, Promise<any>, or equivalent escape hatches. - Very specific: line 96
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 …
- Very specific: line 102
SSE-created nodes must sync to server-side canvasState: When emitPrimaryWorkbenchEvent creates nodes on the client via SSE (workbench-open, …
- Very specific: line 108
MCP tools map 1:1 to PmxCanvas methods: When adding a new canvas operation, add it to: (a) CanvasStateManager, (b) PmxCanvas class in …
- Very specific: line 164
All generated files live under .pmx-canvas/ in the workspace root:
- Very specific: line 183
Stop the server or flush/close the SDK before committing canvas.db.
- Very specific: line 246
Use pmx_release_status (tag, npm, GitHub release and CI for a version) instead of polling npm or GitHub by hand.
- Distinctive: line 252
Work happens on
main: No feature or candidate branches. - Distinctive: line 260
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 …
- Distinctive: line 264
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 …
- Very specific: line 273
Every push gets a CI subscription: After pushing, subscribe the thread to workflow completions for that exact commit …
- Distinctive: line 276
Settle design choices before testing: Ask the owner for design and naming decisions in one batch before an acceptance run, not during it.
- Very specific: line 281
Keep CHANGELOG.md entries simple and user-facing — short, plain sentences, no deep technical exposition, no exhaustive migration tables.
- Distinctive: line 288
No nested sub-bullets, tool-by-tool mappings, or implementation detail.
- Distinctive: line 296
Do not change Readme.md or replace its screenshots or demo video unless the user explicitly asks for that change or approves it first.
- Distinctive: line 301
Keep the README a short product introduction and getting-started guide.
- Distinctive: line 303
Use fictional content and data in README screenshots and demos, never real work boards.
- Distinctive: line 304
Keep agent-skill setup as the recommended starting point.
- Very specific: line 309
Use the pmx-canvas-testing skill for the repo-standard verification ladder, test command selection, and handoff expectations whenever you …
- Very specific: line 312
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 …
- Very specific: line 317
Verify the full stack: Don't just check that code compiles — start the server, hit the endpoints, confirm the SPA loads.
- Very specific: line 330
Assert the user-visible effect, not the server number: When a feature's value IS the rendered result — a viewport move, a node being …
- Very specific: line 378
All server-side modules live in src/server/
- Very specific: line 379
All client-side Preact components live in src/client/
- Very specific: line 400
Add the type string to the union in src/server/canvas-state.ts (CanvasNodeState.type), plus canvas-provenance.ts (CanvasNodeType), …
- Very specific: line 404
Create a renderer component in src/client/nodes/YourNode.tsx, to its artboard in docs/design/ (draw one first if none exists — see Design)
- Very specific: line 406
Add the case to the render switches in src/client/canvas/CanvasViewport.tsx AND ExpandedNodeOverlay.tsx, plus the exhaustive records in …
- Very specific: line 412
Add to NODE_TYPES in src/server/operations/ops/nodes.ts (MCP uses that same enum) and a defaultNodeSize case
- Very specific: line 414
Update SKILL.md, readme.md, and docs/node-types.md with the new type
- Very specific: line 423
Add the handler function in src/server/server.ts
- Very specific: line 424
Add the route in the Bun.serve fetch handler
- Very specific: line 425
Add the corresponding method to PmxCanvas class in src/server/index.ts
- Very specific: line 426
Add the MCP tool in src/mcp/server.ts
- Very specific: line 427
Update SKILL.md, readme.md, and CLI help text
- Distinctive: line 446
Sandbox any iframe pointing at it: If every other iframe in the client is sandboxed, a new unsandboxed one is the bug.
- Very specific: line 451
Create folder in skills/ with skill name (lowercase, hyphens)
- Very specific: line 452
Create SKILL.md with YAML frontmatter:
- Very specific: line 459
Add optional directories: scripts/, references/, assets/
- Very specific: line 460
Ensure folder name matches name field
- Distinctive: line 467
No README.md or other auxiliary files in skill folder
- Very specific: line 469
Run bun run validate:agent-skills after changing any mirrored skill files
- Distinctive: line 474
Keep only high-signal entries: unresolved blockers, recurring failures, or incidents that require durable guardrails
- Distinctive: line 475
Remove one-off resolved noise after extracting reusable guidance
- Distinctive: line 476
Keep active learnings concise and scannable
- Very specific: line 485
Use skill-pipeline as the top-level router for non-trivial coding tasks
- Distinctive: line 489
Treat pipeline depth as task-sized:
- Very specific: line 497
Run pre-flight-check at session start when hooks are available.
- Very specific: line 498
Use learning-aggregator and eval-creator for cross-session outer-loop improvement work
- Distinctive: line 507
When using browser automation for UI investigation:
- Distinctive: line 508
Use a visible browser window (headed mode) so the user can see what is happening
- Distinctive: line 509
Do not run headless unless the user explicitly asks for it
≈ “Common” is an estimate: at least 1 in 5 of the 40,246 AGENTS.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.
Files
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:
- [Step] → verify: [check]
- [Step] → verify: [check]
- [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, orFEATURE_REQUESTS.md - Promote broadly applicable learnings to
CLAUDE.mdandAGENTS.mdwhen 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
- 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. - Do not use
anycasts or annotations: Avoidas any,: any,Promise<any>, or equivalent escape hatches. Model the real type instead. - Do not add defensive noise by default: Do not add extra defensive checks or broad
try/catchblocks unless they are necessary for a specific runtime boundary, recovery path, or user-requested behavior.
Canvas Architecture Rules
- State lives in the server.
CanvasStateManagerincanvas-state.tsis the singleton source of truth. All mutations go through it. The browser is a renderer. State survives browser refresh. - SSE-created nodes must sync to server-side canvasState. When
emitPrimaryWorkbenchEventcreates nodes on the client via SSE (workbench-open,ext-app-open), also create them in the server-sidecanvasStatesingleton. Otherwisecanvas_query { action: "layout" }returns 0 nodes andcanvas-layout-updatereconciliation deletes client-only nodes. - Rebuild canvas bundle after client source changes. After modifying any file under
src/client/, runbun run buildbefore 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 —reloadIfServerUpgradedcompared 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. Theconnectedframe 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. - 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.
- MCP tools map 1:1 to PmxCanvas methods. When adding a new canvas operation, add it to: (a)
CanvasStateManager, (b)PmxCanvasclass insrc/server/index.ts, (c) HTTP endpoint insrc/server/server.ts, (d) MCP tool insrc/mcp/server.ts. All four layers must stay in sync. - 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. - No HTTP server port assumptions. Default port is 4313 but can be changed via
--portorPMX_WEB_CANVAS_PORTfor server startup; when both are unset the server also falls back toPMX_CANVAS_PORT(0.4.0), so one variable can point the client and server at the same port. Amp orbs: the portal-assignedPORTenv is honored after those (only whenAMP_ORBis set), so an orb service command needs no port flag.PMX_CANVAS_PORTotherwise remains the agent CLI's client-side default target port. The server tries fallback ports if the preferred one is taken.PMX_CANVAS_WORKSPACE_ROOTpins the workspace root for both the MCP same-workspace lookup and the daemonstartCanvasServerbinds, overriding the launchcwd— set it when a host spawns--mcpfrom 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; usePMX_CANVAS_ALLOW_WORKSPACE_SPLIT=1to force a separate canvas. - Mutation listeners are single-slot.
canvasState.onMutationandsetWorkItemsChangedListenerhold exactly ONE callback each — registering a second consumer silently displaces the first.server.tsclaimsonMutationwhenever 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. - The viewport is a SCREEN-space translate, not a world-space camera. The canvas renders
matrix(scale, 0, 0, scale, x, y), soscreen = world * scale + viewport. To put a node at a fixed screen margin, computemargin - node.position * scale— whatfitCanvasViewand the client'sfocusNodedo. Writingnode.position - margin / scalemixes conventions and lands the node off-screen at any scale ≠ 1 (it shipped twice:view.focusin 0.4.6, andfit'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 oncanvasArea()(src/client/canvas/canvas-area.ts), neverwindow.innerWidth. - json-render / graph viewers read their spec once, at document load. The iframe
srcis keyed onnodeId + ?v=specVersion, so any content update MUST bumpspecVersion(buildJsonRenderNodeUpdatedoes). 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 fileState 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_PATHenv var PMX_CANVAS_STATE_FILEremains only as a.db-path alias (non-.dbvalues are ignored with a boot warning as of 0.4.0)--demoonly 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.tsThe 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.
- Build to the drawing, not the prose. Find the surface's artboard in the table in
docs/design.mdand take sizes, colours, spacing and copy from its source; values computed in itsrenderVals()script are part of the drawing. If no artboard covers the surface, draw it (or ask) before building; do not invent a look. - Ship with the wave.
docs/design.mdpairs 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. - 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.
- The drawings are the target; the code may lag. Until wave 0 lands, today's CSS still uses
--c-warnfor pins and attention and falls back to Arial on controls. Do not spread those patterns; move the surface you touch toward the drawing. - 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. - One design home. Decisions and deviations go in
docs/design.md; a drawing changed in the proposal canvas is re-exported intodocs/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_changesfrom 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 withsend_thread_message. Never hand over patch files, hash manifests, split archives or/tmpcheckouts, and never push tomainjust 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'scanvas.dbsrc/server/canvas-state.ts: the single source of truth and its single-slot listenerssrc/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.
- Never dismiss failing tests. Investigate every failure before declaring success. A "pre-existing" failure still needs resolution or explicit acknowledgment.
- 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
canvasservice in.amp/services.yaml(port 4313, with a portal); never start it as a shell background job:
Theamp 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 200pmx_canvas_healthtool (.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. - Test MCP server separately. The MCP server can be tested with
bun run src/mcp/server.tsand sending JSON-RPC over stdin. - 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, apanned: true, or updatednode.datais 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. - 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.
- 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 acceptancereruns only rows whose files changed since their last pass and writestest-results/acceptance/report.mdwith 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;ownerChecksare 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 newsrc/file joins a row (tests/unit/acceptance-guide.test.tsfails otherwise). At each release, retire rows the release made obsolete. - 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
.jsextensions for Bun module resolution - The
canvasStatesingleton is shared across HTTP handlers, MCP tools, and the SDK class - Client writes go through the bridge helpers (
requestJson/requestOk/requestBestEffortinsrc/client/state/intent-bridge.ts), never a barefetch: they carry theX-PMX-Workbenchmarker that tells the server the write is the human's. Without it the human's own click is booked as an anonymousapiagent — 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.tsxasserts 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. UseaskText/TextPrompt(src/client/canvas/TextPrompt.tsx) for input,BarHintfor hover hints, and an in-flow caption where an anchored tooltip would clip inside an overflow container.
Adding New Node Types
- Add the type string to the union in
src/server/canvas-state.ts(CanvasNodeState.type), pluscanvas-provenance.ts(CanvasNodeType),ax-interaction.ts(DEFAULT_NODE_AX_CAPABILITIES— exhaustive record),canvas-schema.ts(create schema),canvas-validation.ts(NODE_MIN_CREATE_SIZESfloor) - Create a renderer component in
src/client/nodes/YourNode.tsx, to its artboard indocs/design/(draw one first if none exists — see Design) - Add the case to the render switches in
src/client/canvas/CanvasViewport.tsxANDExpandedNodeOverlay.tsx, plus the exhaustive records instate/node-factory.ts,types.ts(union,TYPE_LABELS,EXPANDABLE_TYPES),icons.tsx - Add the type to
isCanvasNodeTypeinsrc/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 - Add to
NODE_TYPESinsrc/server/operations/ops/nodes.ts(MCP uses that same enum) and adefaultNodeSizecase - Update
SKILL.md,readme.md, anddocs/node-types.mdwith the new type - Regenerate the demo board (
bun run scripts/generate-demo-board.ts) and add the type to it — see Demo Board - 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
- Add the handler function in
src/server/server.ts - Add the route in the
Bun.servefetch handler - Add the corresponding method to
PmxCanvasclass insrc/server/index.ts - Add the MCP tool in
src/mcp/server.ts - Update
SKILL.md,readme.md, and CLI help text - Document it in the contract docs —
docs/http-api.md(anddocs/mcp.md/docs/cli.mdfor a tool/action/command).docs/api-stability.mddefines 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 compositeactionlists, the theme list, and the AX capability matrix inskills/pmx-canvas/references/full-reference.mdall 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 —
realpathSyncthe candidate and the workspace root before comparing. A lexicalresolve()/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-Typefrom the bytes, never from caller-writable node data, and sendX-Content-Type-Options: nosniffplus a non-inline disposition for anything not deliberately rendered. - Stream (
Bun.file) with a size ceiling and anisFile()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
- Create folder in
skills/with skill name (lowercase, hyphens) - Create
SKILL.mdwith YAML frontmatter:--- name: skill-name description: What it does and when to use it. --- - Add optional directories:
scripts/,references/,assets/ - Ensure folder name matches
namefield
Validating Skills
- Frontmatter has required
nameanddescription nameis lowercase, hyphens only, matches folderdescriptionexplains 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-skillsafter changing any mirrored skill files
Error Hygiene
When maintaining .learnings/:
- Keep only high-signal entries: unresolved blockers, recurring failures, or incidents that require durable guardrails
- Remove one-off resolved noise after extracting reusable guidance
- 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.jsonpoint at them). When present, keep them byte-identical; verify withbun run validate:agent-skills, which skips when they are absent - Use
skill-pipelineas 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-gatethensimplify-and-harden - Medium tasks: run
intent-framed-agent, thenverify-gate, thensimplify-and-harden - Large or long-running tasks: run
plan-interview, thenintent-framed-agentwithcontext-surfing, thenverify-gate, thensimplify-and-harden, thenself-improvement - Batch tasks: run
agent-teams-simplify-and-harden, thenself-improvement - CI/headless review: use
simplify-and-harden-ciandself-improvement-ci; uselearning-aggregator-ciandeval-creator-cifor the outer loop - Run
pre-flight-checkat session start when hooks are available; Claude hooks also wirecontext-surfinghandoff detection andself-improvementreminders - Use
learning-aggregatorandeval-creatorfor cross-session outer-loop improvement work
Version
- Imported pipeline version manifest:
.agents/skills/PIPELINE_VERSIONS.md - Canonical imported revision:
01ae6f8b3c9a0ab96e8ec87b27fdd88677696cdefromhttps://github.com/pskoett/pskoett-ai-skills
Browser Automation Visibility Rule
When using browser automation for UI investigation:
- Use a visible browser window (headed mode) so the user can see what is happening
- Do not run headless unless the user explicitly asks for it
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
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.

