pmx-canvas 0.8.2 → 0.9.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.
- package/.github/extensions/pmx-canvas/runtime.json +1 -1
- package/.github/extensions/pmx-canvas/tools.json +601 -22
- package/CHANGELOG.md +49 -0
- package/Readme.md +131 -532
- package/dist/canvas/global.css +2724 -476
- package/dist/canvas/index.js +168 -107
- package/dist/canvas/mcp-app.html +2899 -596
- package/dist/json-render/index.js +20 -20
- package/dist/types/client/canvas/BarHint.d.ts +12 -2
- package/dist/types/client/canvas/BoardLinkDialog.d.ts +3 -1
- package/dist/types/client/canvas/BoardPinButton.d.ts +10 -0
- package/dist/types/client/canvas/BoardSwitcher.d.ts +1 -1
- package/dist/types/client/canvas/CanvasViewport.d.ts +1 -1
- package/dist/types/client/canvas/CommandBar.d.ts +3 -4
- package/dist/types/client/canvas/ConnectionBanner.d.ts +2 -1
- package/dist/types/client/canvas/ContextChip.d.ts +3 -0
- package/dist/types/client/canvas/CrossBoardReferences.d.ts +5 -0
- package/dist/types/client/canvas/EdgeLayer.d.ts +14 -3
- package/dist/types/client/canvas/EmptyState.d.ts +4 -4
- package/dist/types/client/canvas/HomeGraphView.d.ts +2 -0
- package/dist/types/client/canvas/HomeMapView.d.ts +1 -0
- package/dist/types/client/canvas/HomeReviewView.d.ts +6 -0
- package/dist/types/client/canvas/HomeView.d.ts +9 -7
- package/dist/types/client/canvas/KnowledgeDialog.d.ts +4 -0
- package/dist/types/client/canvas/LifecycleChrome.d.ts +5 -0
- package/dist/types/client/canvas/LifecycleFields.d.ts +7 -0
- package/dist/types/client/canvas/LifecyclePanel.d.ts +1 -0
- package/dist/types/client/canvas/MemoryInspector.d.ts +7 -0
- package/dist/types/client/canvas/NearPinLayer.d.ts +14 -0
- package/dist/types/client/canvas/NodeContextMark.d.ts +40 -7
- package/dist/types/client/canvas/RelationPresetsDialog.d.ts +4 -0
- package/dist/types/client/canvas/RelationsPanel.d.ts +4 -0
- package/dist/types/client/canvas/SessionPanel.d.ts +2 -0
- package/dist/types/client/canvas/canvas-area.d.ts +1 -1
- package/dist/types/client/canvas/graph-geometry.d.ts +27 -0
- package/dist/types/client/canvas/graph-model.d.ts +37 -0
- package/dist/types/client/canvas/memory-layout.d.ts +23 -0
- package/dist/types/client/canvas/use-chrome-height.d.ts +2 -0
- package/dist/types/client/canvas/use-focus-trap.d.ts +1 -0
- package/dist/types/client/canvas/use-memory-graph.d.ts +8 -0
- package/dist/types/client/canvas/use-pan-zoom.d.ts +4 -1
- package/dist/types/client/icons.d.ts +0 -10
- package/dist/types/client/nodes/AxStepControls.d.ts +1 -1
- package/dist/types/client/nodes/InlineMarkdownEditor.d.ts +4 -4
- package/dist/types/client/nodes/MarkdownNode.d.ts +5 -1
- package/dist/types/client/nodes/McpAppNode.d.ts +4 -4
- package/dist/types/client/nodes/viewer-runtime-state.d.ts +1 -1
- package/dist/types/client/state/boards-store.d.ts +81 -9
- package/dist/types/client/state/canvas-store.d.ts +14 -1
- package/dist/types/client/state/context-chip-store.d.ts +22 -0
- package/dist/types/client/state/context-status-store.d.ts +35 -0
- package/dist/types/client/state/intent-bridge.d.ts +11 -8
- package/dist/types/client/state/knowledge-store.d.ts +22 -0
- package/dist/types/client/state/near-pin-store.d.ts +44 -1
- package/dist/types/client/state/presence-store.d.ts +4 -4
- package/dist/types/client/state/presentation.d.ts +28 -0
- package/dist/types/client/state/relation-store.d.ts +10 -0
- package/dist/types/client/state/session-store.d.ts +41 -2
- package/dist/types/client/state/workspace-settings.d.ts +7 -0
- package/dist/types/client/types.d.ts +13 -1
- package/dist/types/client/utils/keyboard.d.ts +2 -0
- package/dist/types/client/utils/time-ago.d.ts +4 -0
- package/dist/types/mcp/context-uri-template.d.ts +3 -1
- package/dist/types/server/agent-presence.d.ts +10 -3
- package/dist/types/server/ax-context.d.ts +1 -1
- package/dist/types/server/canvas-db.d.ts +30 -7
- package/dist/types/server/canvas-operations.d.ts +24 -2
- package/dist/types/server/canvas-provenance.d.ts +22 -2
- package/dist/types/server/canvas-serialization.d.ts +2 -0
- package/dist/types/server/canvas-state.d.ts +93 -15
- package/dist/types/server/canvas-validation.d.ts +1 -1
- package/dist/types/server/context-brief.d.ts +94 -6
- package/dist/types/server/context-reads.d.ts +33 -7
- package/dist/types/server/edit-summary.d.ts +20 -0
- package/dist/types/server/index.d.ts +71 -10
- package/dist/types/server/knowledge-lifecycle.d.ts +221 -0
- package/dist/types/server/lifecycle-status.d.ts +10 -0
- package/dist/types/server/memory-graph.d.ts +9 -0
- package/dist/types/server/mutation-history.d.ts +2 -0
- package/dist/types/server/operations/ops/ax-shared.d.ts +1 -1
- package/dist/types/server/operations/ops/boards.d.ts +2 -1
- package/dist/types/server/operations/ops/lifecycle.d.ts +2 -0
- package/dist/types/server/operations/ops/memory-graph.d.ts +2 -0
- package/dist/types/server/operations/ops/nodes.d.ts +5 -0
- package/dist/types/server/operations/ops/query.d.ts +23 -0
- package/dist/types/server/operations/registry.d.ts +1 -0
- package/dist/types/server/placement.d.ts +7 -0
- package/dist/types/server/scope-fence.d.ts +2 -1
- package/dist/types/server/spatial-analysis.d.ts +6 -3
- package/dist/types/server/tour-control.d.ts +32 -0
- package/dist/types/server/wiki-links.d.ts +14 -0
- package/dist/types/server/workbench-auth.d.ts +2 -0
- package/dist/types/shared/agent-context.d.ts +2 -0
- package/dist/types/shared/agent-presence.d.ts +15 -12
- package/dist/types/shared/auto-arrange.d.ts +1 -0
- package/dist/types/shared/boards.d.ts +4 -4
- package/dist/types/shared/knowledge-lifecycle.d.ts +41 -0
- package/dist/types/shared/knowledge-presets.d.ts +6 -0
- package/dist/types/shared/lifecycle-status.d.ts +24 -0
- package/dist/types/shared/memory-graph.d.ts +53 -0
- package/dist/types/shared/relations.d.ts +68 -0
- package/dist/types/shared/semantic-attention.d.ts +2 -0
- package/dist/types/shared/tour.d.ts +38 -0
- package/dist/types/shared/workspace-settings.d.ts +11 -0
- package/docs/RELEASE.md +21 -3
- package/docs/adr-001-bun-only-runtime.md +7 -1
- package/docs/adr-002-shared-product-separate-runtimes.md +93 -0
- package/docs/api-reference.md +109 -0
- package/docs/ax-host-adapter-contract.md +1 -1
- package/docs/ax-state-contract.md +5 -4
- package/docs/cli.md +153 -15
- package/docs/demos/pmx-canvas-demo.mp4 +0 -0
- package/docs/design/AgentContext.dc.html +3 -3
- package/docs/design/BoardPins.dc.html +37 -22
- package/docs/design/Categories.dc.html +208 -0
- package/docs/design/CategoryEditor.dc.html +33 -0
- package/docs/design/Chrome.dc.html +52 -9
- package/docs/design/ContextChip.dc.html +224 -0
- package/docs/design/ContextChip600.dc.html +136 -0
- package/docs/design/EmptyState.dc.html +45 -0
- package/docs/design/Flows.dc.html +23 -1
- package/docs/design/HeaderMarks.dc.html +183 -0
- package/docs/design/Home.dc.html +19 -1
- package/docs/design/HomeGraph.dc.html +86 -202
- package/docs/design/HomeMap.dc.html +23 -1
- package/docs/design/HomeTeam.dc.html +32 -14
- package/docs/design/Lifecycle.dc.html +22 -0
- package/docs/design/Lifecycle600.dc.html +18 -0
- package/docs/design/LifecycleVariants.dc.html +157 -0
- package/docs/design/LinkAuthorship.dc.html +139 -0
- package/docs/design/LinksChanged.dc.html +203 -0
- package/docs/design/LinksOptions.dc.html +137 -0
- package/docs/design/LinksOptions600.dc.html +134 -0
- package/docs/design/NearPin.dc.html +9 -10
- package/docs/design/Pane600.dc.html +4 -3
- package/docs/design/Relations.dc.html +3 -2
- package/docs/design/Restore.dc.html +152 -0
- package/docs/design/SeeChange.dc.html +267 -0
- package/docs/design/Uses.dc.html +12 -6
- package/docs/design/canvas.json +146 -19
- package/docs/design.md +449 -16
- package/docs/environment.md +0 -1
- package/docs/evals/curation-effect.md +128 -0
- package/docs/http-api.md +459 -37
- package/docs/mcp.md +146 -18
- package/docs/node-types.md +58 -3
- package/docs/plans/design-gaps-2026-10-08.md +53 -0
- package/docs/plans/plan-009-tech-debt-backlog.md +379 -0
- package/docs/plans/plan-016-context-made-visible-wave-1.md +150 -5
- package/docs/plans/plan-017-knowledge-categories-and-lifecycle.md +175 -0
- package/docs/plans/plan-018-connected-memory-wave-2.md +594 -0
- package/docs/plans/plan-019-post-0.9-roadmap.md +198 -0
- package/docs/product-context-vision-2026-09.md +17 -1
- package/docs/product-vision-2026-09.md +1082 -43
- package/docs/product-vision-review-2026-09.md +13 -1
- package/docs/screenshots/workbench-live-dark.png +0 -0
- package/docs/screenshots/workbench-live-light.png +0 -0
- package/docs/sdk.md +62 -2
- package/package.json +1 -1
- package/plugins/pmx-canvas/com.github.copilot/extensions/pmx-canvas/runtime.json +1 -1
- package/plugins/pmx-canvas/plugin.json +1 -1
- package/plugins/pmx-canvas/runtime.json +1 -1
- package/plugins/pmx-canvas/tools.json +601 -22
- package/skills/pmx-canvas/SKILL.md +216 -42
- package/skills/pmx-canvas/references/full-reference.md +120 -21
- package/skills/pmx-canvas/references/github-copilot-app-adapter.md +2 -2
- package/skills/pmx-canvas/references/importing-context.md +132 -0
- package/skills/pmx-canvas/references/installing-pmx-canvas.md +6 -6
- package/skills/pmx-canvas/references/tours-and-recording.md +16 -1
- package/skills/pmx-canvas-testing/SKILL.md +20 -0
- package/src/cli/agent.ts +2 -0
- package/src/cli/commands/ax.ts +12 -0
- package/src/cli/commands/boards.ts +87 -11
- package/src/cli/commands/edges.ts +28 -0
- package/src/cli/commands/groups.ts +7 -0
- package/src/cli/commands/nodes.ts +1 -0
- package/src/cli/commands/query.ts +34 -2
- package/src/cli/commands/record.ts +199 -48
- package/src/cli/index.ts +6 -1
- package/src/client/App.tsx +177 -70
- package/src/client/canvas/AttentionHistory.tsx +0 -2
- package/src/client/canvas/BarHint.tsx +40 -2
- package/src/client/canvas/BoardCopyDialog.tsx +1 -1
- package/src/client/canvas/BoardFolderDialog.tsx +7 -7
- package/src/client/canvas/BoardLinkDialog.tsx +138 -33
- package/src/client/canvas/BoardPinButton.tsx +33 -0
- package/src/client/canvas/BoardSwitcher.tsx +26 -40
- package/src/client/canvas/CanvasNode.tsx +360 -237
- package/src/client/canvas/CanvasViewport.tsx +45 -7
- package/src/client/canvas/CommandBar.tsx +29 -23
- package/src/client/canvas/CommandPalette.tsx +5 -12
- package/src/client/canvas/ConnectionBanner.tsx +3 -3
- package/src/client/canvas/ContextChip.tsx +296 -0
- package/src/client/canvas/ContextMenu.tsx +63 -5
- package/src/client/canvas/CrossBoardReferences.tsx +60 -0
- package/src/client/canvas/EdgeLayer.tsx +339 -76
- package/src/client/canvas/EmptyState.tsx +33 -18
- package/src/client/canvas/ExpandedNodeOverlay.tsx +72 -40
- package/src/client/canvas/ExternalWriters.tsx +57 -42
- package/src/client/canvas/HomeGraphView.tsx +942 -0
- package/src/client/canvas/HomeMapView.tsx +641 -0
- package/src/client/canvas/HomeReviewView.tsx +167 -0
- package/src/client/canvas/HomeView.tsx +687 -160
- package/src/client/canvas/IntentLayer.tsx +19 -12
- package/src/client/canvas/KnowledgeDialog.tsx +488 -0
- package/src/client/canvas/LifecycleChrome.tsx +133 -0
- package/src/client/canvas/LifecycleFields.tsx +91 -0
- package/src/client/canvas/LifecyclePanel.tsx +533 -0
- package/src/client/canvas/MemoryInspector.tsx +104 -0
- package/src/client/canvas/Minimap.tsx +3 -3
- package/src/client/canvas/NearPinLayer.tsx +187 -0
- package/src/client/canvas/NodeContextMark.tsx +175 -97
- package/src/client/canvas/Presentation.tsx +23 -57
- package/src/client/canvas/RelationPresetsDialog.tsx +413 -0
- package/src/client/canvas/RelationsPanel.tsx +82 -0
- package/src/client/canvas/SelectionBar.tsx +3 -6
- package/src/client/canvas/SessionPanel.tsx +90 -50
- package/src/client/canvas/SessionReceipt.tsx +311 -39
- package/src/client/canvas/ShortcutOverlay.tsx +1 -1
- package/src/client/canvas/SnapshotPanel.tsx +27 -36
- package/src/client/canvas/ToolRail.tsx +367 -192
- package/src/client/canvas/TopBar.tsx +354 -124
- package/src/client/canvas/canvas-area.ts +10 -3
- package/src/client/canvas/graph-geometry.ts +195 -0
- package/src/client/canvas/graph-model.ts +254 -0
- package/src/client/canvas/memory-layout.ts +106 -0
- package/src/client/canvas/use-chrome-height.ts +20 -0
- package/src/client/canvas/use-focus-trap.ts +31 -5
- package/src/client/canvas/use-memory-graph.ts +58 -0
- package/src/client/canvas/use-node-drag.ts +2 -1
- package/src/client/canvas/use-pan-zoom.ts +64 -7
- package/src/client/icons.tsx +0 -63
- package/src/client/mcp-app/style.css +0 -6
- package/src/client/nodes/AxStepControls.tsx +74 -50
- package/src/client/nodes/BoardNode.tsx +5 -1
- package/src/client/nodes/ContextNode.tsx +18 -11
- package/src/client/nodes/ExtAppFrame.tsx +0 -1
- package/src/client/nodes/FileNode.tsx +46 -42
- package/src/client/nodes/HtmlNode.tsx +32 -0
- package/src/client/nodes/ImageNode.tsx +1 -1
- package/src/client/nodes/InlineFormatBar.tsx +6 -9
- package/src/client/nodes/InlineMarkdownEditor.tsx +56 -17
- package/src/client/nodes/MarkdownNode.tsx +377 -109
- package/src/client/nodes/McpAppNode.tsx +50 -20
- package/src/client/nodes/MdFormatBar.tsx +21 -18
- package/src/client/nodes/PromptNode.tsx +1 -1
- package/src/client/nodes/ResponseNode.tsx +1 -1
- package/src/client/nodes/viewer-runtime-state.ts +54 -22
- package/src/client/state/attention-bridge.ts +64 -15
- package/src/client/state/boards-store.ts +193 -39
- package/src/client/state/canvas-store.ts +99 -12
- package/src/client/state/context-chip-store.ts +70 -0
- package/src/client/state/context-status-store.ts +53 -0
- package/src/client/state/intent-bridge.ts +51 -30
- package/src/client/state/knowledge-store.ts +43 -0
- package/src/client/state/near-pin-store.ts +115 -6
- package/src/client/state/presence-store.ts +7 -10
- package/src/client/state/presentation.ts +91 -0
- package/src/client/state/relation-store.ts +81 -0
- package/src/client/state/session-store.ts +144 -18
- package/src/client/state/sse-bridge.ts +206 -29
- package/src/client/state/workspace-settings.ts +11 -0
- package/src/client/theme/global.css +2724 -476
- package/src/client/types.ts +12 -0
- package/src/client/utils/keyboard.ts +10 -0
- package/src/client/utils/time-ago.ts +14 -0
- package/src/json-render/renderer/index.tsx +31 -0
- package/src/mcp/canvas-app.ts +12 -2
- package/src/mcp/context-uri-template.ts +7 -4
- package/src/mcp/server.ts +63 -41
- package/src/mcp/workbench-app.ts +39 -6
- package/src/server/agent-presence.ts +128 -28
- package/src/server/ax-context.ts +27 -13
- package/src/server/board-export.ts +12 -1
- package/src/server/canvas-db.ts +331 -42
- package/src/server/canvas-operations.ts +195 -83
- package/src/server/canvas-provenance.ts +53 -22
- package/src/server/canvas-serialization.ts +2 -0
- package/src/server/canvas-state.ts +950 -122
- package/src/server/canvas-validation.ts +9 -2
- package/src/server/context-brief.ts +345 -38
- package/src/server/context-reads.ts +178 -30
- package/src/server/demo-state.json +355 -24
- package/src/server/edit-summary.ts +74 -0
- package/src/server/html-surface.ts +8 -1
- package/src/server/index.ts +181 -11
- package/src/server/knowledge-lifecycle.ts +304 -0
- package/src/server/lifecycle-status.ts +232 -0
- package/src/server/memory-graph.ts +166 -0
- package/src/server/mutation-history.ts +18 -6
- package/src/server/operations/composites.ts +32 -11
- package/src/server/operations/http.ts +7 -2
- package/src/server/operations/index.ts +4 -0
- package/src/server/operations/mcp.ts +26 -1
- package/src/server/operations/ops/ax-presence.ts +1 -1
- package/src/server/operations/ops/ax-read.ts +57 -6
- package/src/server/operations/ops/ax-timeline.ts +28 -2
- package/src/server/operations/ops/backup.ts +5 -3
- package/src/server/operations/ops/batch.ts +35 -4
- package/src/server/operations/ops/boards.ts +188 -47
- package/src/server/operations/ops/canvas-wire.ts +31 -1
- package/src/server/operations/ops/edges.ts +36 -2
- package/src/server/operations/ops/groups.ts +6 -1
- package/src/server/operations/ops/lifecycle.ts +262 -0
- package/src/server/operations/ops/memory-graph.ts +45 -0
- package/src/server/operations/ops/nodes.ts +107 -24
- package/src/server/operations/ops/query.ts +281 -68
- package/src/server/operations/ops/snapshots.ts +73 -1
- package/src/server/operations/ops/validate.ts +5 -1
- package/src/server/operations/ops/viewport.ts +97 -3
- package/src/server/operations/registry.ts +74 -25
- package/src/server/placement.ts +29 -0
- package/src/server/scope-fence.ts +78 -6
- package/src/server/server.ts +115 -16
- package/src/server/spatial-analysis.ts +48 -23
- package/src/server/tour-control.ts +82 -0
- package/src/server/wiki-links.ts +137 -0
- package/src/server/workbench-auth.ts +11 -0
- package/src/shared/agent-context.ts +3 -0
- package/src/shared/agent-presence.ts +15 -15
- package/src/shared/auto-arrange.ts +2 -0
- package/src/shared/boards.ts +4 -4
- package/src/shared/knowledge-lifecycle.ts +22 -0
- package/src/shared/knowledge-presets.ts +80 -0
- package/src/shared/lifecycle-status.ts +26 -0
- package/src/shared/memory-graph.ts +74 -0
- package/src/shared/relations.ts +98 -0
- package/src/shared/semantic-attention.ts +15 -1
- package/src/shared/tour.ts +23 -5
- package/src/shared/workspace-settings.ts +13 -0
- package/dist/types/client/canvas/ContextPinBar.d.ts +0 -1
- package/dist/types/client/canvas/FocusFieldLayer.d.ts +0 -1
- package/dist/types/client/canvas/kind-colors.d.ts +0 -3
- package/dist/types/server/agent-context.d.ts +0 -22
- package/src/client/canvas/ContextPinBar.tsx +0 -25
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { effect } from '@preact/signals';
|
|
2
|
+
import { Component } from 'preact';
|
|
2
3
|
import { useCallback, useEffect, useRef, useState } from 'preact/hooks';
|
|
3
4
|
import { askText } from './TextPrompt';
|
|
4
5
|
import { ContextNode } from '../nodes/ContextNode';
|
|
@@ -42,6 +43,7 @@ import { importFiles } from './import-files';
|
|
|
42
43
|
import { IntentLayer } from './IntentLayer';
|
|
43
44
|
import { AgentPresenceLayer } from './AgentPresenceLayer';
|
|
44
45
|
import { HumanPresenceLayer } from './HumanPresenceLayer';
|
|
46
|
+
import { NearPinCard, NearPinLayer } from './NearPinLayer';
|
|
45
47
|
import { reportHumanCursor } from '../state/human-store';
|
|
46
48
|
import { ScopeFenceLayer } from './ScopeFenceLayer';
|
|
47
49
|
import { CanvasNode } from './CanvasNode';
|
|
@@ -94,6 +96,29 @@ function renderNodeContent(node: CanvasNodeState) {
|
|
|
94
96
|
}
|
|
95
97
|
}
|
|
96
98
|
|
|
99
|
+
/**
|
|
100
|
+
* One card in the world. A drag frame replaces only the moved node's object in
|
|
101
|
+
* the nodes map, so every other card keeps its props and skips the re-render
|
|
102
|
+
* (and its header re-measure); its own signals still update it.
|
|
103
|
+
*/
|
|
104
|
+
class WorldNode extends Component<{
|
|
105
|
+
node: CanvasNodeState;
|
|
106
|
+
onContextMenu?: (e: MouseEvent, nodeId: string) => void;
|
|
107
|
+
}> {
|
|
108
|
+
shouldComponentUpdate(next: { node: CanvasNodeState; onContextMenu?: (e: MouseEvent, nodeId: string) => void }) {
|
|
109
|
+
return next.node !== this.props.node || next.onContextMenu !== this.props.onContextMenu;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
render() {
|
|
113
|
+
const { node, onContextMenu } = this.props;
|
|
114
|
+
return (
|
|
115
|
+
<CanvasNode node={node} onContextMenu={onContextMenu}>
|
|
116
|
+
{renderNodeContent(node)}
|
|
117
|
+
</CanvasNode>
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
97
122
|
function distanceToSegment(
|
|
98
123
|
point: { x: number; y: number },
|
|
99
124
|
start: { x: number; y: number },
|
|
@@ -295,6 +320,8 @@ export function CanvasViewport({
|
|
|
295
320
|
const containerRef = usePanZoom({
|
|
296
321
|
viewport,
|
|
297
322
|
disabled: annotationMode,
|
|
323
|
+
onBackgroundContextMenu: onCanvasContextMenu,
|
|
324
|
+
onEdgeContextMenu,
|
|
298
325
|
onViewportChange: (next) => {
|
|
299
326
|
// Don't pan while lassoing — usePanZoom's pointerdown still fires
|
|
300
327
|
// (native listener) before our Preact handler can stopPropagation.
|
|
@@ -683,8 +710,8 @@ export function CanvasViewport({
|
|
|
683
710
|
);
|
|
684
711
|
|
|
685
712
|
const handleContextMenu = useCallback((e: MouseEvent) => {
|
|
686
|
-
//
|
|
687
|
-
//
|
|
713
|
+
// usePanZoom distinguishes a background right-click from a right-drag
|
|
714
|
+
// on release. Suppress the native menu on both press and release.
|
|
688
715
|
e.preventDefault();
|
|
689
716
|
}, []);
|
|
690
717
|
|
|
@@ -783,6 +810,10 @@ export function CanvasViewport({
|
|
|
783
810
|
expandedNodeId.value,
|
|
784
811
|
new Set(hiddenByCollapsedGroup.value.keys()),
|
|
785
812
|
);
|
|
813
|
+
const lowestZ = worldNodes.reduce((lowest, node) => Math.min(lowest, Math.floor(node.zIndex)), 0);
|
|
814
|
+
const highestZ = worldNodes.reduce((highest, node) => Math.max(highest, Math.ceil(node.zIndex)), 0);
|
|
815
|
+
// Keep group-body hit order without changing the frames' visual stacking.
|
|
816
|
+
const hitOffset = highestZ - lowestZ + 3;
|
|
786
817
|
const dropTarget = dragDropTarget.value;
|
|
787
818
|
const dropTargetGroup = dropTarget ? nodes.value.get(dropTarget.groupId) : undefined;
|
|
788
819
|
const draggedNode = dropTarget ? nodes.value.get(dropTarget.nodeId) : undefined;
|
|
@@ -827,7 +858,9 @@ export function CanvasViewport({
|
|
|
827
858
|
width: '100%',
|
|
828
859
|
height: '100%',
|
|
829
860
|
position: 'relative',
|
|
830
|
-
|
|
861
|
+
// The camera owns scrolling. Hidden permits native focus/resize scroll,
|
|
862
|
+
// adding an untracked offset underneath our viewport transform.
|
|
863
|
+
overflow: 'clip',
|
|
831
864
|
cursor:
|
|
832
865
|
annotationTool === 'eraser'
|
|
833
866
|
? 'cell'
|
|
@@ -849,6 +882,9 @@ export function CanvasViewport({
|
|
|
849
882
|
style={{
|
|
850
883
|
transform: `matrix(${v.scale}, 0, 0, ${v.scale}, ${v.x}, ${v.y})`,
|
|
851
884
|
transformOrigin: '0 0',
|
|
885
|
+
'--node-port-z': lowestZ - 1,
|
|
886
|
+
'--node-hit-offset': hitOffset,
|
|
887
|
+
'--edge-line-z': lowestZ - hitOffset - 1,
|
|
852
888
|
position: 'absolute',
|
|
853
889
|
top: 0,
|
|
854
890
|
left: 0,
|
|
@@ -858,7 +894,9 @@ export function CanvasViewport({
|
|
|
858
894
|
<IntentLayer />
|
|
859
895
|
<AgentPresenceLayer />
|
|
860
896
|
<HumanPresenceLayer />
|
|
861
|
-
<EdgeLayer nodes={nodes} edges={edges}
|
|
897
|
+
<EdgeLayer nodes={nodes} edges={edges} part="lines" />
|
|
898
|
+
<EdgeLayer nodes={nodes} edges={edges} part="pills" />
|
|
899
|
+
<EdgeLayer nodes={nodes} edges={edges} part="pill-hits" />
|
|
862
900
|
{draggingEdge.value && (
|
|
863
901
|
<div
|
|
864
902
|
class="edge-hint-pill"
|
|
@@ -893,9 +931,7 @@ export function CanvasViewport({
|
|
|
893
931
|
<AnnotationLayer annotations={Array.from(annotations.value.values())} />
|
|
894
932
|
{draftAnnotation && draftAnnotation.points.length >= 2 && <AnnotationLayer annotations={[draftAnnotation]} />}
|
|
895
933
|
{worldNodes.map((node) => (
|
|
896
|
-
<
|
|
897
|
-
{renderNodeContent(node)}
|
|
898
|
-
</CanvasNode>
|
|
934
|
+
<WorldNode key={node.id} node={node} onContextMenu={onNodeContextMenu} />
|
|
899
935
|
))}
|
|
900
936
|
{/* Snap alignment guide lines */}
|
|
901
937
|
{activeGuides.value && (
|
|
@@ -952,6 +988,8 @@ export function CanvasViewport({
|
|
|
952
988
|
}}
|
|
953
989
|
/>
|
|
954
990
|
)}
|
|
991
|
+
<NearPinLayer />
|
|
992
|
+
<NearPinCard />
|
|
955
993
|
{lassoStyle && <div class="lasso-rect" style={lassoStyle} />}
|
|
956
994
|
{dropActive && (
|
|
957
995
|
<div class="drop-zone-overlay">
|
|
@@ -1,22 +1,26 @@
|
|
|
1
1
|
import { useState } from 'preact/hooks';
|
|
2
|
+
import { BarHint } from './BarHint';
|
|
2
3
|
import { IconSteer, IconClose } from '../icons';
|
|
3
|
-
import {
|
|
4
|
+
import { nodes, toggleContextPin } from '../state/canvas-store';
|
|
5
|
+
import { activeContextPinnedNodeIds } from '../state/knowledge-store';
|
|
4
6
|
import { steerableAgents } from '../state/presence-store';
|
|
5
7
|
import { sendSteering } from '../state/session-store';
|
|
6
8
|
import { modChord } from '../utils/platform';
|
|
7
9
|
import { ChoiceMenu } from './ChoiceMenu';
|
|
10
|
+
import { agentSessionsEnabled } from '../state/workspace-settings';
|
|
11
|
+
import { useChromeHeight } from './use-chrome-height';
|
|
8
12
|
|
|
9
13
|
/**
|
|
10
14
|
* Command bar (rail-chrome-v2 phase 5): the human's steering surface while a
|
|
11
15
|
* session is attached — the design's floating composer, centered at the
|
|
12
|
-
* bottom of the canvas region. Above it, the pinned context as
|
|
16
|
+
* bottom of the canvas region. Above it, the pinned context as blue chips
|
|
13
17
|
* (× unpins — the same pin the node's own control toggles); the composer row
|
|
14
|
-
* posts an AX steering message to the session.
|
|
15
|
-
* in the top bar (`
|
|
16
|
-
* quiet board's pin bar only while attached.
|
|
18
|
+
* posts an AX steering message to the session. What is in the agent's
|
|
19
|
+
* context is summarised in the top bar's context chip (`ContextChip`).
|
|
17
20
|
*/
|
|
18
21
|
export function CommandBar() {
|
|
19
|
-
const
|
|
22
|
+
const barRef = useChromeHeight('--command-bar-height');
|
|
23
|
+
const pinnedIds = [...activeContextPinnedNodeIds.value];
|
|
20
24
|
const nodeMap = nodes.value;
|
|
21
25
|
const [draft, setDraft] = useState('');
|
|
22
26
|
const [sending, setSending] = useState(false);
|
|
@@ -43,7 +47,7 @@ export function CommandBar() {
|
|
|
43
47
|
|
|
44
48
|
const submit = async () => {
|
|
45
49
|
const message = draft.trim();
|
|
46
|
-
if (!message || sending) return;
|
|
50
|
+
if (!agentSessionsEnabled.value || !message || sending) return;
|
|
47
51
|
setSending(true);
|
|
48
52
|
const ok = await sendSteering(message, effectiveTarget);
|
|
49
53
|
setSending(false);
|
|
@@ -51,7 +55,13 @@ export function CommandBar() {
|
|
|
51
55
|
};
|
|
52
56
|
|
|
53
57
|
return (
|
|
54
|
-
<div
|
|
58
|
+
<div
|
|
59
|
+
ref={barRef}
|
|
60
|
+
class="command-bar"
|
|
61
|
+
data-testid="command-bar"
|
|
62
|
+
hidden={!agentSessionsEnabled.value}
|
|
63
|
+
style={!agentSessionsEnabled.value ? { display: 'none' } : undefined}
|
|
64
|
+
>
|
|
55
65
|
{pinnedIds.length > 0 && (
|
|
56
66
|
<div class="command-bar-chips" aria-label="Pinned context">
|
|
57
67
|
{pinnedIds.map((id) => {
|
|
@@ -60,21 +70,17 @@ export function CommandBar() {
|
|
|
60
70
|
typeof node?.data.title === 'string' && node.data.title.trim() ? node.data.title : (node?.type ?? id);
|
|
61
71
|
return (
|
|
62
72
|
<span key={id} class="command-bar-chip" data-node-id={id}>
|
|
63
|
-
<span class="command-bar-chip-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
onClick={() => toggleContextPin(id)}
|
|
75
|
-
>
|
|
76
|
-
<IconClose size={14} />
|
|
77
|
-
</button>
|
|
73
|
+
<span class="command-bar-chip-label">{title}</span>
|
|
74
|
+
<BarHint label="Remove from context" side="up">
|
|
75
|
+
<button
|
|
76
|
+
type="button"
|
|
77
|
+
class="command-bar-chip-unpin"
|
|
78
|
+
aria-label={`Unpin ${title}`}
|
|
79
|
+
onClick={() => toggleContextPin(id)}
|
|
80
|
+
>
|
|
81
|
+
<IconClose size={14} />
|
|
82
|
+
</button>
|
|
83
|
+
</BarHint>
|
|
78
84
|
</span>
|
|
79
85
|
);
|
|
80
86
|
})}
|
|
@@ -13,12 +13,13 @@ import { saveCanvasTheme } from '../state/intent-bridge';
|
|
|
13
13
|
import { createNodeInView } from './create-in-view';
|
|
14
14
|
import { sessionActive } from '../state/presence-store';
|
|
15
15
|
import { startSession } from '../state/session-store';
|
|
16
|
+
import { agentSessionsEnabled } from '../state/workspace-settings';
|
|
16
17
|
import { TYPE_LABELS, type CanvasNodeState } from '../types';
|
|
17
18
|
import { invalidateTokenCache } from '../theme/tokens';
|
|
18
19
|
import { clearThemeOverride } from '../state/theme-override';
|
|
19
20
|
import { canvasThemeScheme } from '../../shared/themes.js';
|
|
20
21
|
import { getNodeIcon, IconArrange, IconFitAll, IconMinimap, IconMoon, IconNodeMarkdown, IconSteer } from '../icons';
|
|
21
|
-
import {
|
|
22
|
+
import { openMemoryTarget } from '../state/boards-store';
|
|
22
23
|
import { requestJson } from '../state/intent-bridge';
|
|
23
24
|
|
|
24
25
|
import { modChord } from '../utils/platform';
|
|
@@ -219,7 +220,7 @@ export function CommandPalette({ onClose, onToggleMinimap }: { onClose: () => vo
|
|
|
219
220
|
onClose();
|
|
220
221
|
},
|
|
221
222
|
},
|
|
222
|
-
...(sessionActive.value
|
|
223
|
+
...(!agentSessionsEnabled.value || sessionActive.value
|
|
223
224
|
? []
|
|
224
225
|
: [
|
|
225
226
|
{
|
|
@@ -341,16 +342,8 @@ export function CommandPalette({ onClose, onToggleMinimap }: { onClose: () => vo
|
|
|
341
342
|
indices: [],
|
|
342
343
|
action: () => {
|
|
343
344
|
void (async () => {
|
|
344
|
-
await
|
|
345
|
-
|
|
346
|
-
for (let attempt = 0; attempt < 40 && activeBoardId.value === hit.boardId; attempt++) {
|
|
347
|
-
if (nodes.value.has(hit.cardId)) {
|
|
348
|
-
focusNode(hit.cardId);
|
|
349
|
-
onClose();
|
|
350
|
-
return;
|
|
351
|
-
}
|
|
352
|
-
await new Promise((resolve) => window.setTimeout(resolve, 25));
|
|
353
|
-
}
|
|
345
|
+
await openMemoryTarget(hit);
|
|
346
|
+
onClose();
|
|
354
347
|
})();
|
|
355
348
|
},
|
|
356
349
|
});
|
|
@@ -11,7 +11,8 @@ import {
|
|
|
11
11
|
* Degraded connection states (rail-chrome-v2 phase 7, design item 14), mapped
|
|
12
12
|
* onto the transport the bridge already runs:
|
|
13
13
|
* - reconnecting: the stream (SSE or the polling fallback) is down and the
|
|
14
|
-
* bridge is backing off.
|
|
14
|
+
* bridge is backing off. HTTP saves may need renewed capability after a
|
|
15
|
+
* daemon restart; the board may be stale.
|
|
15
16
|
* - resyncing: a transport came back after a drop and the full `connected`
|
|
16
17
|
* snapshot is being applied — the seq cursor was dropped, the board reloads
|
|
17
18
|
* from the server's snapshot without touching local edits.
|
|
@@ -39,8 +40,7 @@ export function ConnectionBanner() {
|
|
|
39
40
|
↻
|
|
40
41
|
</span>
|
|
41
42
|
<span class="connection-banner-text">
|
|
42
|
-
<b>Reconnecting…</b> live updates are paused;
|
|
43
|
-
returns.
|
|
43
|
+
<b>Reconnecting…</b> live updates are paused; retry refused saves after reconnect. The board may be stale.
|
|
44
44
|
</span>
|
|
45
45
|
<span class="connection-banner-meta">
|
|
46
46
|
{attempt > 0 ? `retry ${attempt}` : 'retrying'}
|
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
import { age } from '../utils/time-ago';
|
|
2
|
+
import { useEffect, useRef, useState } from 'preact/hooks';
|
|
3
|
+
import { IconClose, IconPin } from '../icons';
|
|
4
|
+
import { attentionHistoryOpen } from '../state/attention-store';
|
|
5
|
+
import { activeBoardId, boardList, browsingMemory, setBoardPinned } from '../state/boards-store';
|
|
6
|
+
import { clearContextPins, nodes, toggleContextPin } from '../state/canvas-store';
|
|
7
|
+
import { activeContextPinnedNodeIds } from '../state/knowledge-store';
|
|
8
|
+
import {
|
|
9
|
+
approxTokens,
|
|
10
|
+
briefSize,
|
|
11
|
+
formatTokens,
|
|
12
|
+
pinnedBoardReads,
|
|
13
|
+
refreshContextChip,
|
|
14
|
+
} from '../state/context-chip-store';
|
|
15
|
+
import { contextPinMeta, linksChanged, pinnedReadState, writerName } from '../state/context-status-store';
|
|
16
|
+
import { nearPins } from '../state/near-pin-store';
|
|
17
|
+
import { activeSession } from '../state/presence-store';
|
|
18
|
+
import { BarHint } from './BarHint';
|
|
19
|
+
import { GLYPHS } from './NodeContextMark';
|
|
20
|
+
|
|
21
|
+
/** The quiet footer when Agent sessions is off; pins still curate agent context. */
|
|
22
|
+
export function ContextPinBar() {
|
|
23
|
+
const count = activeContextPinnedNodeIds.value.size;
|
|
24
|
+
if (count === 0 || attentionHistoryOpen.value) return null;
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<div class="context-pin-bar" role="region" aria-label="Pinned context">
|
|
28
|
+
<IconPin size={14} />
|
|
29
|
+
<span>{plural(count, 'node')} in context</span>
|
|
30
|
+
<BarHint label="Clear all context pins" side="up" align="end">
|
|
31
|
+
<button type="button" aria-label="Clear all context pins" onClick={clearContextPins}>
|
|
32
|
+
<IconClose size={12} />
|
|
33
|
+
</button>
|
|
34
|
+
</BarHint>
|
|
35
|
+
</div>
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* One context chip (docs/design/ContextChip.dc.html): everything in the agent's
|
|
41
|
+
* context, in brief order — cards pinned on this board, near (derived, muted),
|
|
42
|
+
* pinned boards (sent as maps) — with what the brief costs in tokens. Opened,
|
|
43
|
+
* one list with read state and unpin.
|
|
44
|
+
*/
|
|
45
|
+
|
|
46
|
+
function plural(count: number, word: string): string {
|
|
47
|
+
return `${count} ${word}${count === 1 ? '' : 's'}`;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function nodeTitle(id: string): string {
|
|
51
|
+
const node = nodes.value.get(id);
|
|
52
|
+
return typeof node?.data.title === 'string' && node.data.title ? node.data.title : id;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function ContextChip() {
|
|
56
|
+
const [open, setOpen] = useState(false);
|
|
57
|
+
const root = useRef<HTMLDivElement>(null);
|
|
58
|
+
const chip = useRef<HTMLButtonElement>(null);
|
|
59
|
+
const boardId = activeBoardId.value;
|
|
60
|
+
|
|
61
|
+
useEffect(() => {
|
|
62
|
+
if (!open) return;
|
|
63
|
+
refreshContextChip();
|
|
64
|
+
const onKey = (event: KeyboardEvent) => {
|
|
65
|
+
if (event.key !== 'Escape') return;
|
|
66
|
+
setOpen(false);
|
|
67
|
+
chip.current?.focus();
|
|
68
|
+
};
|
|
69
|
+
const onDown = (event: PointerEvent) => {
|
|
70
|
+
if (root.current && !root.current.contains(event.target as Node)) setOpen(false);
|
|
71
|
+
};
|
|
72
|
+
window.addEventListener('keydown', onKey);
|
|
73
|
+
window.addEventListener('pointerdown', onDown);
|
|
74
|
+
return () => {
|
|
75
|
+
window.removeEventListener('keydown', onKey);
|
|
76
|
+
window.removeEventListener('pointerdown', onDown);
|
|
77
|
+
};
|
|
78
|
+
}, [open]);
|
|
79
|
+
|
|
80
|
+
const library = browsingMemory.value;
|
|
81
|
+
if (!boardId && !library) return null;
|
|
82
|
+
|
|
83
|
+
// Library curation must not expose controls for the agent's hidden active cards.
|
|
84
|
+
const cardIds = library ? [] : [...activeContextPinnedNodeIds.value];
|
|
85
|
+
const near = library ? [] : [...nearPins.value.entries()];
|
|
86
|
+
const boards = boardList.value.filter((board) => board.pin && (library || board.id !== boardId));
|
|
87
|
+
const empty = cardIds.length === 0 && boards.length === 0;
|
|
88
|
+
const pinners = [
|
|
89
|
+
...cardIds.map((id) => contextPinMeta.value[id]?.pinnedBy),
|
|
90
|
+
...boards.map((board) => board.pin?.pinnedBy),
|
|
91
|
+
];
|
|
92
|
+
const agentOnly = !empty && pinners.every((by) => by?.actor === 'agent');
|
|
93
|
+
|
|
94
|
+
const size = briefSize.value;
|
|
95
|
+
const cost = size ? `≈ ${approxTokens(size.chars)} tokens` : '';
|
|
96
|
+
const host = activeSession.value;
|
|
97
|
+
|
|
98
|
+
const segments: Array<{ text: string; kind?: 'near' | 'agent' }> = [];
|
|
99
|
+
if (cardIds.length) segments.push({ text: plural(cardIds.length, 'card') });
|
|
100
|
+
if (near.length) segments.push({ text: `${near.length} near`, kind: 'near' });
|
|
101
|
+
if (boards.length) segments.push({ text: plural(boards.length, 'board') });
|
|
102
|
+
if (agentOnly) {
|
|
103
|
+
const by = pinners.find((pinner) => pinner?.actor === 'agent');
|
|
104
|
+
segments.push({ text: `by ${by ? writerName(by) : 'agent'}`, kind: 'agent' });
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return (
|
|
108
|
+
<div class="context-chip-wrap" ref={root}>
|
|
109
|
+
<button
|
|
110
|
+
ref={chip}
|
|
111
|
+
type="button"
|
|
112
|
+
class={`context-chip${empty ? ' is-empty' : ''}${open ? ' is-open' : ''}`}
|
|
113
|
+
aria-expanded={open}
|
|
114
|
+
aria-haspopup="dialog"
|
|
115
|
+
data-testid="context-chip"
|
|
116
|
+
onClick={() => setOpen(!open)}
|
|
117
|
+
>
|
|
118
|
+
<span class="context-chip-pin">
|
|
119
|
+
<IconPin size={13} />
|
|
120
|
+
{agentOnly && <span class="context-chip-agent-dot" aria-hidden="true" />}
|
|
121
|
+
</span>
|
|
122
|
+
{empty ? (
|
|
123
|
+
<span>Nothing in context</span>
|
|
124
|
+
) : (
|
|
125
|
+
<>
|
|
126
|
+
<span class="context-chip-wide">
|
|
127
|
+
{segments.map((segment, index) => (
|
|
128
|
+
<span key={segment.text} class={segment.kind ? `is-${segment.kind}` : undefined}>
|
|
129
|
+
{index > 0 ? ' · ' : ''}
|
|
130
|
+
{segment.text}
|
|
131
|
+
</span>
|
|
132
|
+
))}
|
|
133
|
+
</span>
|
|
134
|
+
<span class="context-chip-narrow">{cardIds.length + boards.length} in context</span>
|
|
135
|
+
{cost && (
|
|
136
|
+
<span class="context-chip-cost" data-testid="token-cost">
|
|
137
|
+
{cost}
|
|
138
|
+
</span>
|
|
139
|
+
)}
|
|
140
|
+
</>
|
|
141
|
+
)}
|
|
142
|
+
</button>
|
|
143
|
+
{open && (
|
|
144
|
+
<div class="context-panel" role="dialog" aria-label="In the agent's context">
|
|
145
|
+
<div class="context-panel-head">
|
|
146
|
+
<span class="context-panel-title">In the agent’s context</span>
|
|
147
|
+
{cost && <span class="context-panel-mono">{cost}</span>}
|
|
148
|
+
</div>
|
|
149
|
+
{empty ? (
|
|
150
|
+
<p class="context-panel-cap">
|
|
151
|
+
Nothing is pinned. Pin a card or a board and the agent gets it first; it can still find and pull anything
|
|
152
|
+
else on the board.
|
|
153
|
+
</p>
|
|
154
|
+
) : (
|
|
155
|
+
<>
|
|
156
|
+
<p class="context-panel-cap">
|
|
157
|
+
What the canvas sends the agent costs {cost || 'a few tokens'}.
|
|
158
|
+
{host?.contextUsage
|
|
159
|
+
? ` ${host.label}’s whole context holds ${formatTokens(host.contextUsage.used)} tokens.`
|
|
160
|
+
: ''}
|
|
161
|
+
</p>
|
|
162
|
+
|
|
163
|
+
<div class="context-panel-section">
|
|
164
|
+
<span>Cards on this board · {cardIds.length}</span>
|
|
165
|
+
{cardIds.length > 0 && (
|
|
166
|
+
<button type="button" class="context-panel-link" onClick={() => clearContextPins()}>
|
|
167
|
+
Clear all
|
|
168
|
+
</button>
|
|
169
|
+
)}
|
|
170
|
+
</div>
|
|
171
|
+
<ul class="context-panel-list">
|
|
172
|
+
{cardIds.map((id) => {
|
|
173
|
+
const node = nodes.value.get(id);
|
|
174
|
+
const state = pinnedReadState(id, node?.contentRevision ?? 0);
|
|
175
|
+
const links = state.kind === 'read' && linksChanged(id, node?.linksRevision ?? 0);
|
|
176
|
+
const byAgent = contextPinMeta.value[id]?.pinnedBy.actor === 'agent';
|
|
177
|
+
return (
|
|
178
|
+
<li key={id} class="context-panel-row">
|
|
179
|
+
<span class="context-panel-pin">
|
|
180
|
+
<IconPin size={9} />
|
|
181
|
+
{byAgent && <span class="context-chip-agent-dot" aria-hidden="true" />}
|
|
182
|
+
</span>
|
|
183
|
+
<span class="context-panel-name">{nodeTitle(id)}</span>
|
|
184
|
+
{links ? (
|
|
185
|
+
<span class="context-tag is-links">
|
|
186
|
+
<svg viewBox="0 0 24 24" width="10" height="10" aria-hidden="true">
|
|
187
|
+
<path d="M10 14a4 4 0 0 0 5.7 0l3-3a4 4 0 0 0-5.7-5.7l-1 1M14 10a4 4 0 0 0-5.7 0l-3 3a4 4 0 0 0 5.7 5.7l1-1" />
|
|
188
|
+
</svg>
|
|
189
|
+
links changed
|
|
190
|
+
</span>
|
|
191
|
+
) : (
|
|
192
|
+
<ReadTag state={state.kind} at={state.kind === 'not-read' ? null : state.at} />
|
|
193
|
+
)}
|
|
194
|
+
<button
|
|
195
|
+
type="button"
|
|
196
|
+
class="context-panel-x"
|
|
197
|
+
aria-label={`Unpin ${nodeTitle(id)}`}
|
|
198
|
+
onClick={() => toggleContextPin(id)}
|
|
199
|
+
>
|
|
200
|
+
×
|
|
201
|
+
</button>
|
|
202
|
+
</li>
|
|
203
|
+
);
|
|
204
|
+
})}
|
|
205
|
+
</ul>
|
|
206
|
+
|
|
207
|
+
{near.length > 0 && (
|
|
208
|
+
<>
|
|
209
|
+
<div class="context-panel-section">
|
|
210
|
+
<span>Near · {near.length}</span>
|
|
211
|
+
<span class="context-panel-cap-inline">
|
|
212
|
+
derived · <span class="context-panel-wide">sent as </span>title + summary
|
|
213
|
+
</span>
|
|
214
|
+
</div>
|
|
215
|
+
<ul class="context-panel-list">
|
|
216
|
+
{near.map(([id, pins]) => (
|
|
217
|
+
<li key={id} class="context-panel-row is-near">
|
|
218
|
+
<span class="context-panel-near-ring" aria-hidden="true" />
|
|
219
|
+
<span class="context-panel-name is-soft">{nodeTitle(id)}</span>
|
|
220
|
+
<span class="context-panel-cap-inline">near {pins[0]?.pinTitle}</span>
|
|
221
|
+
<button type="button" class="context-panel-promote" onClick={() => toggleContextPin(id)}>
|
|
222
|
+
Pin
|
|
223
|
+
</button>
|
|
224
|
+
</li>
|
|
225
|
+
))}
|
|
226
|
+
</ul>
|
|
227
|
+
</>
|
|
228
|
+
)}
|
|
229
|
+
|
|
230
|
+
{boards.length > 0 && (
|
|
231
|
+
<>
|
|
232
|
+
<div class="context-panel-section">
|
|
233
|
+
<span>Boards · {boards.length}</span>
|
|
234
|
+
<span class="context-panel-cap-inline">sent as a map</span>
|
|
235
|
+
</div>
|
|
236
|
+
<ul class="context-panel-list">
|
|
237
|
+
{boards.map((board) => {
|
|
238
|
+
const read = pinnedBoardReads.value.get(board.id);
|
|
239
|
+
return (
|
|
240
|
+
<li key={board.id} class="context-panel-row">
|
|
241
|
+
<svg
|
|
242
|
+
class="context-panel-board"
|
|
243
|
+
viewBox="0 0 24 24"
|
|
244
|
+
width="14"
|
|
245
|
+
height="14"
|
|
246
|
+
aria-hidden="true"
|
|
247
|
+
>
|
|
248
|
+
<path d="M4 4h16v16H4zM9 9h6v6H9z" />
|
|
249
|
+
</svg>
|
|
250
|
+
<span class="context-panel-name">{board.name}</span>
|
|
251
|
+
<ReadTag state={read ? 'read' : 'not-read'} at={read?.lastReadAt ?? null} />
|
|
252
|
+
<span class="context-panel-cap-inline">map</span>
|
|
253
|
+
<button
|
|
254
|
+
type="button"
|
|
255
|
+
class="context-panel-x"
|
|
256
|
+
aria-label={`Unpin ${board.name}`}
|
|
257
|
+
onClick={() => void setBoardPinned(board.id, false)}
|
|
258
|
+
>
|
|
259
|
+
×
|
|
260
|
+
</button>
|
|
261
|
+
</li>
|
|
262
|
+
);
|
|
263
|
+
})}
|
|
264
|
+
</ul>
|
|
265
|
+
<p class="context-panel-cap">
|
|
266
|
+
A pinned board travels as a map: its folder, README summary, each pinned card’s title and summary,
|
|
267
|
+
and its links and backlinks. The agent pulls a card or the whole board in full when it decides it
|
|
268
|
+
needs to.
|
|
269
|
+
</p>
|
|
270
|
+
</>
|
|
271
|
+
)}
|
|
272
|
+
</>
|
|
273
|
+
)}
|
|
274
|
+
</div>
|
|
275
|
+
)}
|
|
276
|
+
</div>
|
|
277
|
+
);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/** A read-state tag with its glyph (ContextChip.dc.html): eye read, eye-off not read, warn changed. */
|
|
281
|
+
function ReadTag({ state, at }: { state: 'read' | 'not-read' | 'changed'; at: string | null }) {
|
|
282
|
+
const [tone, glyph, word] =
|
|
283
|
+
state === 'changed'
|
|
284
|
+
? (['is-changed', GLYPHS.warn, 'changed'] as const)
|
|
285
|
+
: state === 'read' && at
|
|
286
|
+
? (['is-read', GLYPHS.eye, `read ${age(at)}`] as const)
|
|
287
|
+
: (['is-not-read', GLYPHS.eyeOff, 'not read yet'] as const);
|
|
288
|
+
return (
|
|
289
|
+
<span class={`context-tag ${tone}`}>
|
|
290
|
+
<svg viewBox="0 0 24 24" width="11" height="11" aria-hidden="true">
|
|
291
|
+
<path d={glyph} />
|
|
292
|
+
</svg>
|
|
293
|
+
{word}
|
|
294
|
+
</span>
|
|
295
|
+
);
|
|
296
|
+
}
|