pmx-canvas 0.1.18 → 0.1.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/CHANGELOG.md +128 -0
  2. package/Readme.md +19 -6
  3. package/dist/canvas/global.css +35 -2
  4. package/dist/canvas/index.js +70 -69
  5. package/dist/json-render/index.js +109 -109
  6. package/dist/types/client/canvas/CanvasViewport.d.ts +1 -1
  7. package/dist/types/client/icons.d.ts +2 -0
  8. package/dist/types/client/state/canvas-store.d.ts +2 -0
  9. package/dist/types/client/types.d.ts +2 -1
  10. package/dist/types/json-render/charts/components.d.ts +5 -1
  11. package/dist/types/json-render/renderer/index.d.ts +1 -0
  12. package/dist/types/json-render/server.d.ts +1 -0
  13. package/dist/types/mcp/canvas-access.d.ts +3 -0
  14. package/dist/types/server/canvas-operations.d.ts +4 -0
  15. package/dist/types/server/canvas-schema.d.ts +19 -3
  16. package/dist/types/server/canvas-serialization.d.ts +1 -0
  17. package/dist/types/server/canvas-state.d.ts +8 -2
  18. package/dist/types/server/html-primitives.d.ts +34 -0
  19. package/dist/types/server/index.d.ts +19 -0
  20. package/docs/RELEASE.md +153 -0
  21. package/docs/bun-webview-integration.md +296 -0
  22. package/docs/cli.md +143 -0
  23. package/docs/evals/e2e-cli-coverage.md +61 -0
  24. package/docs/http-api.md +201 -0
  25. package/docs/mcp.md +137 -0
  26. package/docs/node-types.md +272 -0
  27. package/docs/plans/.gitkeep +0 -0
  28. package/docs/plans/plan-001-semantic-watch-mvp.md +335 -0
  29. package/docs/plans/plan-002-human-attention-layer-design-spec.md +679 -0
  30. package/docs/plans/plan-003-human-attention-layer-implementation-plan.md +572 -0
  31. package/docs/reactive-canvas-proposal.md +578 -0
  32. package/docs/release-review-0.1.0.md +38 -0
  33. package/docs/screenshot.png +0 -0
  34. package/docs/screenshots/demo-workbench-dark.png +0 -0
  35. package/docs/screenshots/demo-workbench-light.png +0 -0
  36. package/docs/screenshots/welcome-dark.png +0 -0
  37. package/docs/screenshots/welcome-light.png +0 -0
  38. package/docs/sdk.md +103 -0
  39. package/package.json +2 -1
  40. package/skills/pmx-canvas/SKILL.md +8 -0
  41. package/src/cli/agent.ts +167 -5
  42. package/src/client/App.tsx +20 -1
  43. package/src/client/canvas/AnnotationLayer.tsx +33 -12
  44. package/src/client/canvas/CanvasViewport.tsx +88 -7
  45. package/src/client/canvas/CommandPalette.tsx +1 -1
  46. package/src/client/canvas/ContextMenu.tsx +2 -2
  47. package/src/client/canvas/ExpandedNodeOverlay.tsx +7 -1
  48. package/src/client/icons.tsx +13 -0
  49. package/src/client/nodes/McpAppNode.tsx +12 -4
  50. package/src/client/state/canvas-store.ts +15 -5
  51. package/src/client/state/sse-bridge.ts +4 -3
  52. package/src/client/theme/global.css +35 -2
  53. package/src/client/types.ts +2 -1
  54. package/src/json-render/charts/components.tsx +41 -7
  55. package/src/json-render/charts/extra-components.tsx +13 -12
  56. package/src/json-render/renderer/index.tsx +1 -0
  57. package/src/json-render/server.ts +3 -1
  58. package/src/mcp/canvas-access.ts +25 -0
  59. package/src/mcp/server.ts +85 -27
  60. package/src/server/agent-context.ts +17 -0
  61. package/src/server/canvas-operations.ts +91 -38
  62. package/src/server/canvas-schema.ts +83 -3
  63. package/src/server/canvas-serialization.ts +9 -2
  64. package/src/server/canvas-state.ts +27 -9
  65. package/src/server/demo-state.json +1143 -0
  66. package/src/server/demo.ts +25 -777
  67. package/src/server/html-primitives.ts +990 -0
  68. package/src/server/index.ts +43 -2
  69. package/src/server/server.ts +140 -14
  70. package/src/server/spatial-analysis.ts +3 -3
@@ -1,5 +1,5 @@
1
1
  import type { CanvasNodeState } from '../types';
2
- type AnnotationTool = 'pen' | 'eraser' | null;
2
+ type AnnotationTool = 'pen' | 'eraser' | 'text' | null;
3
3
  interface CanvasViewportProps {
4
4
  onNodeContextMenu?: (e: MouseEvent, nodeId: string) => void;
5
5
  onCanvasContextMenu?: (e: MouseEvent, canvasX: number, canvasY: number) => void;
@@ -23,6 +23,8 @@ export declare function IconMoon(p: IconProps): JSX.Element;
23
23
  export declare function IconPen(p: IconProps): JSX.Element;
24
24
  /** Eraser — remove canvas annotations */
25
25
  export declare function IconEraser(p: IconProps): JSX.Element;
26
+ /** Text cursor — canvas text annotation mode */
27
+ export declare function IconTextAnnotation(p: IconProps): JSX.Element;
26
28
  /** Camera — snapshots */
27
29
  export declare function IconSnapshot(p: IconProps): JSX.Element;
28
30
  /** Bullseye — trace toggle */
@@ -45,9 +45,11 @@ export declare function removeEdgesForNode(nodeId: string): void;
45
45
  export declare function addAnnotation(annotation: CanvasAnnotation): void;
46
46
  export declare function removeAnnotation(id: string): void;
47
47
  export declare function createAnnotationFromClient(input: {
48
+ type?: CanvasAnnotation['type'];
48
49
  points: CanvasAnnotation['points'];
49
50
  color: string;
50
51
  width: number;
52
+ text?: string;
51
53
  label?: string;
52
54
  }): Promise<{
53
55
  ok: boolean;
@@ -35,7 +35,7 @@ export interface CanvasAnnotationPoint {
35
35
  }
36
36
  export interface CanvasAnnotation {
37
37
  id: string;
38
- type: 'freehand';
38
+ type: 'freehand' | 'text';
39
39
  points: CanvasAnnotationPoint[];
40
40
  bounds: {
41
41
  x: number;
@@ -45,6 +45,7 @@ export interface CanvasAnnotation {
45
45
  };
46
46
  color: string;
47
47
  width: number;
48
+ text?: string;
48
49
  label?: string;
49
50
  createdAt: string;
50
51
  }
@@ -6,7 +6,7 @@
6
6
  * chat example. Each component receives BaseComponentProps<T> and renders
7
7
  * a responsive chart inside a styled container.
8
8
  */
9
- import type { ReactNode } from 'react';
9
+ import { type ReactNode } from 'react';
10
10
  import type { BaseComponentProps } from '@json-render/react';
11
11
  export declare const CHART_COLORS: string[];
12
12
  export type AggregateMode = 'sum' | 'count' | 'avg';
@@ -56,6 +56,10 @@ export declare const axisTickMargin = 8;
56
56
  export declare const legendMargin: {
57
57
  top: number;
58
58
  };
59
+ export declare function useChartFrameHeight(explicitHeight: number | null | undefined, fallbackHeight?: number): {
60
+ frameRef: import("react").RefObject<HTMLDivElement | null>;
61
+ height: number;
62
+ };
59
63
  /** Shared wrapper for cartesian charts (Line + Bar). */
60
64
  export declare function CartesianChart({ props, children, className, }: {
61
65
  props: CartesianChartProps;
@@ -12,5 +12,6 @@ declare global {
12
12
  state?: Record<string, unknown>;
13
13
  };
14
14
  __PMX_CANVAS_JSON_RENDER_THEME__?: string;
15
+ __PMX_CANVAS_JSON_RENDER_DISPLAY__?: string;
15
16
  }
16
17
  }
@@ -58,4 +58,5 @@ export declare function buildJsonRenderViewerHtml(options: {
58
58
  title: string;
59
59
  spec: JsonRenderSpec;
60
60
  theme?: 'dark' | 'light' | 'high-contrast';
61
+ display?: 'expanded';
61
62
  }): Promise<string>;
@@ -8,6 +8,8 @@ type AddDiagramInput = Parameters<PmxCanvas['addDiagram']>[0];
8
8
  type AddJsonRenderNodeInput = Parameters<PmxCanvas['addJsonRenderNode']>[0];
9
9
  type AddJsonRenderNodeResult = ReturnType<PmxCanvas['addJsonRenderNode']>;
10
10
  type AddHtmlNodeInput = Parameters<PmxCanvas['addHtmlNode']>[0];
11
+ type AddHtmlPrimitiveInput = Parameters<PmxCanvas['addHtmlPrimitive']>[0];
12
+ type AddHtmlPrimitiveResult = ReturnType<PmxCanvas['addHtmlPrimitive']>;
11
13
  type AddGraphNodeInput = Parameters<PmxCanvas['addGraphNode']>[0];
12
14
  type AddGraphNodeResult = ReturnType<PmxCanvas['addGraphNode']>;
13
15
  type UpdateNodePatch = Parameters<PmxCanvas['updateNode']>[1];
@@ -50,6 +52,7 @@ export interface CanvasAccess {
50
52
  addDiagram(input: AddDiagramInput): Promise<OpenMcpAppResult>;
51
53
  addJsonRenderNode(input: AddJsonRenderNodeInput): Promise<AddJsonRenderNodeResult>;
52
54
  addHtmlNode(input: AddHtmlNodeInput): Promise<string>;
55
+ addHtmlPrimitive(input: AddHtmlPrimitiveInput): Promise<AddHtmlPrimitiveResult>;
53
56
  addGraphNode(input: AddGraphNodeInput): Promise<AddGraphNodeResult>;
54
57
  buildWebArtifact(input: WebArtifactInput): Promise<WebArtifactResult>;
55
58
  updateNode(id: string, patch: UpdateNodePatch): Promise<void>;
@@ -56,6 +56,10 @@ interface CanvasAddNodeInput {
56
56
  fileMode?: 'path' | 'inline' | 'auto';
57
57
  strictSize?: boolean;
58
58
  }
59
+ export declare const MARKDOWN_NODE_DEFAULT_SIZE: {
60
+ width: number;
61
+ height: number;
62
+ };
59
63
  interface CanvasCreateGroupInput {
60
64
  title?: string;
61
65
  childIds?: string[];
@@ -1,5 +1,6 @@
1
1
  import { type JsonRenderComponentDescriptor } from '../json-render/catalog.js';
2
2
  import { type GraphNodeInput, type JsonRenderSpec } from '../json-render/server.js';
3
+ import { type HtmlPrimitiveDescriptor } from './html-primitives.js';
3
4
  export interface CanvasCreateField {
4
5
  name: string;
5
6
  type: string;
@@ -19,8 +20,17 @@ export interface CanvasCreateTypeSchema {
19
20
  }
20
21
  export interface StructuredValidationResult {
21
22
  ok: true;
22
- type: 'json-render' | 'graph';
23
- normalizedSpec: JsonRenderSpec;
23
+ type: 'json-render' | 'graph' | 'html-primitive';
24
+ normalizedSpec?: JsonRenderSpec;
25
+ normalizedPrimitive?: {
26
+ kind: string;
27
+ title: string;
28
+ htmlBytes: number;
29
+ defaultSize: {
30
+ width: number;
31
+ height: number;
32
+ };
33
+ };
24
34
  summary: Record<string, unknown>;
25
35
  }
26
36
  declare const CANONICAL_GRAPH_TYPES: readonly ["line", "bar", "pie", "area", "scatter", "radar", "stacked-bar", "composed"];
@@ -37,6 +47,7 @@ export declare function describeCanvasSchema(): {
37
47
  graph: {
38
48
  graphTypes: CanvasGraphType[];
39
49
  };
50
+ htmlPrimitives: HtmlPrimitiveDescriptor[];
40
51
  mcp: {
41
52
  tools: string[];
42
53
  resources: string[];
@@ -44,8 +55,13 @@ export declare function describeCanvasSchema(): {
44
55
  };
45
56
  };
46
57
  export declare function validateStructuredCanvasPayload(input: {
47
- type: 'json-render' | 'graph';
58
+ type: 'json-render' | 'graph' | 'html-primitive';
48
59
  spec?: unknown;
49
60
  graph?: GraphNodeInput;
61
+ primitive?: {
62
+ kind: string;
63
+ title?: string;
64
+ data?: Record<string, unknown>;
65
+ };
50
66
  }): StructuredValidationResult;
51
67
  export {};
@@ -18,6 +18,7 @@ export interface CanvasAnnotationSummary {
18
18
  color: string;
19
19
  width: number;
20
20
  pointCount: number;
21
+ text: string | null;
21
22
  label: string | null;
22
23
  createdAt: string;
23
24
  }
@@ -39,6 +39,8 @@ export interface CanvasSnapshot {
39
39
  export interface CanvasSnapshotListOptions {
40
40
  limit?: number;
41
41
  query?: string;
42
+ before?: string;
43
+ after?: string;
42
44
  all?: boolean;
43
45
  }
44
46
  export interface CanvasSnapshotGcOptions {
@@ -88,7 +90,7 @@ export interface CanvasAnnotationPoint {
88
90
  }
89
91
  export interface CanvasAnnotation {
90
92
  id: string;
91
- type: 'freehand';
93
+ type: 'freehand' | 'text';
92
94
  points: CanvasAnnotationPoint[];
93
95
  bounds: {
94
96
  x: number;
@@ -98,6 +100,7 @@ export interface CanvasAnnotation {
98
100
  };
99
101
  color: string;
100
102
  width: number;
103
+ text?: string;
101
104
  label?: string;
102
105
  createdAt: string;
103
106
  }
@@ -132,6 +135,9 @@ interface GroupNodesOptions {
132
135
  layout?: 'grid' | 'column' | 'flow';
133
136
  keepGroupFrame?: boolean;
134
137
  }
138
+ interface ApplyUpdatesOptions {
139
+ skipGroupChildTranslation?: boolean;
140
+ }
135
141
  declare class CanvasStateManager {
136
142
  private nodes;
137
143
  private edges;
@@ -229,7 +235,7 @@ declare class CanvasStateManager {
229
235
  private removeEdgesForNode;
230
236
  getLayout(): CanvasLayout;
231
237
  getLayoutForPersistence(): CanvasLayout;
232
- applyUpdates(updates: CanvasNodeUpdate[]): {
238
+ applyUpdates(updates: CanvasNodeUpdate[], options?: ApplyUpdatesOptions): {
233
239
  applied: number;
234
240
  skipped: number;
235
241
  };
@@ -0,0 +1,34 @@
1
+ export declare const HTML_PRIMITIVE_KINDS: readonly ["choice-grid", "plan-timeline", "review-sheet", "pr-writeup", "system-map", "code-walkthrough", "design-sheet", "component-gallery", "interaction-prototype", "flowchart", "deck", "illustration-set", "explainer", "status-report", "incident-report", "triage-board", "config-editor", "prompt-tuner"];
2
+ export type HtmlPrimitiveKind = typeof HTML_PRIMITIVE_KINDS[number];
3
+ export interface HtmlPrimitiveDescriptor {
4
+ kind: HtmlPrimitiveKind;
5
+ title: string;
6
+ description: string;
7
+ useWhen: string;
8
+ defaultSize: {
9
+ width: number;
10
+ height: number;
11
+ };
12
+ dataShape: string;
13
+ example: Record<string, unknown>;
14
+ }
15
+ export interface HtmlPrimitiveInput {
16
+ kind: HtmlPrimitiveKind;
17
+ title?: string;
18
+ data?: Record<string, unknown>;
19
+ }
20
+ export interface HtmlPrimitiveBuildResult {
21
+ kind: HtmlPrimitiveKind;
22
+ title: string;
23
+ html: string;
24
+ summary: string;
25
+ defaultSize: {
26
+ width: number;
27
+ height: number;
28
+ };
29
+ data: Record<string, unknown>;
30
+ }
31
+ export declare function isHtmlPrimitiveKind(value: string): value is HtmlPrimitiveKind;
32
+ export declare function getHtmlPrimitiveDescriptor(kind: HtmlPrimitiveKind): HtmlPrimitiveDescriptor;
33
+ export declare function listHtmlPrimitiveDescriptors(): HtmlPrimitiveDescriptor[];
34
+ export declare function buildHtmlPrimitive(input: HtmlPrimitiveInput): HtmlPrimitiveBuildResult;
@@ -3,6 +3,7 @@ import type { CanvasAnnotation, CanvasNodeState, CanvasEdge, CanvasLayout } from
3
3
  import { searchNodes } from './spatial-analysis.js';
4
4
  import { diffLayouts } from './mutation-history.js';
5
5
  import { fitCanvasView, gcCanvasSnapshots, listCanvasSnapshots } from './canvas-operations.js';
6
+ import type { HtmlPrimitiveKind } from './html-primitives.js';
6
7
  import { type WebArtifactBuildInput, type WebArtifactCanvasBuildResult } from './web-artifacts.js';
7
8
  import { type ExternalMcpTransportConfig } from './mcp-app-runtime.js';
8
9
  import { type DiagramPresetOpenInput } from './diagram-presets.js';
@@ -181,6 +182,7 @@ export declare class PmxCanvas extends EventEmitter {
181
182
  graph: {
182
183
  graphTypes: ("line" | "bar" | "pie" | "area" | "scatter" | "radar" | "composed" | "stacked-bar")[];
183
184
  };
185
+ htmlPrimitives: import("./html-primitives.js").HtmlPrimitiveDescriptor[];
184
186
  mcp: {
185
187
  tools: string[];
186
188
  resources: string[];
@@ -248,6 +250,21 @@ export declare class PmxCanvas extends EventEmitter {
248
250
  height?: number;
249
251
  strictSize?: boolean;
250
252
  }): string;
253
+ addHtmlPrimitive(input: {
254
+ kind: HtmlPrimitiveKind;
255
+ title?: string;
256
+ data?: Record<string, unknown>;
257
+ x?: number;
258
+ y?: number;
259
+ width?: number;
260
+ height?: number;
261
+ strictSize?: boolean;
262
+ }): {
263
+ id: string;
264
+ kind: HtmlPrimitiveKind;
265
+ title: string;
266
+ htmlBytes: number;
267
+ };
251
268
  addGraphNode(input: GraphNodeInput): {
252
269
  id: string;
253
270
  url: string;
@@ -275,10 +292,12 @@ export type { SpatialCluster, SpatialContext, SpatialNeighbor, NodeSpatialInfo }
275
292
  export { mutationHistory, diffLayouts, formatDiff } from './mutation-history.js';
276
293
  export { recomputeCodeGraph, buildCodeGraphSummary, formatCodeGraph } from './code-graph.js';
277
294
  export { describeCanvasSchema, validateStructuredCanvasPayload } from './canvas-schema.js';
295
+ export { buildHtmlPrimitive, isHtmlPrimitiveKind, listHtmlPrimitiveDescriptors } from './html-primitives.js';
278
296
  export { buildWebArtifactOnCanvas, executeWebArtifactBuild, openWebArtifactInCanvas, resolveWebArtifactScriptPath, resolveWorkspacePath, } from './web-artifacts.js';
279
297
  export { buildGraphSpec, buildJsonRenderViewerHtml, createJsonRenderNodeData, GRAPH_NODE_SIZE, JSON_RENDER_NODE_SIZE, normalizeAndValidateJsonRenderSpec, } from '../json-render/server.js';
280
298
  export type { CodeGraphSummary, CodeGraphEdge } from './code-graph.js';
281
299
  export type { MutationEntry, MutationSummary, SnapshotDiffResult } from './mutation-history.js';
282
300
  export type { WebArtifactBuildInput, WebArtifactBuildOutput, WebArtifactCanvasBuildResult, WebArtifactCanvasOpenResult, } from './web-artifacts.js';
283
301
  export type { GraphNodeInput, JsonRenderNodeInput, JsonRenderSpec } from '../json-render/server.js';
302
+ export type { HtmlPrimitiveKind, HtmlPrimitiveDescriptor, HtmlPrimitiveInput, HtmlPrimitiveBuildResult } from './html-primitives.js';
284
303
  export { traceManager } from './trace-manager.js';
@@ -0,0 +1,153 @@
1
+ # Release process
2
+
3
+ Internal recipe for cutting a new `pmx-canvas` release. Lives in `docs/`
4
+ rather than the README so end-user agents and humans driving the canvas
5
+ aren't distracted by maintainer-only flow. Agents working **on** this
6
+ repo (improving the canvas itself) should treat this file as the
7
+ single source of truth for the release dance — see also
8
+ [`AGENTS.md`](../AGENTS.md) and [`CLAUDE.md`](../CLAUDE.md).
9
+
10
+ ## TL;DR
11
+
12
+ 1. Land all the changes you want in the release on `main` with green CI.
13
+ 2. Bump `package.json` `version`.
14
+ 3. Add a `## [X.Y.Z]` block to `CHANGELOG.md`.
15
+ 4. Commit, push, wait for `test.yml` green.
16
+ 5. `git tag -a vX.Y.Z -m "..." && git push origin vX.Y.Z` →
17
+ `publish.yml` runs `npm publish --access public --provenance`.
18
+ 6. `gh release create vX.Y.Z --notes-file <notes.md>`.
19
+ 7. Smoke-test the published tarball: `bunx pmx-canvas@X.Y.Z --no-open`.
20
+
21
+ ## Pre-flight gates
22
+
23
+ Run these locally before tagging. They mirror what `publish.yml`
24
+ re-runs in CI; a failure here means the publish workflow will also fail.
25
+
26
+ ```bash
27
+ bun install --frozen-lockfile
28
+ bun run typecheck
29
+ bun run build
30
+ bun test tests/unit # 200+ tests, all green
31
+ bun run test:web-canvas # Playwright E2E, all green
32
+ bun run test:e2e-cli # fresh-workspace CLI eval
33
+ bun run release:check # bundles + lints
34
+ bun run release:smoke # packs + boots from a clean dir
35
+ bun run pack:dry-run # confirms the tarball shape
36
+ ```
37
+
38
+ `bun run test:e2e-cli` starts a local server in a fresh temp workspace
39
+ and exercises the CLI flows from
40
+ [`docs/evals/e2e-cli-coverage.md`](evals/e2e-cli-coverage.md).
41
+
42
+ ## Versioning
43
+
44
+ Semantic versioning. While we are still in `0.1.x`:
45
+
46
+ - Patch (`0.1.x → 0.1.x+1`): bug fixes, hardening, internal cleanups,
47
+ additive CLI flags / MCP fields with backwards-compat fallbacks.
48
+ - Minor (`0.1.x → 0.2.0`): documented breaking changes (e.g. dropping
49
+ the `hostMode + path` fallback in `getCanvasNodeKind`, removing legacy
50
+ `ext-app-ext-app-…` ID handling, JSON-shape changes that aren't
51
+ additive).
52
+ - Major (`0.x → 1.0`): production-stability commitment.
53
+
54
+ CLAUDE.md rule #5 (CanvasStateManager / PmxCanvas SDK / HTTP / MCP
55
+ four-layer parity) is the strongest hard rule for what counts as
56
+ non-breaking — a CLI/HTTP-only addition without MCP parity has
57
+ historically required a follow-up patch release to restore parity.
58
+
59
+ ## CHANGELOG
60
+
61
+ `CHANGELOG.md` follows [Keep a Changelog](https://keepachangelog.com/).
62
+ Each release section uses these subheadings:
63
+
64
+ - **Added** — new public surface (HTTP endpoint, MCP tool/resource, CLI
65
+ command/flag, SDK export, JSON shape).
66
+ - **Changed** — behavior changes that aren't strictly bug fixes
67
+ (response shape additions, schema cleanups, stricter validation).
68
+ - **Fixed** — bug fixes.
69
+ - **Internal** — refactors, test additions, docs that don't affect
70
+ the public surface.
71
+
72
+ Don't ship a release without a CHANGELOG entry. The GitHub release
73
+ notes file (`/tmp/pmx-canvas-vX.Y.Z-release-notes.md` by convention)
74
+ expands on the CHANGELOG with examples and migration notes.
75
+
76
+ ## Tag → publish
77
+
78
+ The publish workflow ([`/.github/workflows/publish.yml`](../.github/workflows/publish.yml))
79
+ triggers on tags matching `v*`:
80
+
81
+ ```bash
82
+ git tag -a v0.1.6 -m "v0.1.6 — short summary"
83
+ git push origin v0.1.6
84
+ ```
85
+
86
+ It will:
87
+
88
+ 1. Verify the tag matches `package.json` version.
89
+ 2. Re-run typecheck / build / unit / E2E / pack.
90
+ 3. `npm publish --access public --provenance` using the `NPM_TOKEN`
91
+ secret. Provenance attestations are signed via sigstore and visible
92
+ on the npm package page.
93
+
94
+ Watch it complete:
95
+
96
+ ```bash
97
+ gh run watch
98
+ ```
99
+
100
+ If publish fails after npm has accepted the tarball, you cannot reuse
101
+ the same version number. Bump the patch and re-tag.
102
+
103
+ If this is the first release from your machine, run `bunx npm login`
104
+ once so Bun can reuse your npm credentials. CI does not need this —
105
+ it uses `NPM_TOKEN` directly.
106
+
107
+ ## GitHub release
108
+
109
+ After `npm publish` succeeds:
110
+
111
+ ```bash
112
+ gh release create v0.1.6 \
113
+ --title "pmx-canvas 0.1.6 — short theme" \
114
+ --notes-file /tmp/pmx-canvas-v0.1.6-release-notes.md \
115
+ --verify-tag
116
+ ```
117
+
118
+ `--verify-tag` makes the command fail if the local tag drifted from
119
+ the remote — a small but useful guard.
120
+
121
+ ## Smoke test from npm
122
+
123
+ ```bash
124
+ cd /tmp && rm -rf smoke && mkdir smoke && cd smoke
125
+ bunx --bun pmx-canvas@<version> --no-open --port=4926 > smoke.log &
126
+ SP=$!
127
+ for i in 1 2 3 4 5 6 7 8 9 10; do
128
+ curl -sf http://localhost:4926/health >/dev/null 2>&1 && break
129
+ sleep 1
130
+ done
131
+ curl -s http://localhost:4926/health # → {"ok":true,"workspace":"…"}
132
+ bunx pmx-canvas@<version> --version # → <version>
133
+ kill $SP
134
+ ```
135
+
136
+ If `bunx pmx-canvas@<version>` resolves and the health endpoint replies
137
+ with `ok: true`, the published tarball is intact end-to-end.
138
+
139
+ ## Common gotchas
140
+
141
+ - **`/.pmx-canvas/artifacts/.web-artifacts/sdlc-control-room/.parcel-cache/`**
142
+ files always show as modified after running an artifact build. They
143
+ are workspace-local cache and must not be committed; they're gitignored
144
+ but still appear in `git status`. Use `git restore --staged` if they
145
+ sneak into a `git add -A`.
146
+ - **`docs/screenshot.png`** updates whenever the showcase E2E runs.
147
+ Don't bake those updates into a release commit unless the screenshot
148
+ in `Readme.md` actually needs the refresh.
149
+ - **CHANGELOG dates**: use the actual publish date in the
150
+ `## [version] - YYYY-MM-DD` header, not the day you wrote the entry.
151
+ - **Node 24 deprecation timeline**: GitHub Actions is migrating
152
+ `actions/checkout`, `setup-node`, `upload-artifact` to Node 24 by
153
+ June 2, 2026. The publish workflow already pins `@v5` of all three.