pmx-canvas 0.8.2 → 0.9.1
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 +59 -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/human-store.d.ts +2 -1
- 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/evals/evals.json +224 -0
- 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 +88 -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 +543 -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 +6 -3
- 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/human-store.ts +13 -7
- 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 +235 -32
- 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,8 +1,16 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { boardList } from '../state/boards-store';
|
|
2
|
+
import { cardQualification, effectiveLifecycleFilter as lifecycleFilter } from '../state/knowledge-store';
|
|
3
|
+
import { knowledgeLifecycleEnabled } from '../state/workspace-settings';
|
|
4
|
+
import { relationHighlight } from '../state/relation-store';
|
|
5
|
+
import { LifecycleFooter } from './LifecycleChrome';
|
|
6
|
+
import { BarHint } from './BarHint';
|
|
7
|
+
import { CrossBoardReferences } from './CrossBoardReferences';
|
|
8
|
+
import { BoardPinButton } from './BoardPinButton';
|
|
9
|
+
import { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'preact/hooks';
|
|
10
|
+
import { mutatingNodeIds, sessionChromeActive } from '../state/presence-store';
|
|
3
11
|
import { attentionPulseNodeIds } from '../state/attention-store';
|
|
4
|
-
import { isAgentPin, NearPinMark, NodeContextMark } from './NodeContextMark';
|
|
5
|
-
import { activityLensNodeIds } from '../state/session-store';
|
|
12
|
+
import { type ChipFold, headerChip, isAgentPin, NearPinMark, NodeContextMark, NodeTypeIcon } from './NodeContextMark';
|
|
13
|
+
import { activityLensNodeIds, sessionEditedIds } from '../state/session-store';
|
|
6
14
|
import {
|
|
7
15
|
activeNodeId,
|
|
8
16
|
activeNeighborNodeIds,
|
|
@@ -37,6 +45,7 @@ import {
|
|
|
37
45
|
updateNodeFromClient,
|
|
38
46
|
} from '../state/intent-bridge';
|
|
39
47
|
import { reportHumanGrab, takeOverNode, yieldedNodes } from '../state/human-store';
|
|
48
|
+
import { hoveredNodeId, noteNearAtGrab, settleNearAfterDrop } from '../state/near-pin-store';
|
|
40
49
|
import { AxStepControls } from '../nodes/AxStepControls';
|
|
41
50
|
import { getNodeIcon, IconChevronRight, IconClose, IconMore, IconExpand, IconPin } from '../icons';
|
|
42
51
|
import { EXPANDABLE_TYPES, TYPE_LABELS } from '../types';
|
|
@@ -118,6 +127,43 @@ export function nodeChromeScale(viewportScale: number, nodeHeight: number, nodeW
|
|
|
118
127
|
return Math.max(1, bounded);
|
|
119
128
|
}
|
|
120
129
|
|
|
130
|
+
/** A header title keeps at least this much room, or all of it when shorter (HeaderMarks.dc.html). */
|
|
131
|
+
const TITLE_MIN_WIDTH = 140;
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Whether the header's one state chip shows its word, only its glyph, or folds
|
|
135
|
+
* into the type icon's hint, so the title keeps TITLE_MIN_WIDTH (scaled with
|
|
136
|
+
* the chrome). Measured after each render, one step at a time; a different
|
|
137
|
+
* title, chip, width or zoom starts from the word again. Amber never folds.
|
|
138
|
+
*/
|
|
139
|
+
function useHeaderFold(
|
|
140
|
+
titleRef: { current: HTMLSpanElement | null },
|
|
141
|
+
chip: { amber: boolean; folded: string } | null,
|
|
142
|
+
title: string,
|
|
143
|
+
width: number,
|
|
144
|
+
scale: number,
|
|
145
|
+
): ChipFold {
|
|
146
|
+
const [fold, setFold] = useState<ChipFold>('word');
|
|
147
|
+
const key = `${title}\0${chip ? (chip.amber ? 'amber' : chip.folded) : ''}\0${width}\0${scale.toFixed(3)}`;
|
|
148
|
+
const keyRef = useRef(key);
|
|
149
|
+
useLayoutEffect(() => {
|
|
150
|
+
if (keyRef.current !== key) {
|
|
151
|
+
keyRef.current = key;
|
|
152
|
+
if (fold !== 'word') {
|
|
153
|
+
setFold('word');
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
const el = titleRef.current;
|
|
158
|
+
if (!el || !chip || chip.amber || fold === 'hidden') return;
|
|
159
|
+
const room = Math.min(el.scrollWidth, TITLE_MIN_WIDTH * scale);
|
|
160
|
+
if (el.clientWidth + 0.5 < room) setFold(fold === 'word' ? 'glyph' : 'hidden');
|
|
161
|
+
// Measures only when what fits can change: a moved card keeps its key, so a
|
|
162
|
+
// drag frame does not force a layout read.
|
|
163
|
+
}, [key, fold]);
|
|
164
|
+
return chip ? fold : 'word';
|
|
165
|
+
}
|
|
166
|
+
|
|
121
167
|
interface CanvasNodeProps {
|
|
122
168
|
node: CanvasNodeState;
|
|
123
169
|
children: preact.ComponentChildren;
|
|
@@ -128,6 +174,12 @@ export function CanvasNode({ node, children, onContextMenu }: CanvasNodeProps) {
|
|
|
128
174
|
const isActive = activeNodeId.value === node.id;
|
|
129
175
|
const isSelected = selectedNodeIds.value.has(node.id);
|
|
130
176
|
const isContextPinned = contextPinnedNodeIds.value.has(node.id);
|
|
177
|
+
const titleRef = useRef<HTMLSpanElement>(null);
|
|
178
|
+
const shellRef = useRef<HTMLDivElement>(null);
|
|
179
|
+
const portalTarget =
|
|
180
|
+
node.type === 'board' && typeof node.data.boardId === 'string'
|
|
181
|
+
? (boardList.value.find((board) => board.id === node.data.boardId) ?? null)
|
|
182
|
+
: null;
|
|
131
183
|
const isAttentionPulse = attentionPulseNodeIds.value.has(node.id);
|
|
132
184
|
const isNeighbor = !isActive && activeNeighborNodeIds.value.has(node.id);
|
|
133
185
|
const searchSet = searchHighlightIds.value;
|
|
@@ -160,6 +212,7 @@ export function CanvasNode({ node, children, onContextMenu }: CanvasNodeProps) {
|
|
|
160
212
|
clearSnapCache();
|
|
161
213
|
activeGuides.value = null;
|
|
162
214
|
reportHumanGrab(null);
|
|
215
|
+
settleNearAfterDrop(node.id);
|
|
163
216
|
if (escListener.current) {
|
|
164
217
|
document.removeEventListener('keydown', escListener.current);
|
|
165
218
|
escListener.current = null;
|
|
@@ -258,6 +311,7 @@ export function CanvasNode({ node, children, onContextMenu }: CanvasNodeProps) {
|
|
|
258
311
|
bringToFront(node.id);
|
|
259
312
|
// User wins (phase 8): hold the node for the drag; an agent mid-edit yields.
|
|
260
313
|
reportHumanGrab(node.id);
|
|
314
|
+
noteNearAtGrab(node.id);
|
|
261
315
|
takeOverNode(node.id, typeof node.data.title === 'string' && node.data.title ? node.data.title : node.type);
|
|
262
316
|
buildSnapCache(node.id, nodes.value.values());
|
|
263
317
|
if (node.type !== 'group') {
|
|
@@ -394,6 +448,7 @@ export function CanvasNode({ node, children, onContextMenu }: CanvasNodeProps) {
|
|
|
394
448
|
const isStrictSize = node.data.strictSize === true;
|
|
395
449
|
const viewportScale = Math.max(viewport.value.scale, 0.01);
|
|
396
450
|
const chromeScale = nodeChromeScale(viewportScale, node.collapsed ? 0 : node.size.height, node.size.width);
|
|
451
|
+
const chipFold = useHeaderFold(titleRef, headerChip(node, isContextPinned), title, node.size.width, chromeScale);
|
|
397
452
|
|
|
398
453
|
const groupColor = isGroup && node.data.color ? (node.data.color as string) : undefined;
|
|
399
454
|
const nodeStyle: Record<string, string | number> = {
|
|
@@ -408,7 +463,7 @@ export function CanvasNode({ node, children, onContextMenu }: CanvasNodeProps) {
|
|
|
408
463
|
};
|
|
409
464
|
// Shimmer while an attached agent is mutating this node (rail-chrome-v2
|
|
410
465
|
// phase 3). Gated on sessionActive: the quiet board never shimmers.
|
|
411
|
-
const isAgentMutating =
|
|
466
|
+
const isAgentMutating = sessionChromeActive.value && mutatingNodeIds.value.has(node.id);
|
|
412
467
|
const nodeClass = [
|
|
413
468
|
'canvas-node',
|
|
414
469
|
isActive ? 'active' : '',
|
|
@@ -424,7 +479,16 @@ export function CanvasNode({ node, children, onContextMenu }: CanvasNodeProps) {
|
|
|
424
479
|
isGroup ? 'group-node' : '',
|
|
425
480
|
isStrictSize ? 'strict-size' : '',
|
|
426
481
|
isAgentMutating ? 'agent-mutating' : '',
|
|
482
|
+
sessionChromeActive.value && sessionEditedIds.value.has(node.id) ? 'session-edited' : '',
|
|
427
483
|
activityLensNodeIds.value && !activityLensNodeIds.value.has(node.id) ? 'lens-dimmed' : '',
|
|
484
|
+
relationHighlight.value && !relationHighlight.value.cards.has(node.id) ? 'relation-dimmed' : '',
|
|
485
|
+
knowledgeLifecycleEnabled.value && cardQualification(node.id)?.retired ? 'lifecycle-retired' : '',
|
|
486
|
+
lifecycleFilter.value === 'To review' &&
|
|
487
|
+
!cardQualification(node.id)?.due &&
|
|
488
|
+
!cardQualification(node.id)?.sources.length
|
|
489
|
+
? 'lifecycle-filter-dimmed'
|
|
490
|
+
: '',
|
|
491
|
+
lifecycleFilter.value === 'Retired' && !cardQualification(node.id)?.retired ? 'lifecycle-filter-dimmed' : '',
|
|
428
492
|
]
|
|
429
493
|
.filter(Boolean)
|
|
430
494
|
.join(' ');
|
|
@@ -457,7 +521,6 @@ export function CanvasNode({ node, children, onContextMenu }: CanvasNodeProps) {
|
|
|
457
521
|
style={{ ...nodeStyle, width: 'auto', height: 'auto' }}
|
|
458
522
|
onPointerDown={handlePointerDown}
|
|
459
523
|
onDblClick={handleDblClick}
|
|
460
|
-
title="Expand group"
|
|
461
524
|
data-testid="group-chip"
|
|
462
525
|
>
|
|
463
526
|
<div class="node-titlebar group-chip-inner" onPointerDown={handleTitlePointerDown}>
|
|
@@ -484,280 +547,340 @@ export function CanvasNode({ node, children, onContextMenu }: CanvasNodeProps) {
|
|
|
484
547
|
</div>
|
|
485
548
|
</div>
|
|
486
549
|
</div>
|
|
550
|
+
<LifecycleFooter nodeId={node.id} />
|
|
487
551
|
</div>
|
|
488
552
|
);
|
|
489
553
|
}
|
|
490
554
|
|
|
491
555
|
return (
|
|
556
|
+
// Position/size without a stacking context: sibling ports participate in
|
|
557
|
+
// the world's lower hit plane, while the card keeps its own z-order.
|
|
492
558
|
<div
|
|
493
|
-
class=
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
// Shift+click toggles multi-selection; the browser would otherwise extend
|
|
501
|
-
// a text selection from the previous card across this one.
|
|
502
|
-
onMouseDown={(e) => {
|
|
503
|
-
if (e.shiftKey) e.preventDefault();
|
|
559
|
+
class="canvas-node-owner"
|
|
560
|
+
style={{ ...nodeStyle, zIndex: 'auto' }}
|
|
561
|
+
onPointerEnter={() => {
|
|
562
|
+
hoveredNodeId.value = node.id;
|
|
563
|
+
}}
|
|
564
|
+
onPointerLeave={() => {
|
|
565
|
+
if (hoveredNodeId.value === node.id) hoveredNodeId.value = null;
|
|
504
566
|
}}
|
|
505
|
-
onDblClick={handleDblClick}
|
|
506
567
|
>
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
type="button"
|
|
560
|
-
class="group-action"
|
|
561
|
-
title="Collapse group"
|
|
562
|
-
aria-label="Collapse group"
|
|
563
|
-
onClick={(e) => {
|
|
564
|
-
e.stopPropagation();
|
|
565
|
-
toggleCollapsed(node.id);
|
|
566
|
-
}}
|
|
567
|
-
>
|
|
568
|
-
<svg
|
|
569
|
-
width="11"
|
|
570
|
-
height="11"
|
|
571
|
-
viewBox="0 0 16 16"
|
|
572
|
-
fill="none"
|
|
573
|
-
stroke="currentColor"
|
|
574
|
-
stroke-width="1.8"
|
|
575
|
-
stroke-linecap="round"
|
|
576
|
-
stroke-linejoin="round"
|
|
577
|
-
aria-hidden="true"
|
|
578
|
-
>
|
|
579
|
-
<path d="M3 10 L8 5 L13 10" />
|
|
580
|
-
</svg>
|
|
581
|
-
</button>
|
|
582
|
-
<button
|
|
583
|
-
type="button"
|
|
584
|
-
class="group-action"
|
|
585
|
-
title="Rename · Ungroup · Pin all to context"
|
|
586
|
-
aria-label="Group menu"
|
|
587
|
-
aria-expanded={groupMenuOpen}
|
|
588
|
-
onClick={(e) => {
|
|
589
|
-
e.stopPropagation();
|
|
590
|
-
setGroupMenuOpen((open) => !open);
|
|
591
|
-
}}
|
|
592
|
-
>
|
|
593
|
-
<IconMore size={14} />
|
|
594
|
-
</button>
|
|
595
|
-
{groupMenuOpen && (
|
|
596
|
-
<div class="group-menu" role="menu" onPointerDown={(e) => e.stopPropagation()}>
|
|
597
|
-
<button
|
|
598
|
-
type="button"
|
|
599
|
-
role="menuitem"
|
|
600
|
-
onClick={(e) => {
|
|
601
|
-
e.stopPropagation();
|
|
602
|
-
setGroupMenuOpen(false);
|
|
603
|
-
setRenaming(true);
|
|
604
|
-
requestAnimationFrame(() => renameRef.current?.focus());
|
|
568
|
+
{isGroup && (
|
|
569
|
+
<div
|
|
570
|
+
class="group-frame-hit"
|
|
571
|
+
aria-hidden="true"
|
|
572
|
+
style={{ zIndex: `calc(${node.zIndex} - var(--node-hit-offset, 3))` }}
|
|
573
|
+
onPointerDown={(e) => {
|
|
574
|
+
handlePointerDown(e);
|
|
575
|
+
if (e.button === 0 && !e.shiftKey) shellRef.current?.focus({ preventScroll: true });
|
|
576
|
+
}}
|
|
577
|
+
onMouseDown={(e) => {
|
|
578
|
+
// Keep the shell's roving focus instead of the hit target's ancestor.
|
|
579
|
+
if (e.button === 0 || e.shiftKey) e.preventDefault();
|
|
580
|
+
}}
|
|
581
|
+
onDblClick={handleDblClick}
|
|
582
|
+
/>
|
|
583
|
+
)}
|
|
584
|
+
<div
|
|
585
|
+
ref={shellRef}
|
|
586
|
+
class={`${nodeClass}${isDropTarget ? ' is-drop-target' : ''}${isDropSource ? ' is-drop-source' : ''}${isEdgeTarget ? ' is-edge-target' : ''}${isGroup && groupMenuOpen ? ' group-menu-open' : ''}`}
|
|
587
|
+
data-node-type={node.type}
|
|
588
|
+
data-node-id={node.id}
|
|
589
|
+
style={{
|
|
590
|
+
position: 'relative',
|
|
591
|
+
left: 0,
|
|
592
|
+
top: 0,
|
|
593
|
+
width: '100%',
|
|
594
|
+
height: node.collapsed ? 'auto' : '100%',
|
|
595
|
+
zIndex: node.zIndex,
|
|
596
|
+
}}
|
|
597
|
+
tabIndex={isActive ? 0 : -1}
|
|
598
|
+
onKeyDown={handleNodeKeyDown}
|
|
599
|
+
onPointerDown={handlePointerDown}
|
|
600
|
+
// Shift+click toggles multi-selection; the browser would otherwise extend
|
|
601
|
+
// a text selection from the previous card across this one.
|
|
602
|
+
onMouseDown={(e) => {
|
|
603
|
+
if (e.shiftKey) e.preventDefault();
|
|
604
|
+
}}
|
|
605
|
+
onDblClick={handleDblClick}
|
|
606
|
+
>
|
|
607
|
+
<div class="node-content">
|
|
608
|
+
{isGroup && (
|
|
609
|
+
<div class="node-titlebar group-edge-row" onPointerDown={handleTitlePointerDown}>
|
|
610
|
+
<span class="group-name-pill">
|
|
611
|
+
{renaming ? (
|
|
612
|
+
<input
|
|
613
|
+
ref={renameRef}
|
|
614
|
+
class="node-title-input group-title-input"
|
|
615
|
+
value={title}
|
|
616
|
+
onBlur={(e) => commitRename((e.target as HTMLInputElement).value)}
|
|
617
|
+
onKeyDown={(e) => {
|
|
618
|
+
if (e.key === 'Enter') commitRename((e.target as HTMLInputElement).value);
|
|
619
|
+
if (e.key === 'Escape') setRenaming(false);
|
|
605
620
|
}}
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
621
|
+
onClick={(e) => e.stopPropagation()}
|
|
622
|
+
/>
|
|
623
|
+
) : (
|
|
624
|
+
<span class="group-name">{title}</span>
|
|
625
|
+
)}
|
|
626
|
+
<span class="group-count">{groupChildren.length}</span>
|
|
627
|
+
</span>
|
|
628
|
+
<span class="group-edge-spacer" />
|
|
629
|
+
<span class="group-actions" role="toolbar" aria-label={`${title} actions`}>
|
|
630
|
+
<BarHint label="Auto-arrange children" align="end">
|
|
609
631
|
<button
|
|
610
632
|
type="button"
|
|
611
|
-
|
|
633
|
+
class="group-action"
|
|
634
|
+
aria-label="Auto-arrange children"
|
|
635
|
+
disabled={groupChildren.length === 0}
|
|
612
636
|
onClick={(e) => {
|
|
613
637
|
e.stopPropagation();
|
|
614
|
-
|
|
615
|
-
// Dissolve — the same op an agent's ungroup runs.
|
|
616
|
-
removeNode(node.id);
|
|
617
|
-
void ungroupFromClient(node.id);
|
|
638
|
+
void addToGroupFromClient(node.id, groupChildren, 'grid');
|
|
618
639
|
}}
|
|
619
640
|
>
|
|
620
|
-
|
|
641
|
+
<svg
|
|
642
|
+
width="11"
|
|
643
|
+
height="11"
|
|
644
|
+
viewBox="0 0 16 16"
|
|
645
|
+
fill="none"
|
|
646
|
+
stroke="currentColor"
|
|
647
|
+
stroke-width="1.5"
|
|
648
|
+
aria-hidden="true"
|
|
649
|
+
>
|
|
650
|
+
<rect x="1.5" y="1.5" width="5" height="5" rx="1" />
|
|
651
|
+
<rect x="9.5" y="1.5" width="5" height="5" rx="1" />
|
|
652
|
+
<rect x="1.5" y="9.5" width="5" height="5" rx="1" />
|
|
653
|
+
<rect x="9.5" y="9.5" width="5" height="5" rx="1" />
|
|
654
|
+
</svg>
|
|
621
655
|
</button>
|
|
656
|
+
</BarHint>
|
|
657
|
+
<BarHint label="Collapse group" align="end">
|
|
622
658
|
<button
|
|
623
659
|
type="button"
|
|
624
|
-
|
|
625
|
-
|
|
660
|
+
class="group-action"
|
|
661
|
+
aria-label="Collapse group"
|
|
626
662
|
onClick={(e) => {
|
|
627
663
|
e.stopPropagation();
|
|
628
|
-
|
|
629
|
-
addContextPins(groupChildren);
|
|
664
|
+
toggleCollapsed(node.id);
|
|
630
665
|
}}
|
|
631
666
|
>
|
|
632
|
-
|
|
667
|
+
<svg
|
|
668
|
+
width="11"
|
|
669
|
+
height="11"
|
|
670
|
+
viewBox="0 0 16 16"
|
|
671
|
+
fill="none"
|
|
672
|
+
stroke="currentColor"
|
|
673
|
+
stroke-width="1.8"
|
|
674
|
+
stroke-linecap="round"
|
|
675
|
+
stroke-linejoin="round"
|
|
676
|
+
aria-hidden="true"
|
|
677
|
+
>
|
|
678
|
+
<path d="M3 10 L8 5 L13 10" />
|
|
679
|
+
</svg>
|
|
633
680
|
</button>
|
|
634
|
-
</
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
681
|
+
</BarHint>
|
|
682
|
+
<button
|
|
683
|
+
type="button"
|
|
684
|
+
class="group-action"
|
|
685
|
+
aria-label="Group menu"
|
|
686
|
+
aria-expanded={groupMenuOpen}
|
|
687
|
+
onClick={(e) => {
|
|
688
|
+
e.stopPropagation();
|
|
689
|
+
setGroupMenuOpen((open) => !open);
|
|
690
|
+
}}
|
|
691
|
+
>
|
|
692
|
+
<IconMore size={14} />
|
|
693
|
+
</button>
|
|
694
|
+
{groupMenuOpen && (
|
|
695
|
+
<div class="group-menu" role="menu" onPointerDown={(e) => e.stopPropagation()}>
|
|
696
|
+
<button
|
|
697
|
+
type="button"
|
|
698
|
+
role="menuitem"
|
|
699
|
+
onClick={(e) => {
|
|
700
|
+
e.stopPropagation();
|
|
701
|
+
setGroupMenuOpen(false);
|
|
702
|
+
setRenaming(true);
|
|
703
|
+
requestAnimationFrame(() => renameRef.current?.focus());
|
|
704
|
+
}}
|
|
705
|
+
>
|
|
706
|
+
Rename
|
|
707
|
+
</button>
|
|
708
|
+
<button
|
|
709
|
+
type="button"
|
|
710
|
+
role="menuitem"
|
|
711
|
+
onClick={(e) => {
|
|
712
|
+
e.stopPropagation();
|
|
713
|
+
setGroupMenuOpen(false);
|
|
714
|
+
// Dissolve — the same op an agent's ungroup runs.
|
|
715
|
+
removeNode(node.id);
|
|
716
|
+
void ungroupFromClient(node.id);
|
|
717
|
+
}}
|
|
718
|
+
>
|
|
719
|
+
Ungroup
|
|
720
|
+
</button>
|
|
721
|
+
<button
|
|
722
|
+
type="button"
|
|
723
|
+
role="menuitem"
|
|
724
|
+
disabled={groupChildren.length === 0}
|
|
725
|
+
onClick={(e) => {
|
|
726
|
+
e.stopPropagation();
|
|
727
|
+
setGroupMenuOpen(false);
|
|
728
|
+
addContextPins(groupChildren);
|
|
729
|
+
}}
|
|
730
|
+
>
|
|
731
|
+
Pin all to context
|
|
732
|
+
</button>
|
|
733
|
+
</div>
|
|
734
|
+
)}
|
|
735
|
+
</span>
|
|
736
|
+
</div>
|
|
737
|
+
)}
|
|
738
|
+
{!isGroup && (
|
|
739
|
+
<div class="node-titlebar" onPointerDown={handleTitlePointerDown}>
|
|
740
|
+
<NodeTypeIcon node={node} pinned={isContextPinned} title={title} fold={chipFold}>
|
|
741
|
+
{(() => {
|
|
742
|
+
const NodeIcon = getNodeIcon(node.type);
|
|
743
|
+
return <NodeIcon size={Math.round(14 * chromeScale)} />;
|
|
744
|
+
})()}
|
|
745
|
+
</NodeTypeIcon>
|
|
746
|
+
{/* The kind-colored icon says what type this is (rail-chrome-v2 card
|
|
648
747
|
shell: icon · title · controls, no type badge). Only the AX status
|
|
649
748
|
chip joins it, and only when there is a status to show. */}
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
</span>
|
|
669
|
-
)}
|
|
670
|
-
<NearPinMark node={node} pinned={isContextPinned} />
|
|
671
|
-
<NodeContextMark node={node} pinned={isContextPinned} />
|
|
672
|
-
<div class="node-controls">
|
|
673
|
-
{isPinned && (
|
|
674
|
-
<span class="pin-indicator" title="Pinned">
|
|
675
|
-
⊙
|
|
749
|
+
{typeof node.data.axWorkStatus === 'string' && (
|
|
750
|
+
<span class={`node-ax-status node-ax-status-${node.data.axWorkStatus}`}>{node.data.axWorkStatus}</span>
|
|
751
|
+
)}
|
|
752
|
+
{renaming ? (
|
|
753
|
+
<input
|
|
754
|
+
ref={renameRef}
|
|
755
|
+
class="node-title-input"
|
|
756
|
+
value={title}
|
|
757
|
+
onBlur={(e) => commitRename((e.target as HTMLInputElement).value)}
|
|
758
|
+
onKeyDown={(e) => {
|
|
759
|
+
if (e.key === 'Enter') commitRename((e.target as HTMLInputElement).value);
|
|
760
|
+
if (e.key === 'Escape') setRenaming(false);
|
|
761
|
+
}}
|
|
762
|
+
onClick={(e) => e.stopPropagation()}
|
|
763
|
+
/>
|
|
764
|
+
) : (
|
|
765
|
+
<span ref={titleRef} class="node-title">
|
|
766
|
+
{title}
|
|
676
767
|
</span>
|
|
677
768
|
)}
|
|
678
|
-
{
|
|
769
|
+
<NearPinMark node={node} pinned={isContextPinned} fold={chipFold} />
|
|
770
|
+
<NodeContextMark node={node} pinned={isContextPinned} fold={chipFold} />
|
|
771
|
+
<div class="node-controls">
|
|
772
|
+
{isPinned && (
|
|
773
|
+
<BarHint label="Pinned in place" align="end" fitWithin=".node-content" tapToOpen>
|
|
774
|
+
<span class="pin-indicator">⊙</span>
|
|
775
|
+
</BarHint>
|
|
776
|
+
)}
|
|
777
|
+
{/* Expand and the context pin always show; the rest sit behind ⋯ and ×
|
|
679
778
|
on hover (docs/design/Chrome.dc.html). */}
|
|
680
|
-
|
|
779
|
+
{EXPANDABLE_TYPES.has(node.type) && (
|
|
780
|
+
<BarHint label="Expand (focus mode)" align="end" fitWithin=".node-content">
|
|
781
|
+
<button
|
|
782
|
+
type="button"
|
|
783
|
+
onClick={(e) => {
|
|
784
|
+
e.stopPropagation();
|
|
785
|
+
expandNode(node.id);
|
|
786
|
+
}}
|
|
787
|
+
aria-label="Expand (focus mode)"
|
|
788
|
+
>
|
|
789
|
+
<IconExpand size={14} />
|
|
790
|
+
</button>
|
|
791
|
+
</BarHint>
|
|
792
|
+
)}
|
|
793
|
+
{node.type === 'board' ? (
|
|
794
|
+
// A portal's one pin is its board's pin: the linked board joins the
|
|
795
|
+
// agent's working set (BoardPins.dc.html), not this card.
|
|
796
|
+
portalTarget && <BoardPinButton board={portalTarget} inNodeHeader />
|
|
797
|
+
) : (
|
|
798
|
+
<BarHint
|
|
799
|
+
label={isContextPinned ? 'Remove from context' : 'Add to context'}
|
|
800
|
+
align="end"
|
|
801
|
+
fitWithin=".node-content"
|
|
802
|
+
>
|
|
803
|
+
<button
|
|
804
|
+
type="button"
|
|
805
|
+
class={`ctx-pin-btn${isContextPinned ? ' ctx-pin-active' : ''}`}
|
|
806
|
+
onClick={(e) => {
|
|
807
|
+
e.stopPropagation();
|
|
808
|
+
toggleContextPin(node.id);
|
|
809
|
+
}}
|
|
810
|
+
aria-label={isContextPinned ? 'Remove from context' : 'Add to context'}
|
|
811
|
+
aria-pressed={isContextPinned}
|
|
812
|
+
>
|
|
813
|
+
<span class="ctx-pin-mark">
|
|
814
|
+
<IconPin />
|
|
815
|
+
{isContextPinned && isAgentPin(node.id) && (
|
|
816
|
+
<span class="ctx-pin-agent-dot" aria-hidden="true" />
|
|
817
|
+
)}
|
|
818
|
+
</span>
|
|
819
|
+
</button>
|
|
820
|
+
</BarHint>
|
|
821
|
+
)}
|
|
681
822
|
<button
|
|
682
823
|
type="button"
|
|
824
|
+
class="node-hover-control"
|
|
683
825
|
onClick={(e) => {
|
|
684
826
|
e.stopPropagation();
|
|
685
|
-
|
|
827
|
+
onContextMenu?.(e, node.id);
|
|
686
828
|
}}
|
|
687
|
-
|
|
829
|
+
aria-label="More actions"
|
|
688
830
|
>
|
|
689
|
-
<
|
|
831
|
+
<IconMore size={14} />
|
|
690
832
|
</button>
|
|
691
|
-
|
|
692
|
-
<button
|
|
693
|
-
type="button"
|
|
694
|
-
class={`ctx-pin-btn${isContextPinned ? ' ctx-pin-active' : ''}`}
|
|
695
|
-
onClick={(e) => {
|
|
696
|
-
e.stopPropagation();
|
|
697
|
-
toggleContextPin(node.id);
|
|
698
|
-
}}
|
|
699
|
-
title={isContextPinned ? 'Remove from context' : 'Add to context'}
|
|
700
|
-
aria-label={isContextPinned ? 'Remove from context' : 'Add to context'}
|
|
701
|
-
aria-pressed={isContextPinned}
|
|
702
|
-
>
|
|
703
|
-
<span class="ctx-pin-mark">
|
|
704
|
-
<IconPin />
|
|
705
|
-
{isContextPinned && isAgentPin(node.id) && <span class="ctx-pin-agent-dot" aria-hidden="true" />}
|
|
706
|
-
</span>
|
|
707
|
-
</button>
|
|
708
|
-
<button
|
|
709
|
-
type="button"
|
|
710
|
-
class="node-hover-control"
|
|
711
|
-
onClick={(e) => {
|
|
712
|
-
e.stopPropagation();
|
|
713
|
-
onContextMenu?.(e, node.id);
|
|
714
|
-
}}
|
|
715
|
-
title="More actions"
|
|
716
|
-
aria-label="More actions"
|
|
717
|
-
>
|
|
718
|
-
<IconMore size={14} />
|
|
719
|
-
</button>
|
|
720
|
-
{/* Report #64: status nodes get the same remove control as every other
|
|
833
|
+
{/* Report #64: status nodes get the same remove control as every other
|
|
721
834
|
node type (backend removal + undo/history handle status uniformly). */}
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
835
|
+
<button
|
|
836
|
+
type="button"
|
|
837
|
+
class="node-hover-control"
|
|
838
|
+
onClick={(e) => {
|
|
839
|
+
e.stopPropagation();
|
|
840
|
+
void removeNodeFromClient(node.id);
|
|
841
|
+
}}
|
|
842
|
+
aria-label="Close"
|
|
843
|
+
>
|
|
844
|
+
<IconClose size={14} />
|
|
845
|
+
</button>
|
|
846
|
+
</div>
|
|
733
847
|
</div>
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
{/* AX flow step controls — rendered here rather than in a node renderer
|
|
848
|
+
)}
|
|
849
|
+
{!node.collapsed && (
|
|
850
|
+
<div ref={bodyRef} class="node-body">
|
|
851
|
+
{children}
|
|
852
|
+
{/* AX flow step controls — rendered here rather than in a node renderer
|
|
740
853
|
because a materialized step can be ANY node type, and because the
|
|
741
854
|
body is what auto-fit measures (a footer outside it would be clipped
|
|
742
855
|
on a content-fitted node). Renders nothing unless the node carries a
|
|
743
856
|
`data.axStep` stamp. */}
|
|
744
|
-
|
|
745
|
-
|
|
857
|
+
<AxStepControls node={node} />
|
|
858
|
+
{!isGroup && <LifecycleFooter nodeId={node.id} />}
|
|
859
|
+
<CrossBoardReferences node={node} />
|
|
860
|
+
</div>
|
|
861
|
+
)}
|
|
862
|
+
{/* Group children live in the world layer, not this body. Keep their
|
|
863
|
+
warning in the frame's bottom chrome, out of the content flex rule. */}
|
|
864
|
+
{(node.collapsed || isGroup) && <LifecycleFooter nodeId={node.id} />}
|
|
865
|
+
</div>
|
|
866
|
+
{!node.collapsed && (
|
|
867
|
+
<div class="node-resize-handle" onPointerDown={(e) => startResize(e, node.size.width, node.size.height)} />
|
|
868
|
+
)}
|
|
869
|
+
{isSelected &&
|
|
870
|
+
(['tl', 'tr', 'bl', 'br'] as const).map((corner) => (
|
|
871
|
+
<span key={corner} class={`node-selection-handle is-${corner}`} aria-hidden="true" />
|
|
872
|
+
))}
|
|
873
|
+
{yieldedNodes.value.has(node.id) && (
|
|
874
|
+
<span class="node-yield-pill" data-testid="yield-pill">
|
|
875
|
+
<span class="node-yield-dot" aria-hidden="true" />
|
|
876
|
+
{yieldedNodes.value.get(node.id)} took over — agent yielded
|
|
877
|
+
</span>
|
|
746
878
|
)}
|
|
879
|
+
{!isGroup &&
|
|
880
|
+
(['top', 'right', 'bottom', 'left'] as const).map((side) => (
|
|
881
|
+
<span key={side} class={`node-port-dot node-port-dot-${side}`} aria-hidden="true" />
|
|
882
|
+
))}
|
|
747
883
|
</div>
|
|
748
|
-
{!node.collapsed && (
|
|
749
|
-
<div class="node-resize-handle" onPointerDown={(e) => startResize(e, node.size.width, node.size.height)} />
|
|
750
|
-
)}
|
|
751
|
-
{isSelected &&
|
|
752
|
-
(['tl', 'tr', 'bl', 'br'] as const).map((corner) => (
|
|
753
|
-
<span key={corner} class={`node-selection-handle is-${corner}`} aria-hidden="true" />
|
|
754
|
-
))}
|
|
755
|
-
{yieldedNodes.value.has(node.id) && (
|
|
756
|
-
<span class="node-yield-pill" data-testid="yield-pill">
|
|
757
|
-
<span class="node-yield-dot" aria-hidden="true" />
|
|
758
|
-
{yieldedNodes.value.get(node.id)} took over — agent yielded
|
|
759
|
-
</span>
|
|
760
|
-
)}
|
|
761
884
|
{/* Connection port handles — visible on hover, drag to connect. Groups
|
|
762
885
|
are frames, not endpoints: no ports (and the edge row sits on the top
|
|
763
886
|
edge where the port would be). */}
|