pmx-canvas 0.6.5 → 0.7.0

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 (169) hide show
  1. package/.github/extensions/pmx-canvas/extension.mjs +67 -3
  2. package/CHANGELOG.md +54 -0
  3. package/Readme.md +29 -5
  4. package/dist/canvas/global.css +440 -24
  5. package/dist/canvas/index.js +90 -75
  6. package/dist/types/cli/agent.d.ts +3 -0
  7. package/dist/types/cli/commands/backup.d.ts +1 -0
  8. package/dist/types/cli/commands/boards.d.ts +1 -0
  9. package/dist/types/cli/commands/export.d.ts +1 -0
  10. package/dist/types/client/canvas/BoardCopyDialog.d.ts +5 -0
  11. package/dist/types/client/canvas/BoardFolderDialog.d.ts +7 -0
  12. package/dist/types/client/canvas/BoardLinkDialog.d.ts +3 -0
  13. package/dist/types/client/canvas/BoardSwitcher.d.ts +8 -0
  14. package/dist/types/client/canvas/ExportDialog.d.ts +7 -0
  15. package/dist/types/client/canvas/HomeView.d.ts +9 -0
  16. package/dist/types/client/nodes/AttachmentNode.d.ts +6 -0
  17. package/dist/types/client/nodes/BoardNode.d.ts +5 -0
  18. package/dist/types/client/state/boards-store.d.ts +79 -0
  19. package/dist/types/client/state/canvas-store.d.ts +2 -0
  20. package/dist/types/client/state/intent-bridge.d.ts +1 -0
  21. package/dist/types/client/state/session-store.d.ts +7 -0
  22. package/dist/types/client/types.d.ts +2 -2
  23. package/dist/types/json-render/catalog.d.ts +1 -1
  24. package/dist/types/json-render/charts/definitions.d.ts +1 -1
  25. package/dist/types/json-render/server.d.ts +6 -0
  26. package/dist/types/mcp/canvas-access.d.ts +10 -3
  27. package/dist/types/mcp/context-uri-template.d.ts +6 -0
  28. package/dist/types/server/agent-presence.d.ts +1 -1
  29. package/dist/types/server/attribution.d.ts +11 -0
  30. package/dist/types/server/ax-context.d.ts +3 -1
  31. package/dist/types/server/ax-interaction.d.ts +2 -2
  32. package/dist/types/server/ax-state.d.ts +13 -3
  33. package/dist/types/server/backup.d.ts +36 -0
  34. package/dist/types/server/board-export.d.ts +45 -0
  35. package/dist/types/server/canvas-db.d.ts +63 -9
  36. package/dist/types/server/canvas-operations.d.ts +23 -0
  37. package/dist/types/server/canvas-provenance.d.ts +1 -1
  38. package/dist/types/server/canvas-state.d.ts +140 -3
  39. package/dist/types/server/context-brief.d.ts +111 -0
  40. package/dist/types/server/context-reads.d.ts +55 -0
  41. package/dist/types/server/document-import.d.ts +66 -0
  42. package/dist/types/server/index.d.ts +103 -4
  43. package/dist/types/server/mutation-history.d.ts +1 -0
  44. package/dist/types/server/operations/invoker.d.ts +2 -0
  45. package/dist/types/server/operations/mcp.d.ts +7 -1
  46. package/dist/types/server/operations/ops/ax-shared.d.ts +2 -2
  47. package/dist/types/server/operations/ops/backup.d.ts +2 -0
  48. package/dist/types/server/operations/ops/boards.d.ts +10 -0
  49. package/dist/types/server/operations/ops/export.d.ts +2 -0
  50. package/dist/types/server/operations/ops/imports.d.ts +2 -0
  51. package/dist/types/server/operations/ops/nodes.d.ts +1 -1
  52. package/dist/types/server/operations/ops/query.d.ts +2 -0
  53. package/dist/types/server/operations/registry.d.ts +9 -1
  54. package/dist/types/server/operations/types.d.ts +1 -1
  55. package/dist/types/server/server.d.ts +1 -0
  56. package/dist/types/server/trace-manager.d.ts +7 -0
  57. package/dist/types/server/webpage-node.d.ts +22 -1
  58. package/dist/types/server/workbench-auth.d.ts +3 -0
  59. package/dist/types/shared/boards.d.ts +6 -0
  60. package/docs/RELEASE.md +5 -0
  61. package/docs/cli.md +68 -0
  62. package/docs/http-api.md +198 -2
  63. package/docs/mcp.md +71 -6
  64. package/docs/node-types.md +26 -0
  65. package/docs/plans/plan-010-attributed-ask-answers.md +39 -0
  66. package/docs/plans/plan-011-context-read-instrumentation.md +54 -0
  67. package/docs/plans/plan-012-many-boards-stage-1.md +98 -0
  68. package/docs/plans/plan-013-static-board-export.md +47 -0
  69. package/docs/plans/plan-014-connected-board-memory.md +140 -0
  70. package/docs/product-vision-2026-09.md +118 -18
  71. package/docs/sdk.md +50 -0
  72. package/package.json +1 -1
  73. package/skills/pmx-canvas/SKILL.md +53 -4
  74. package/skills/pmx-canvas/references/full-reference.md +43 -7
  75. package/skills/pmx-canvas/references/github-copilot-app-adapter.md +3 -0
  76. package/skills/pmx-canvas/references/installing-pmx-canvas.md +4 -4
  77. package/skills/pmx-canvas-orchestration/SKILL.md +5 -0
  78. package/skills/pmx-canvas-testing/SKILL.md +4 -0
  79. package/src/cli/agent.ts +4 -0
  80. package/src/cli/commands/ax.ts +13 -0
  81. package/src/cli/commands/backup.ts +66 -0
  82. package/src/cli/commands/boards.ts +71 -0
  83. package/src/cli/commands/export.ts +30 -0
  84. package/src/cli/commands/nodes.ts +4 -1
  85. package/src/cli/commands/pins.ts +6 -2
  86. package/src/cli/commands/pump.ts +3 -0
  87. package/src/cli/commands/query.ts +33 -7
  88. package/src/cli/commands/view.ts +7 -5
  89. package/src/cli/index.ts +19 -9
  90. package/src/client/App.tsx +21 -3
  91. package/src/client/canvas/BoardCopyDialog.tsx +153 -0
  92. package/src/client/canvas/BoardFolderDialog.tsx +120 -0
  93. package/src/client/canvas/BoardLinkDialog.tsx +93 -0
  94. package/src/client/canvas/BoardSwitcher.tsx +149 -0
  95. package/src/client/canvas/CanvasViewport.tsx +3 -0
  96. package/src/client/canvas/CommandPalette.tsx +107 -4
  97. package/src/client/canvas/ContextMenu.tsx +4 -0
  98. package/src/client/canvas/ExpandedNodeOverlay.tsx +3 -0
  99. package/src/client/canvas/ExportDialog.tsx +197 -0
  100. package/src/client/canvas/HomeView.tsx +277 -0
  101. package/src/client/canvas/SessionPanel.tsx +28 -1
  102. package/src/client/canvas/ToolRail.tsx +38 -3
  103. package/src/client/canvas/TopBar.tsx +34 -18
  104. package/src/client/canvas/import-files.ts +19 -1
  105. package/src/client/canvas/kind-colors.ts +1 -0
  106. package/src/client/icons.tsx +2 -0
  107. package/src/client/nodes/AttachmentNode.tsx +148 -0
  108. package/src/client/nodes/BoardNode.tsx +35 -0
  109. package/src/client/nodes/FileNode.tsx +6 -0
  110. package/src/client/nodes/MarkdownNode.tsx +47 -19
  111. package/src/client/nodes/ax-node-actions.ts +2 -5
  112. package/src/client/nodes/use-ax-surface-bridge.ts +1 -1
  113. package/src/client/state/boards-store.ts +167 -0
  114. package/src/client/state/canvas-store.ts +22 -0
  115. package/src/client/state/intent-bridge.ts +30 -2
  116. package/src/client/state/node-factory.ts +1 -0
  117. package/src/client/state/session-store.ts +5 -0
  118. package/src/client/state/sse-bridge.ts +49 -5
  119. package/src/client/theme/global.css +440 -24
  120. package/src/client/types.ts +2 -0
  121. package/src/json-render/server.ts +16 -9
  122. package/src/mcp/canvas-access.ts +59 -22
  123. package/src/mcp/context-uri-template.ts +28 -0
  124. package/src/mcp/server.ts +122 -15
  125. package/src/server/agent-context.ts +116 -29
  126. package/src/server/attribution.ts +29 -0
  127. package/src/server/ax-context.ts +4 -2
  128. package/src/server/ax-interaction.ts +1 -0
  129. package/src/server/ax-state-manager.ts +72 -3
  130. package/src/server/ax-state.ts +41 -0
  131. package/src/server/backup.ts +178 -0
  132. package/src/server/board-export.ts +686 -0
  133. package/src/server/canvas-db.ts +599 -107
  134. package/src/server/canvas-operations.ts +68 -9
  135. package/src/server/canvas-provenance.ts +1 -0
  136. package/src/server/canvas-schema.ts +12 -0
  137. package/src/server/canvas-state.ts +774 -45
  138. package/src/server/context-brief.ts +372 -0
  139. package/src/server/context-reads.ts +238 -0
  140. package/src/server/demo-state.json +659 -2
  141. package/src/server/document-import.ts +223 -0
  142. package/src/server/index.ts +231 -5
  143. package/src/server/mutation-history.ts +10 -13
  144. package/src/server/operations/composites.ts +26 -4
  145. package/src/server/operations/http.ts +9 -3
  146. package/src/server/operations/index.ts +8 -0
  147. package/src/server/operations/invoker.ts +14 -3
  148. package/src/server/operations/mcp.ts +50 -11
  149. package/src/server/operations/ops/app.ts +5 -0
  150. package/src/server/operations/ops/ax-read.ts +10 -6
  151. package/src/server/operations/ops/ax-timeline.ts +75 -0
  152. package/src/server/operations/ops/backup.ts +134 -0
  153. package/src/server/operations/ops/batch.ts +8 -1
  154. package/src/server/operations/ops/boards.ts +351 -0
  155. package/src/server/operations/ops/export.ts +74 -0
  156. package/src/server/operations/ops/ext-app.ts +4 -0
  157. package/src/server/operations/ops/imports.ts +220 -0
  158. package/src/server/operations/ops/nodes.ts +57 -5
  159. package/src/server/operations/ops/query.ts +206 -9
  160. package/src/server/operations/registry.ts +123 -31
  161. package/src/server/operations/types.ts +1 -1
  162. package/src/server/server.ts +146 -2
  163. package/src/server/spatial-analysis.ts +9 -17
  164. package/src/server/trace-manager.ts +19 -0
  165. package/src/server/web-artifacts.ts +2 -1
  166. package/src/server/webpage-node.ts +173 -13
  167. package/src/server/workbench-auth.ts +11 -0
  168. package/src/shared/boards.ts +18 -0
  169. package/src/shared/node-sizes.ts +1 -0
@@ -60,6 +60,26 @@
60
60
  --hud-bar-height: 44px;
61
61
  }
62
62
 
63
+ .board-node, .md-card-preview { display: flex; flex-direction: column; height: 100%; min-height: 0; }
64
+ .board-node-content { flex: 1; min-height: 0; overflow: auto; padding: 16px; overflow-wrap: anywhere; }
65
+ .board-node-label { color: var(--c-dim); font-size: 10px; font-weight: 600; text-transform: uppercase; letter-spacing: .1em; margin-bottom: 8px; }
66
+ .board-node-title { display: block; font-size: 17px; line-height: 1.35; margin-bottom: 6px; }
67
+ .board-node-meta { color: var(--c-dim); font-size: 11px; line-height: 1.5; }
68
+ .board-node-summary { color: var(--c-text-soft); font-size: 13px; line-height: 1.6; margin: 14px 0; }
69
+ .board-node-open { display: flex; justify-content: space-between; align-items: center; flex-shrink: 0; width: 100%; padding: 10px 16px; border: 0; border-top: 1px solid var(--c-line); background: var(--c-panel); color: var(--c-accent); font: 500 12px var(--font); text-align: left; cursor: pointer; }
70
+ .board-node-open:hover:not(:disabled) { background: var(--c-accent-8); }
71
+ .board-node-open:disabled { color: var(--c-dim); cursor: default; }
72
+ .md-card-content { flex: 1; min-height: 0; overflow: auto; }
73
+ .md-card-actions { display: flex; align-items: center; gap: 8px; flex-shrink: 0; border-top: 1px solid var(--c-line); padding: 6px 36px 6px 8px; background: var(--c-panel); }
74
+ .md-card-actions button { border: 1px solid transparent; border-radius: var(--radius-sm); background: transparent; color: var(--c-dim); padding: 4px 8px; font: 500 11px var(--font); cursor: pointer; }
75
+ .md-card-actions button:hover { color: var(--c-text); background: var(--c-accent-8); }
76
+ .md-card-actions .md-readme-btn[aria-pressed="true"] { color: var(--c-accent); background: var(--c-accent-8); border-color: var(--c-accent-25); }
77
+ .md-card-edit { margin-left: auto; }
78
+ .board-node-open:focus-visible, .md-card-actions button:focus-visible { outline: 2px solid var(--c-accent); outline-offset: -2px; }
79
+ .canvas-node[data-node-type="markdown"] .node-body,
80
+ .canvas-node[data-node-type="board"] .node-body { padding: 0; }
81
+ .md-card-content { padding: 10px 14px; }
82
+
63
83
  :root[data-theme="light"] {
64
84
  /* ── Core palette (light · paper + ink) ──────────────────── */
65
85
  --c-bg: #F4EFE6;
@@ -1251,7 +1271,7 @@ body,
1251
1271
  }
1252
1272
 
1253
1273
  .agent-chip-end:hover {
1254
- color: var(--c-danger, #ef4444);
1274
+ color: var(--c-danger);
1255
1275
  background: color-mix(in srgb, currentColor 12%, transparent);
1256
1276
  }
1257
1277
 
@@ -3317,29 +3337,6 @@ body,
3317
3337
  }
3318
3338
 
3319
3339
  /* Raw markdown source editor */
3320
- .md-edit-btn {
3321
- position: absolute;
3322
- top: 4px;
3323
- right: 4px;
3324
- padding: 3px 8px;
3325
- font-size: 10px;
3326
- background: var(--c-panel);
3327
- border: 1px solid var(--c-line);
3328
- border-radius: 4px;
3329
- color: var(--c-text-soft);
3330
- cursor: pointer;
3331
- opacity: 0;
3332
- transition: opacity 0.12s ease;
3333
- }
3334
- .canvas-node:hover .md-edit-btn,
3335
- .md-edit-btn:focus-visible {
3336
- opacity: 1;
3337
- }
3338
- .md-edit-btn:hover {
3339
- color: var(--c-text);
3340
- border-color: var(--c-accent-25);
3341
- }
3342
-
3343
3340
  .md-editor-split {
3344
3341
  display: flex;
3345
3342
  height: 100%;
@@ -6862,3 +6859,422 @@ html.is-node-dragging .intent-line-layer {
6862
6859
  background: var(--c-panel);
6863
6860
  color: var(--c-text);
6864
6861
  }
6862
+
6863
+ /* ── Many boards (plan 012): top-bar switcher + Home ── */
6864
+ .board-switcher {
6865
+ display: inline-flex;
6866
+ /* The board name is the bar's identity: it keeps room for a short name
6867
+ ("Home") when chips crowd a 600px pane; long names still ellipsize. */
6868
+ min-width: 88px;
6869
+ flex-shrink: 1;
6870
+ }
6871
+ .board-switcher-button {
6872
+ display: inline-flex;
6873
+ align-items: center;
6874
+ gap: 6px;
6875
+ max-width: 280px;
6876
+ padding: 4px 8px;
6877
+ margin-left: -8px;
6878
+ background: none;
6879
+ border: 1px solid transparent;
6880
+ border-radius: 8px;
6881
+ cursor: pointer;
6882
+ }
6883
+ .board-switcher-button:hover,
6884
+ .board-switcher-button[aria-expanded='true'] {
6885
+ border-color: var(--c-line);
6886
+ background: var(--c-panel);
6887
+ }
6888
+ .board-switcher-name {
6889
+ overflow: hidden;
6890
+ text-overflow: ellipsis;
6891
+ white-space: nowrap;
6892
+ }
6893
+ .board-switcher-caret {
6894
+ font-size: 10px;
6895
+ color: var(--c-text-soft);
6896
+ }
6897
+ .board-switcher-menu {
6898
+ width: min(340px, calc(100vw - 24px));
6899
+ min-width: 0;
6900
+ padding: 8px;
6901
+ background: var(--c-panel);
6902
+ backdrop-filter: none;
6903
+ overflow: hidden;
6904
+ }
6905
+ .board-switcher-heading {
6906
+ padding: 8px 10px 10px;
6907
+ font-size: 11px;
6908
+ font-weight: 600;
6909
+ color: var(--c-dim);
6910
+ text-transform: uppercase;
6911
+ letter-spacing: 0.08em;
6912
+ flex-shrink: 0;
6913
+ }
6914
+ .board-switcher-recents { min-height: 0; overflow-y: auto; }
6915
+ .board-switcher-menu .toolbar-menu-item { padding: 10px; gap: 12px; }
6916
+ .board-switcher-menu .toolbar-menu-item:hover,
6917
+ .board-switcher-menu .toolbar-menu-item:focus-visible {
6918
+ background: var(--c-accent-8);
6919
+ border-color: var(--c-accent-25);
6920
+ outline: none;
6921
+ }
6922
+ .board-switcher-icon { display: flex; align-items: center; justify-content: center; width: 20px; flex-shrink: 0; color: var(--c-accent); font-size: 22px; }
6923
+ .board-switcher-item-copy { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 4px; }
6924
+ .board-switcher-item-name {
6925
+ color: var(--c-text);
6926
+ font-weight: 500;
6927
+ line-height: 1.35;
6928
+ overflow-wrap: anywhere;
6929
+ text-align: left;
6930
+ }
6931
+ .board-switcher-item-folder { font-size: 11px; line-height: 1.4; color: var(--c-dim); overflow-wrap: anywhere; }
6932
+ .board-switcher-item-meta {
6933
+ font-size: 10px;
6934
+ color: var(--c-dim);
6935
+ white-space: nowrap;
6936
+ align-self: flex-start;
6937
+ padding-top: 2px;
6938
+ }
6939
+ .board-switcher-actions { border-top: 1px solid var(--c-line); margin-top: 6px; padding-top: 6px; flex-shrink: 0; }
6940
+
6941
+ .home-view {
6942
+ position: absolute;
6943
+ inset: 0;
6944
+ /* Above the canvas's floating chrome (the updates tab sits at 60), below menus and prompts. */
6945
+ z-index: 70;
6946
+ overflow-y: auto;
6947
+ background: var(--c-bg);
6948
+ padding: 32px 16px;
6949
+ }
6950
+ .home-view-inner {
6951
+ max-width: 720px;
6952
+ margin: 0 auto;
6953
+ display: flex;
6954
+ flex-direction: column;
6955
+ gap: 18px;
6956
+ }
6957
+ .home-view-head {
6958
+ display: flex;
6959
+ align-items: center;
6960
+ justify-content: space-between;
6961
+ gap: 12px;
6962
+ }
6963
+ .home-view-title {
6964
+ margin: 0;
6965
+ font-size: 20px;
6966
+ font-weight: 650;
6967
+ color: var(--c-text);
6968
+ }
6969
+ .home-view-new {
6970
+ padding: 7px 14px;
6971
+ border-radius: 8px;
6972
+ border: 1px solid var(--c-accent);
6973
+ background: var(--c-accent);
6974
+ color: var(--c-bg);
6975
+ font-weight: 600;
6976
+ cursor: pointer;
6977
+ }
6978
+ .home-view-empty {
6979
+ color: var(--c-text-soft);
6980
+ font-size: 13px;
6981
+ }
6982
+ .home-view-list {
6983
+ list-style: none;
6984
+ margin: 0;
6985
+ padding: 0;
6986
+ display: flex;
6987
+ flex-direction: column;
6988
+ gap: 7px;
6989
+ }
6990
+ .home-board {
6991
+ display: flex;
6992
+ align-items: center;
6993
+ gap: 8px;
6994
+ padding: 7px 8px 7px 5px;
6995
+ border: 1px solid var(--c-line);
6996
+ border-radius: 10px;
6997
+ background: var(--c-panel);
6998
+ }
6999
+ .home-board.is-confirming {
7000
+ border-color: var(--c-danger);
7001
+ padding: 10px 12px;
7002
+ flex-wrap: wrap;
7003
+ }
7004
+ .home-board-open {
7005
+ flex: 1;
7006
+ min-width: 0;
7007
+ display: flex;
7008
+ flex-direction: column;
7009
+ align-items: flex-start;
7010
+ gap: 2px;
7011
+ padding: 6px 8px;
7012
+ background: none;
7013
+ border: none;
7014
+ border-radius: 8px;
7015
+ color: var(--c-text);
7016
+ cursor: pointer;
7017
+ text-align: left;
7018
+ }
7019
+ .home-board-open:hover {
7020
+ background: color-mix(in srgb, var(--c-accent) 10%, transparent);
7021
+ }
7022
+ .home-board-name {
7023
+ max-width: 100%;
7024
+ font-size: 14px;
7025
+ font-weight: 600;
7026
+ overflow: hidden;
7027
+ text-overflow: ellipsis;
7028
+ white-space: nowrap;
7029
+ }
7030
+ .home-board-meta {
7031
+ font: 11px var(--mono);
7032
+ color: var(--c-text-soft);
7033
+ }
7034
+ .home-board-confirm-text {
7035
+ flex: 1 1 240px;
7036
+ font-size: 13px;
7037
+ color: var(--c-text);
7038
+ }
7039
+ .home-board-actions {
7040
+ display: flex;
7041
+ gap: 2px;
7042
+ flex-shrink: 0;
7043
+ }
7044
+ .home-board-action {
7045
+ padding: 6px 7px;
7046
+ border-radius: 6px;
7047
+ border: 1px solid transparent;
7048
+ background: none;
7049
+ color: var(--c-dim);
7050
+ font-size: 11px;
7051
+ cursor: pointer;
7052
+ }
7053
+ .home-board-action:hover {
7054
+ border-color: var(--c-line);
7055
+ background: color-mix(in srgb, var(--c-text) 5%, transparent);
7056
+ color: var(--c-text-soft);
7057
+ }
7058
+ .home-board-action.is-danger {
7059
+ border-color: var(--c-danger);
7060
+ background: var(--c-danger);
7061
+ color: var(--c-bg);
7062
+ font-weight: 600;
7063
+ }
7064
+ .home-board-open:focus-visible,
7065
+ .home-board-action:focus-visible,
7066
+ .home-view-new:focus-visible,
7067
+ .board-switcher-button:focus-visible {
7068
+ outline: 2px solid var(--c-accent);
7069
+ outline-offset: 2px;
7070
+ }
7071
+
7072
+ /* ── Board export dialog (plan 013) ── */
7073
+ .export-dialog {
7074
+ width: min(520px, calc(100% - 32px));
7075
+ }
7076
+ .export-dialog-note {
7077
+ font-size: 12px;
7078
+ color: var(--c-text-soft);
7079
+ }
7080
+ .export-dialog-list {
7081
+ margin: 0;
7082
+ padding-left: 18px;
7083
+ font-size: 13px;
7084
+ color: var(--c-text);
7085
+ display: flex;
7086
+ flex-direction: column;
7087
+ gap: 4px;
7088
+ }
7089
+ .export-dialog-files {
7090
+ display: flex;
7091
+ gap: 8px;
7092
+ align-items: flex-start;
7093
+ font-size: 12px;
7094
+ color: var(--c-text);
7095
+ word-break: break-all;
7096
+ }
7097
+ .export-dialog-result {
7098
+ font-size: 12px;
7099
+ color: var(--c-text);
7100
+ display: flex;
7101
+ flex-direction: column;
7102
+ gap: 6px;
7103
+ word-break: break-all;
7104
+ }
7105
+ .export-dialog-links {
7106
+ display: flex;
7107
+ gap: 12px;
7108
+ }
7109
+ .export-dialog-links a {
7110
+ color: var(--c-accent);
7111
+ }
7112
+
7113
+ /* ── Home categories ── */
7114
+ .home-section {
7115
+ display: flex;
7116
+ flex-direction: column;
7117
+ gap: 8px;
7118
+ }
7119
+ .home-section-head {
7120
+ display: flex;
7121
+ align-items: center;
7122
+ gap: 9px;
7123
+ min-height: 42px;
7124
+ width: 100%;
7125
+ padding: 9px 10px;
7126
+ background: none;
7127
+ border: none;
7128
+ border-radius: 8px;
7129
+ color: var(--c-text-soft);
7130
+ font-size: 12px;
7131
+ font-weight: 600;
7132
+ text-transform: uppercase;
7133
+ letter-spacing: 0.06em;
7134
+ cursor: pointer;
7135
+ text-align: left;
7136
+ }
7137
+ .home-section-head:hover {
7138
+ color: var(--c-text);
7139
+ background: color-mix(in srgb, var(--c-text) 5%, transparent);
7140
+ }
7141
+ .home-section-chevron,
7142
+ .home-section-folder {
7143
+ flex: 0 0 18px;
7144
+ stroke: currentColor;
7145
+ stroke-width: 1.75;
7146
+ stroke-linecap: round;
7147
+ stroke-linejoin: round;
7148
+ }
7149
+ .home-section-chevron {
7150
+ transition: transform 120ms ease;
7151
+ }
7152
+ .home-section-head[aria-expanded='true'] .home-section-chevron {
7153
+ transform: rotate(90deg);
7154
+ }
7155
+ .home-section-folder { color: var(--c-dim); }
7156
+ .home-section-count {
7157
+ font: 11px var(--mono);
7158
+ text-transform: none;
7159
+ color: var(--c-dim);
7160
+ margin-left: auto;
7161
+ }
7162
+ .home-section-head:focus-visible {
7163
+ outline: 2px solid var(--c-accent);
7164
+ outline-offset: 2px;
7165
+ }
7166
+ .home-section-head {
7167
+ text-transform: none;
7168
+ letter-spacing: normal;
7169
+ max-width: 100%;
7170
+ }
7171
+ .home-section-name {
7172
+ font-size: 13px;
7173
+ font-weight: 650;
7174
+ overflow-wrap: anywhere;
7175
+ }
7176
+ .home-folder-children {
7177
+ display: flex;
7178
+ flex-direction: column;
7179
+ gap: 10px;
7180
+ padding-left: 12px;
7181
+ border-left: 1px solid var(--c-line);
7182
+ }
7183
+ .home-search { width: 100%; box-sizing: border-box; }
7184
+ .board-copy-dialog,
7185
+ .board-link-dialog {
7186
+ width: min(540px, calc(100vw - 32px));
7187
+ max-height: calc(100dvh - 40px);
7188
+ overflow-y: auto;
7189
+ box-sizing: border-box;
7190
+ }
7191
+ .board-copy-dialog form,
7192
+ .board-copy-field,
7193
+ .board-copy-options {
7194
+ display: flex;
7195
+ flex-direction: column;
7196
+ gap: 8px;
7197
+ }
7198
+ .board-copy-dialog h2,
7199
+ .board-link-dialog h2 { margin: 0 0 12px; font-size: 17px; }
7200
+ .board-copy-field { color: var(--c-text-soft); font-size: 12px; }
7201
+ .board-copy-cards {
7202
+ margin: 14px 0;
7203
+ padding: 8px;
7204
+ border: 1px solid var(--c-line);
7205
+ border-radius: 9px;
7206
+ max-height: 230px;
7207
+ overflow-y: auto;
7208
+ }
7209
+ .board-copy-cards legend { color: var(--c-text-soft); font-size: 12px; }
7210
+ .board-copy-card {
7211
+ display: flex;
7212
+ align-items: center;
7213
+ gap: 9px;
7214
+ padding: 8px;
7215
+ border-radius: 7px;
7216
+ cursor: pointer;
7217
+ }
7218
+ .board-copy-card:hover { background: color-mix(in srgb, var(--c-accent) 9%, transparent); }
7219
+ .board-copy-card span { display: flex; flex-direction: column; min-width: 0; }
7220
+ .board-copy-card strong { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 13px; }
7221
+ .board-copy-card small,
7222
+ .board-link-results small { color: var(--c-text-soft); font: 11px var(--mono); }
7223
+ .board-copy-options { font-size: 13px; }
7224
+ .board-copy-options label { display: flex; gap: 8px; align-items: center; }
7225
+ .board-copy-note,
7226
+ .board-copy-status { color: var(--c-text-soft); font-size: 12px; line-height: 1.5; }
7227
+ .board-copy-error { color: var(--c-danger); font-size: 12px; margin-top: 8px; }
7228
+ .board-link-results { display: flex; flex-direction: column; gap: 5px; margin: 12px 0; padding: 0; max-height: 320px; overflow-y: auto; list-style: none; }
7229
+ .board-link-results button { width: 100%; display: flex; justify-content: space-between; gap: 12px; padding: 10px; border: 1px solid var(--c-line); border-radius: 8px; background: var(--c-panel); color: var(--c-text); text-align: left; cursor: pointer; }
7230
+ .board-link-results button:hover,
7231
+ .board-link-results button:focus-visible { border-color: var(--c-accent); outline: none; }
7232
+ .home-folder-dialog {
7233
+ width: min(460px, calc(100vw - 32px));
7234
+ max-height: calc(100dvh - 48px);
7235
+ overflow-y: auto;
7236
+ box-sizing: border-box;
7237
+ }
7238
+ .home-folder-hint {
7239
+ color: var(--c-text-soft);
7240
+ font-size: 12px;
7241
+ line-height: 1.5;
7242
+ overflow-wrap: anywhere;
7243
+ }
7244
+ .home-folder-options {
7245
+ margin: 12px 0;
7246
+ padding: 8px;
7247
+ border: 1px solid var(--c-line);
7248
+ border-radius: 8px;
7249
+ max-height: 240px;
7250
+ overflow-y: auto;
7251
+ }
7252
+ .home-folder-options legend { color: var(--c-text-soft); font-size: 12px; }
7253
+ .home-folder-option {
7254
+ display: flex;
7255
+ align-items: center;
7256
+ gap: 8px;
7257
+ padding: 8px;
7258
+ border-radius: 6px;
7259
+ font-size: 13px;
7260
+ overflow-wrap: anywhere;
7261
+ cursor: pointer;
7262
+ }
7263
+ .home-folder-option:has(input:checked) { background: color-mix(in srgb, var(--c-accent) 12%, transparent); }
7264
+ .home-folder-option input { accent-color: var(--c-accent); flex-shrink: 0; }
7265
+ .home-folder-new { display: flex; flex-direction: column; gap: 6px; font-size: 12px; overflow-wrap: anywhere; }
7266
+ .attachment-node { padding: 16px; display: flex; flex-direction: column; align-items: flex-start; gap: 10px; font-size: 13px; overflow: auto; height: 100%; box-sizing: border-box; }
7267
+ .attachment-node p { margin: 0; color: var(--c-text-soft); line-height: 1.5; }
7268
+ .attachment-node a, .md-import-source a { color: var(--c-accent); }
7269
+ .attachment-status { font-weight: 600; }
7270
+ .attachment-node .home-board-action { border-color: var(--c-line); color: var(--c-text); min-height: 34px; }
7271
+ .attachment-node .home-board-action:hover { background: var(--c-panel); border-color: var(--c-accent); }
7272
+ .attachment-review { width: 100%; border-top: 1px solid var(--c-line); padding-top: 12px; }
7273
+ .attachment-review pre { white-space: pre-wrap; overflow-wrap: anywhere; font: 12px/1.6 var(--mono); padding: 12px; background: var(--c-bg); border-radius: 8px; }
7274
+ .attachment-review h4 { margin-bottom: 6px; }
7275
+ .md-import-source { padding: 6px 12px; font-size: 11px; overflow-wrap: anywhere; color: var(--c-text-soft); border-top: 1px solid var(--c-line); }
7276
+ @media (max-width: 640px) {
7277
+ .home-board { flex-wrap: wrap; }
7278
+ .home-board-open { flex-basis: 100%; }
7279
+ .home-board-actions { padding: 0 8px 4px; }
7280
+ }
@@ -14,6 +14,7 @@ export interface CanvasNodeState {
14
14
  | 'webpage'
15
15
  | 'json-render'
16
16
  | 'graph'
17
+ | 'board'
17
18
  | 'prompt'
18
19
  | 'response'
19
20
  | 'status'
@@ -74,6 +75,7 @@ export const TYPE_LABELS: Record<CanvasNodeState['type'], string> = {
74
75
  webpage: 'WEB',
75
76
  'json-render': 'UI',
76
77
  graph: 'GRAPH',
78
+ board: 'BOARD',
77
79
  prompt: 'ASK',
78
80
  response: 'ANS',
79
81
  status: 'STATUS',
@@ -149,7 +149,7 @@ function bundleDir(): string {
149
149
  return candidates[candidates.length - 1];
150
150
  }
151
151
 
152
- function escapeInlineScriptSource(source: string): string {
152
+ export function escapeInlineScriptSource(source: string): string {
153
153
  return source.replace(/<\/script/gi, '<\\/script');
154
154
  }
155
155
 
@@ -956,6 +956,20 @@ export function createJsonRenderNodeData(
956
956
  };
957
957
  }
958
958
 
959
+ /** The viewer bundle's script and stylesheet, as the viewer document inlines them. */
960
+ export async function readJsonRenderBundle(): Promise<{ js: string; css: string }> {
961
+ await ensureJsonRenderBundle();
962
+ const dir = bundleDir();
963
+ const jsPath = join(dir, 'index.js');
964
+ const cssPath = join(dir, 'index.css');
965
+ return {
966
+ js: existsSync(jsPath)
967
+ ? readFileSync(jsPath, 'utf-8')
968
+ : 'document.body.innerHTML = "<pre>json-render bundle missing</pre>";',
969
+ css: existsSync(cssPath) ? readFileSync(cssPath, 'utf-8') : '',
970
+ };
971
+ }
972
+
959
973
  export async function buildJsonRenderViewerHtml(options: {
960
974
  title: string;
961
975
  spec: JsonRenderSpec;
@@ -975,14 +989,7 @@ export async function buildJsonRenderViewerHtml(options: {
975
989
  const sanitizeAxValue = (v?: string): string =>
976
990
  typeof v === 'string' ? v.replace(/[^A-Za-z0-9_-]/g, '').slice(0, 80) : '';
977
991
  try {
978
- await ensureJsonRenderBundle();
979
- const dir = bundleDir();
980
- const jsPath = join(dir, 'index.js');
981
- const cssPath = join(dir, 'index.css');
982
- const jsBundle = existsSync(jsPath)
983
- ? readFileSync(jsPath, 'utf-8')
984
- : 'document.body.innerHTML = "<pre>json-render bundle missing</pre>";';
985
- const cssBundle = existsSync(cssPath) ? readFileSync(cssPath, 'utf-8') : '';
992
+ const { js: jsBundle, css: cssBundle } = await readJsonRenderBundle();
986
993
  const boot = [
987
994
  `window.__PMX_CANVAS_JSON_RENDER_SPEC__ = ${JSON.stringify(options.spec)};`,
988
995
  ...(options.theme ? [`window.__PMX_CANVAS_JSON_RENDER_THEME__ = ${JSON.stringify(options.theme)};`] : []),
@@ -1,15 +1,9 @@
1
1
  import { realpathSync } from 'node:fs';
2
2
  import { homedir } from 'node:os';
3
3
  import { basename, dirname, resolve } from 'node:path';
4
- import {
5
- createCanvas,
6
- canvasState,
7
- type CanvasEdge,
8
- type CanvasLayout,
9
- type CanvasNodeState,
10
- type PmxCanvas,
11
- } from '../server/index.js';
4
+ import { createCanvas, canvasState, type CanvasLayout, type CanvasNodeState, type PmxCanvas } from '../server/index.js';
12
5
  import type { PmxAxSource } from '../server/ax-state.js';
6
+ import type { ContextReadInput } from '../server/context-reads.js';
13
7
  import { HttpOperationInvoker, LocalOperationInvoker, type OperationInvoker } from '../server/operations/index.js';
14
8
 
15
9
  // openMcpApp / addDiagram / buildWebArtifact / refreshWebpageNode / addHtmlNode /
@@ -54,8 +48,8 @@ export interface CanvasAccess {
54
48
  readonly remoteBaseUrl: string | null;
55
49
  /** Operation-registry invoker (plan-005): local in-process or HTTP, matching the access mode. */
56
50
  invoker(): OperationInvoker;
57
- getLayout(): Promise<CanvasLayout>;
58
- getNode(id: string): Promise<CanvasNodeState | undefined>;
51
+ getLayout(board?: string): Promise<CanvasLayout>;
52
+ getNode(id: string, board?: string): Promise<CanvasNodeState | undefined>;
59
53
  getAxState(): Promise<AxStateResult>;
60
54
  getAxContext(options?: { consumer?: string }): Promise<AxContextResult>;
61
55
  getAxTimeline(query?: GetAxTimelineQuery): Promise<GetAxTimelineResult>;
@@ -72,7 +66,10 @@ export interface CanvasAccess {
72
66
  ingestActivity(input: IngestActivityInput, options?: { source?: PmxAxSource }): Promise<IngestActivityResult>;
73
67
  getPolicy(): Promise<GetPolicyResult>;
74
68
  getHistory(): Promise<HistoryResult>;
75
- getPinnedNodeIds(): Promise<string[]>;
69
+ getPinnedNodeIds(board?: string): Promise<string[]>;
70
+ prepareContextRead(board?: string): Promise<{ boardId: string | null; pinnedNodeIds: string[] }>;
71
+ /** Record a context read the agent made through this MCP server (plan-011). */
72
+ recordContextRead(read: ContextReadInput): Promise<void>;
76
73
  runBatch(operations: RunBatchInput): Promise<RunBatchResult>;
77
74
  getCodeGraph(): Promise<CodeGraphResult>;
78
75
  // canvas_screenshot (still hand-written — binary payload) is the only webview
@@ -99,11 +96,15 @@ class LocalCanvasAccess implements CanvasAccess {
99
96
  return this.operationInvoker;
100
97
  }
101
98
 
102
- async getLayout(): Promise<CanvasLayout> {
103
- return this.canvas.getLayout();
99
+ async getLayout(board?: string): Promise<CanvasLayout> {
100
+ return board
101
+ ? ((await this.operationInvoker.invoke('layout.get', { board, includeBlobs: true })) as CanvasLayout)
102
+ : this.canvas.getLayout();
104
103
  }
105
104
 
106
- async getNode(id: string): Promise<CanvasNodeState | undefined> {
105
+ async getNode(id: string, board?: string): Promise<CanvasNodeState | undefined> {
106
+ if (board)
107
+ return (await this.operationInvoker.invoke('node.get', { id, board, includeBlobs: true })) as CanvasNodeState;
107
108
  return this.canvas.getNode(id);
108
109
  }
109
110
 
@@ -162,10 +163,23 @@ class LocalCanvasAccess implements CanvasAccess {
162
163
  return this.canvas.getHistory();
163
164
  }
164
165
 
165
- async getPinnedNodeIds(): Promise<string[]> {
166
+ async getPinnedNodeIds(board?: string): Promise<string[]> {
167
+ if (board) {
168
+ const result = (await this.operationInvoker.invoke('pinned-context.get', { board })) as { nodeIds?: string[] };
169
+ return result.nodeIds ?? [];
170
+ }
166
171
  return Array.from(canvasState.contextPinnedNodeIds);
167
172
  }
168
173
 
174
+ async prepareContextRead(board?: string): Promise<{ boardId: string | null; pinnedNodeIds: string[] }> {
175
+ const boardId = board ?? canvasState.activeBoardId;
176
+ return { boardId, pinnedNodeIds: boardId ? await this.getPinnedNodeIds(boardId) : [] };
177
+ }
178
+
179
+ async recordContextRead(read: ContextReadInput): Promise<void> {
180
+ canvasState.recordContextRead(read, read.boardId === undefined ? canvasState.activeBoardId : read.boardId);
181
+ }
182
+
169
183
  async runBatch(operations: RunBatchInput): Promise<RunBatchResult> {
170
184
  return await this.canvas.runBatch(operations);
171
185
  }
@@ -202,7 +216,12 @@ class RemoteCanvasAccess implements CanvasAccess {
202
216
  private async requestJson<T>(method: string, path: string, body?: unknown): Promise<T> {
203
217
  const response = await fetch(`${this.remoteBaseUrl}${path}`, {
204
218
  method,
205
- headers: body === undefined ? undefined : { 'Content-Type': 'application/json' },
219
+ // These fetches serve the agent's MCP reads, which this server records
220
+ // itself — the daemon must not record them a second time (plan-011).
221
+ headers: {
222
+ 'x-pmx-proxied-read': '1',
223
+ ...(body === undefined ? {} : { 'Content-Type': 'application/json' }),
224
+ },
206
225
  body: body === undefined ? undefined : JSON.stringify(body),
207
226
  });
208
227
  const text = await response.text();
@@ -227,12 +246,16 @@ class RemoteCanvasAccess implements CanvasAccess {
227
246
  return parsed as T;
228
247
  }
229
248
 
230
- async getLayout(): Promise<CanvasLayout> {
231
- return await this.requestJson<CanvasLayout>('GET', '/api/canvas/state?includeBlobs=true');
249
+ async getLayout(board?: string): Promise<CanvasLayout> {
250
+ const params = new URLSearchParams({ includeBlobs: 'true' });
251
+ if (board) params.set('board', board);
252
+ return await this.requestJson<CanvasLayout>('GET', `/api/canvas/state?${params}`);
232
253
  }
233
254
 
234
- async getNode(id: string): Promise<CanvasNodeState | undefined> {
235
- const response = await fetch(`${this.remoteBaseUrl}/api/canvas/node/${encodeURIComponent(id)}?includeBlobs=true`);
255
+ async getNode(id: string, board?: string): Promise<CanvasNodeState | undefined> {
256
+ const params = new URLSearchParams({ includeBlobs: 'true' });
257
+ if (board) params.set('board', board);
258
+ const response = await fetch(`${this.remoteBaseUrl}/api/canvas/node/${encodeURIComponent(id)}?${params}`);
236
259
  if (response.status === 404) return undefined;
237
260
  const text = await response.text();
238
261
  let parsed: unknown;
@@ -346,11 +369,25 @@ class RemoteCanvasAccess implements CanvasAccess {
346
369
  return response.reviewAnnotations ?? [];
347
370
  }
348
371
 
349
- async getPinnedNodeIds(): Promise<string[]> {
350
- const response = await this.requestJson<{ nodeIds?: string[] }>('GET', '/api/canvas/pinned-context');
372
+ async getPinnedNodeIds(board?: string): Promise<string[]> {
373
+ const qs = board ? `?board=${encodeURIComponent(board)}` : '';
374
+ const response = await this.requestJson<{ nodeIds?: string[] }>('GET', `/api/canvas/pinned-context${qs}`);
351
375
  return Array.isArray(response.nodeIds) ? response.nodeIds : [];
352
376
  }
353
377
 
378
+ async prepareContextRead(board?: string): Promise<{ boardId: string | null; pinnedNodeIds: string[] }> {
379
+ let boardId = board;
380
+ if (!boardId) {
381
+ const boards = await this.requestJson<{ activeBoardId?: string | null }>('GET', '/api/canvas/boards');
382
+ boardId = boards.activeBoardId ?? undefined;
383
+ }
384
+ return { boardId: boardId ?? null, pinnedNodeIds: boardId ? await this.getPinnedNodeIds(boardId) : [] };
385
+ }
386
+
387
+ async recordContextRead(read: ContextReadInput): Promise<void> {
388
+ await this.requestJson('POST', '/api/canvas/ax/context-reads', read);
389
+ }
390
+
354
391
  async runBatch(operations: RunBatchInput): Promise<RunBatchResult> {
355
392
  return await this.requestJson<RunBatchResult>('POST', '/api/canvas/batch', { operations });
356
393
  }