pmx-canvas 0.7.2 → 0.8.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/THIRD_PARTY_LICENSES.txt +331 -0
- package/.github/extensions/pmx-canvas/extension.mjs +25 -172
- package/.github/extensions/pmx-canvas/mcp-bootstrap.ts +198 -0
- package/.github/extensions/pmx-canvas/runtime.d.mts +41 -0
- package/.github/extensions/pmx-canvas/runtime.json +3 -0
- package/.github/extensions/pmx-canvas/runtime.mjs +194 -0
- package/.github/extensions/pmx-canvas/start-mcp.mjs +51 -0
- package/.github/extensions/pmx-canvas/tools.json +2560 -0
- package/CHANGELOG.md +60 -0
- package/Readme.md +44 -16
- package/dist/canvas/fonts/OFL.txt +93 -0
- package/dist/canvas/fonts/ibm-plex-mono-latin-400-normal.woff2 +0 -0
- package/dist/canvas/fonts/ibm-plex-mono-latin-500-normal.woff2 +0 -0
- package/dist/canvas/fonts/ibm-plex-mono-latin-ext-400-normal.woff2 +0 -0
- package/dist/canvas/fonts/ibm-plex-mono-latin-ext-500-normal.woff2 +0 -0
- package/dist/canvas/fonts/ibm-plex-sans-latin-400-normal.woff2 +0 -0
- package/dist/canvas/fonts/ibm-plex-sans-latin-500-normal.woff2 +0 -0
- package/dist/canvas/fonts/ibm-plex-sans-latin-600-normal.woff2 +0 -0
- package/dist/canvas/fonts/ibm-plex-sans-latin-ext-400-normal.woff2 +0 -0
- package/dist/canvas/fonts/ibm-plex-sans-latin-ext-500-normal.woff2 +0 -0
- package/dist/canvas/fonts/ibm-plex-sans-latin-ext-600-normal.woff2 +0 -0
- package/dist/canvas/global.css +844 -710
- package/dist/canvas/index.js +92 -90
- package/dist/canvas/mcp-app.html +7878 -0
- package/dist/canvas/mermaid-entry.js +1 -1
- package/dist/canvas/surface-theme.css +151 -46
- package/dist/json-render/index.css +1 -1
- package/dist/json-render/index.js +55 -55
- package/dist/types/cli/commands/record.d.ts +1 -1
- package/dist/types/client/canvas/ChoiceMenu.d.ts +13 -0
- package/dist/types/client/canvas/NodeContextMark.d.ts +24 -0
- package/dist/types/client/canvas/SessionReceipt.d.ts +3 -1
- package/dist/types/client/icons.d.ts +1 -2
- package/dist/types/client/mcp-app/index.d.ts +1 -0
- package/dist/types/client/nodes/McpAppNode.d.ts +2 -1
- package/dist/types/client/nodes/ax-node-actions.d.ts +2 -2
- package/dist/types/client/nodes/markdown-sanitize.d.ts +2 -0
- package/dist/types/client/nodes/use-workbench-asset.d.ts +2 -0
- package/dist/types/client/nodes/viewer-runtime-state.d.ts +1 -0
- package/dist/types/client/state/attention-store.d.ts +0 -5
- package/dist/types/client/state/canvas-store.d.ts +2 -0
- package/dist/types/client/state/context-status-store.d.ts +53 -0
- package/dist/types/client/state/intent-bridge.d.ts +4 -1
- package/dist/types/client/state/near-pin-store.d.ts +16 -0
- package/dist/types/client/state/presence-store.d.ts +5 -0
- package/dist/types/client/state/session-store.d.ts +33 -3
- package/dist/types/client/state/workbench-transport.d.ts +16 -0
- package/dist/types/client/theme/tokens.d.ts +1 -3
- package/dist/types/client/types.d.ts +9 -0
- package/dist/types/json-render/renderer/index.d.ts +2 -1
- package/dist/types/json-render/server.d.ts +3 -1
- package/dist/types/mcp/app-server.d.ts +3 -0
- package/dist/types/mcp/canvas-access.d.ts +1 -1
- package/dist/types/mcp/canvas-app.d.ts +4 -0
- package/dist/types/mcp/workbench-app.d.ts +4 -0
- package/dist/types/server/agent-presence.d.ts +1 -1
- package/dist/types/server/ax-state.d.ts +1 -1
- package/dist/types/server/canvas-db.d.ts +8 -0
- package/dist/types/server/canvas-geometry.d.ts +25 -0
- package/dist/types/server/canvas-operations.d.ts +1 -11
- package/dist/types/server/canvas-state.d.ts +25 -3
- package/dist/types/server/context-brief.d.ts +8 -1
- package/dist/types/server/context-reads.d.ts +30 -7
- package/dist/types/server/index.d.ts +6 -1
- package/dist/types/server/mcp-app-runtime.d.ts +1 -0
- package/dist/types/server/operations/http.d.ts +2 -0
- package/dist/types/server/operations/invoker.d.ts +4 -2
- package/dist/types/server/operations/ops/nodes.d.ts +0 -6
- package/dist/types/server/server.d.ts +1 -0
- package/dist/types/server/spatial-analysis.d.ts +2 -0
- package/dist/types/shared/agent-context.d.ts +22 -0
- package/dist/types/shared/agent-presence.d.ts +3 -10
- package/dist/types/shared/ax-surface-protocol.d.ts +2 -0
- package/dist/types/shared/canvas-app.d.ts +54 -0
- package/dist/types/shared/themes.d.ts +1 -1
- package/docs/RELEASE.md +7 -3
- package/docs/agent-plugin.md +123 -0
- package/docs/ax-host-adapter-contract.md +13 -2
- package/docs/ax-state-contract.md +40 -0
- package/docs/cli.md +7 -2
- package/docs/design/AgentContext.dc.html +186 -0
- package/docs/design/Before.dc.html +208 -0
- package/docs/design/BoardPins.dc.html +151 -0
- package/docs/design/Chrome.dc.html +208 -0
- package/docs/design/Context.dc.html +239 -0
- package/docs/design/Flows.dc.html +181 -0
- package/docs/design/Home.dc.html +209 -0
- package/docs/design/HomeGraph.dc.html +211 -0
- package/docs/design/HomeMap.dc.html +117 -0
- package/docs/design/HomeTeam.dc.html +176 -0
- package/docs/design/Light.dc.html +28 -0
- package/docs/design/Main.dc.html +280 -0
- package/docs/design/NearPin.dc.html +260 -0
- package/docs/design/Pane600.dc.html +120 -0
- package/docs/design/Relations.dc.html +297 -0
- package/docs/design/Themes.dc.html +121 -0
- package/docs/design/TokenSystem.dc.html +136 -0
- package/docs/design/Tokens.dc.html +163 -0
- package/docs/design/Types1.dc.html +177 -0
- package/docs/design/Types2.dc.html +226 -0
- package/docs/design/Types3.dc.html +195 -0
- package/docs/design/Uses.dc.html +152 -0
- package/docs/design/Vision.dc.html +116 -0
- package/docs/design/Zoom.dc.html +82 -0
- package/docs/design/canvas.json +274 -0
- package/docs/design.md +214 -0
- package/docs/environment.md +1 -1
- package/docs/http-api.md +80 -15
- package/docs/mcp.md +198 -3
- package/docs/node-types.md +34 -10
- package/docs/plans/plan-009-tech-debt-backlog.md +1 -1
- package/docs/plans/plan-015-design-foundation-wave-0.md +177 -0
- package/docs/plans/plan-016-context-made-visible-wave-1.md +148 -0
- package/docs/product-vision-2026-09.md +47 -7
- package/docs/screenshot.png +0 -0
- package/docs/sdk.md +1 -0
- package/package.json +10 -4
- package/plugins/pmx-canvas/LICENSE +21 -0
- package/plugins/pmx-canvas/THIRD_PARTY_LICENSES.txt +331 -0
- package/plugins/pmx-canvas/assets/preview.png +0 -0
- package/plugins/pmx-canvas/com.github.copilot/extensions/pmx-canvas/extension.mjs +930 -0
- package/plugins/pmx-canvas/com.github.copilot/extensions/pmx-canvas/runtime.json +3 -0
- package/plugins/pmx-canvas/com.github.copilot/extensions/pmx-canvas/runtime.mjs +194 -0
- package/plugins/pmx-canvas/com.github.copilot/extensions/pmx-canvas/steering-delivery.mjs +79 -0
- package/plugins/pmx-canvas/mcp.json +12 -0
- package/plugins/pmx-canvas/plugin.json +24 -0
- package/plugins/pmx-canvas/runtime.json +3 -0
- package/plugins/pmx-canvas/runtime.mjs +194 -0
- package/plugins/pmx-canvas/start-mcp.mjs +51 -0
- package/plugins/pmx-canvas/tools.json +2560 -0
- package/skills/pmx-canvas/SKILL.md +31 -16
- package/skills/pmx-canvas/evals/evals.json +69 -0
- package/skills/pmx-canvas/references/full-reference.md +14 -10
- package/skills/pmx-canvas/references/github-copilot-app-adapter.md +20 -8
- package/skills/pmx-canvas/references/installing-pmx-canvas.md +76 -4
- package/skills/pmx-canvas-testing/SKILL.md +54 -4
- package/src/cli/commands/copilot.ts +5 -0
- package/src/cli/commands/view.ts +1 -1
- package/src/cli/index.ts +8 -3
- package/src/client/App.tsx +6 -1
- package/src/client/canvas/AgentPresenceLayer.tsx +3 -3
- package/src/client/canvas/AnnotationLayer.tsx +1 -1
- package/src/client/canvas/CanvasNode.tsx +208 -220
- package/src/client/canvas/CanvasViewport.tsx +6 -2
- package/src/client/canvas/ChoiceMenu.tsx +122 -0
- package/src/client/canvas/CommandBar.tsx +25 -23
- package/src/client/canvas/CommandPalette.tsx +3 -2
- package/src/client/canvas/ContextMenu.tsx +11 -0
- package/src/client/canvas/EdgeLayer.tsx +1 -1
- package/src/client/canvas/ExportDialog.tsx +33 -6
- package/src/client/canvas/FeedbackDialog.tsx +10 -8
- package/src/client/canvas/IntentLayer.tsx +2 -2
- package/src/client/canvas/Minimap.tsx +12 -7
- package/src/client/canvas/NodeContextMark.tsx +156 -0
- package/src/client/canvas/SessionPanel.tsx +4 -7
- package/src/client/canvas/SessionReceipt.tsx +114 -17
- package/src/client/canvas/ShortcutOverlay.tsx +6 -1
- package/src/client/canvas/SnapshotPanel.tsx +11 -2
- package/src/client/canvas/ToolRail.tsx +58 -108
- package/src/client/canvas/TopBar.tsx +16 -11
- package/src/client/icons.tsx +2 -12
- package/src/client/mcp-app/index.tsx +137 -0
- package/src/client/mcp-app/style.css +10 -0
- package/src/client/nodes/AttachmentNode.tsx +8 -1
- package/src/client/nodes/AxStepControls.tsx +1 -1
- package/src/client/nodes/ContextNode.tsx +9 -9
- package/src/client/nodes/ExtAppFrame.tsx +13 -8
- package/src/client/nodes/FileNode.tsx +8 -5
- package/src/client/nodes/ImageNode.tsx +4 -2
- package/src/client/nodes/InlineMarkdownEditor.tsx +2 -1
- package/src/client/nodes/LedgerNode.tsx +2 -2
- package/src/client/nodes/MarkdownNode.tsx +44 -9
- package/src/client/nodes/McpAppNode.tsx +22 -4
- package/src/client/nodes/PromptNode.tsx +5 -17
- package/src/client/nodes/ResponseNode.tsx +3 -15
- package/src/client/nodes/StatusNode.tsx +2 -2
- package/src/client/nodes/StatusSummary.tsx +3 -1
- package/src/client/nodes/TraceNode.tsx +6 -4
- package/src/client/nodes/WebpageNode.tsx +1 -1
- package/src/client/nodes/ax-node-actions.ts +2 -2
- package/src/client/nodes/iframe-document-url.ts +3 -2
- package/src/client/nodes/markdown-sanitize.ts +10 -0
- package/src/client/nodes/surface-url.ts +10 -0
- package/src/client/nodes/use-ax-surface-bridge.ts +4 -2
- package/src/client/nodes/use-surface-frame.ts +3 -1
- package/src/client/nodes/use-workbench-asset.ts +26 -0
- package/src/client/nodes/viewer-runtime-state.ts +78 -0
- package/src/client/state/attention-bridge.ts +0 -8
- package/src/client/state/attention-store.ts +0 -17
- package/src/client/state/canvas-store.ts +7 -2
- package/src/client/state/context-status-store.ts +66 -0
- package/src/client/state/human-store.ts +2 -0
- package/src/client/state/iframe-mode.ts +5 -0
- package/src/client/state/intent-bridge.ts +24 -6
- package/src/client/state/near-pin-store.ts +53 -0
- package/src/client/state/presence-store.ts +5 -10
- package/src/client/state/session-store.ts +106 -9
- package/src/client/state/sse-bridge.ts +26 -7
- package/src/client/state/theme-override.ts +1 -1
- package/src/client/state/workbench-transport.ts +36 -0
- package/src/client/theme/global.css +844 -710
- package/src/client/theme/surface-theme.css +151 -46
- package/src/client/theme/tokens.ts +7 -11
- package/src/client/types.ts +10 -0
- package/src/json-render/renderer/index.tsx +58 -38
- package/src/json-render/server.ts +79 -15
- package/src/mcp/app-server.ts +64 -0
- package/src/mcp/canvas-access.ts +14 -11
- package/src/mcp/canvas-app.ts +318 -0
- package/src/mcp/server.ts +1 -1
- package/src/mcp/workbench-app.ts +211 -0
- package/src/server/agent-presence.ts +45 -6
- package/src/server/ax-context.ts +1 -1
- package/src/server/ax-state.ts +1 -1
- package/src/server/board-export.ts +5 -2
- package/src/server/canvas-db.ts +46 -12
- package/src/server/canvas-geometry.ts +64 -0
- package/src/server/canvas-operations.ts +6 -24
- package/src/server/canvas-state.ts +105 -15
- package/src/server/canvas-validation.ts +19 -4
- package/src/server/context-brief.ts +39 -2
- package/src/server/context-reads.ts +86 -15
- package/src/server/demo-state.json +31 -31
- package/src/server/html-surface.ts +4 -5
- package/src/server/index.ts +19 -3
- package/src/server/mcp-app-runtime.ts +11 -0
- package/src/server/operations/composites.ts +4 -3
- package/src/server/operations/http.ts +4 -4
- package/src/server/operations/invoker.ts +13 -4
- package/src/server/operations/ops/ax-read.ts +1 -1
- package/src/server/operations/ops/ax-timeline.ts +37 -0
- package/src/server/operations/ops/boards.ts +1 -1
- package/src/server/operations/ops/canvas-wire.ts +3 -6
- package/src/server/operations/ops/intent.ts +31 -2
- package/src/server/operations/ops/json-render.ts +1 -1
- package/src/server/operations/ops/nodes.ts +8 -76
- package/src/server/operations/ops/query.ts +8 -2
- package/src/server/operations/ops/snapshots.ts +4 -1
- package/src/server/operations/ops/viewport.ts +29 -5
- package/src/server/operations/registry.ts +38 -4
- package/src/server/scope-fence.ts +55 -10
- package/src/server/server.ts +86 -6
- package/src/server/spatial-analysis.ts +2 -2
- package/src/server/workboard.ts +6 -1
- package/src/{server → shared}/agent-context.ts +2 -2
- package/src/shared/agent-presence.ts +3 -15
- package/src/shared/ax-surface-protocol.ts +3 -0
- package/src/shared/canvas-app.ts +62 -0
- package/src/shared/themes.ts +5 -5
- package/src/client/canvas/FocusFieldLayer.tsx +0 -116
- package/src/client/canvas/kind-colors.ts +0 -23
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { useLayoutEffect, useRef, useState } from 'preact/hooks';
|
|
2
|
+
import { IconChevronDown } from '../icons';
|
|
3
|
+
|
|
4
|
+
/** Themed single-choice menu shared by the feedback form and steering composer. */
|
|
5
|
+
export function ChoiceMenu({
|
|
6
|
+
label,
|
|
7
|
+
value,
|
|
8
|
+
options,
|
|
9
|
+
onChange,
|
|
10
|
+
className = '',
|
|
11
|
+
above = false,
|
|
12
|
+
}: {
|
|
13
|
+
label: string;
|
|
14
|
+
value: string;
|
|
15
|
+
options: Array<{ value: string; label: string; disabled?: boolean }>;
|
|
16
|
+
onChange: (value: string) => void;
|
|
17
|
+
className?: string;
|
|
18
|
+
above?: boolean;
|
|
19
|
+
}) {
|
|
20
|
+
const [open, setOpen] = useState(false);
|
|
21
|
+
const root = useRef<HTMLDivElement>(null);
|
|
22
|
+
const trigger = useRef<HTMLButtonElement>(null);
|
|
23
|
+
const menu = useRef<HTMLDivElement>(null);
|
|
24
|
+
const close = () => {
|
|
25
|
+
setOpen(false);
|
|
26
|
+
trigger.current?.focus();
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
useLayoutEffect(() => {
|
|
30
|
+
if (!open) return;
|
|
31
|
+
const selected = menu.current?.querySelector<HTMLButtonElement>('[aria-checked="true"]:not(:disabled)');
|
|
32
|
+
(selected ?? menu.current?.querySelector<HTMLButtonElement>('button:not(:disabled)'))?.focus();
|
|
33
|
+
const outside = (event: PointerEvent) => {
|
|
34
|
+
if (event.target instanceof Node && !root.current?.contains(event.target)) setOpen(false);
|
|
35
|
+
};
|
|
36
|
+
document.addEventListener('pointerdown', outside);
|
|
37
|
+
return () => document.removeEventListener('pointerdown', outside);
|
|
38
|
+
}, [open]);
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
<div
|
|
42
|
+
ref={root}
|
|
43
|
+
class={`choice-menu ${className}`}
|
|
44
|
+
onBlur={(e) => {
|
|
45
|
+
if (e.relatedTarget instanceof Node && !root.current?.contains(e.relatedTarget)) setOpen(false);
|
|
46
|
+
}}
|
|
47
|
+
onKeyDown={(e) => {
|
|
48
|
+
// Menu typing/navigation must not trigger canvas tool shortcuts.
|
|
49
|
+
// Tab still reaches modal focus traps; closed Escape reaches the dialog.
|
|
50
|
+
if (e.key !== 'Tab' && (e.key !== 'Escape' || open)) e.stopPropagation();
|
|
51
|
+
if (e.key === 'Escape' && open) {
|
|
52
|
+
e.preventDefault();
|
|
53
|
+
e.stopPropagation();
|
|
54
|
+
close();
|
|
55
|
+
}
|
|
56
|
+
if (['ArrowDown', 'ArrowUp', 'Home', 'End'].includes(e.key)) {
|
|
57
|
+
e.preventDefault();
|
|
58
|
+
e.stopPropagation();
|
|
59
|
+
if (!open) {
|
|
60
|
+
setOpen(true);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
const items = [...(menu.current?.querySelectorAll<HTMLButtonElement>('button:not(:disabled)') ?? [])];
|
|
64
|
+
if (!items.length) return;
|
|
65
|
+
const index = items.findIndex((item) => item === document.activeElement);
|
|
66
|
+
const next =
|
|
67
|
+
e.key === 'Home'
|
|
68
|
+
? 0
|
|
69
|
+
: e.key === 'End'
|
|
70
|
+
? items.length - 1
|
|
71
|
+
: (index + (e.key === 'ArrowUp' ? -1 : 1) + items.length) % items.length;
|
|
72
|
+
items[next]?.focus();
|
|
73
|
+
}
|
|
74
|
+
}}
|
|
75
|
+
>
|
|
76
|
+
<button
|
|
77
|
+
ref={trigger}
|
|
78
|
+
type="button"
|
|
79
|
+
class="choice-menu-trigger"
|
|
80
|
+
aria-label={label}
|
|
81
|
+
aria-haspopup="menu"
|
|
82
|
+
aria-expanded={open}
|
|
83
|
+
onClick={() => setOpen(!open)}
|
|
84
|
+
>
|
|
85
|
+
<span>{options.find((option) => option.value === value)?.label ?? label}</span>
|
|
86
|
+
<IconChevronDown size={14} />
|
|
87
|
+
</button>
|
|
88
|
+
{open && (
|
|
89
|
+
<div
|
|
90
|
+
ref={menu}
|
|
91
|
+
role="menu"
|
|
92
|
+
aria-label={label}
|
|
93
|
+
class={`toolbar-menu choice-menu-options${above ? ' opens-above' : ''}`}
|
|
94
|
+
>
|
|
95
|
+
{options.map((option) => (
|
|
96
|
+
<button
|
|
97
|
+
key={option.value}
|
|
98
|
+
type="button"
|
|
99
|
+
role="menuitemradio"
|
|
100
|
+
class={`toolbar-menu-item${option.value === value ? ' active' : ''}`}
|
|
101
|
+
aria-checked={option.value === value}
|
|
102
|
+
disabled={option.disabled}
|
|
103
|
+
tabIndex={-1}
|
|
104
|
+
data-value={option.value}
|
|
105
|
+
onClick={() => {
|
|
106
|
+
onChange(option.value);
|
|
107
|
+
close();
|
|
108
|
+
}}
|
|
109
|
+
>
|
|
110
|
+
<span>{option.label}</span>
|
|
111
|
+
{option.value === value && (
|
|
112
|
+
<span class="toolbar-menu-check" aria-hidden="true">
|
|
113
|
+
✓
|
|
114
|
+
</span>
|
|
115
|
+
)}
|
|
116
|
+
</button>
|
|
117
|
+
))}
|
|
118
|
+
</div>
|
|
119
|
+
)}
|
|
120
|
+
</div>
|
|
121
|
+
);
|
|
122
|
+
}
|
|
@@ -4,6 +4,7 @@ import { contextPinnedNodeIds, nodes, toggleContextPin } from '../state/canvas-s
|
|
|
4
4
|
import { steerableAgents } from '../state/presence-store';
|
|
5
5
|
import { sendSteering } from '../state/session-store';
|
|
6
6
|
import { modChord } from '../utils/platform';
|
|
7
|
+
import { ChoiceMenu } from './ChoiceMenu';
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* Command bar (rail-chrome-v2 phase 5): the human's steering surface while a
|
|
@@ -83,33 +84,34 @@ export function CommandBar() {
|
|
|
83
84
|
<div class="command-bar-composer">
|
|
84
85
|
<IconSteer size={15} class="command-bar-icon" />
|
|
85
86
|
{showPicker && (
|
|
86
|
-
<
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
title="Connected agents — steering reaches all of them unless you address one"
|
|
87
|
+
<ChoiceMenu
|
|
88
|
+
className="command-bar-target"
|
|
89
|
+
label="Steer which agent"
|
|
90
90
|
value={effectiveTarget ?? ''}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
91
|
+
above
|
|
92
|
+
onChange={(value) => setTarget(value || null)}
|
|
93
|
+
options={[
|
|
94
|
+
{ value: '', label: 'All agents' },
|
|
95
|
+
...agents.map((agent) => {
|
|
96
|
+
// Pump health at the point of choice: unclaimed steers already
|
|
97
|
+
// sitting in this agent's queue (a growing number means its loop
|
|
98
|
+
// is not polling), and proof-of-polling when its consumer key
|
|
99
|
+
// claimed recently — attached alone doesn't mean anyone reads
|
|
100
|
+
// the inbox.
|
|
101
|
+
const queued = agent.pendingSteers && agent.pendingSteers > 0 ? ` · ${agent.pendingSteers} queued` : '';
|
|
102
|
+
const polling = agent.polling ? ' · polling' : '';
|
|
103
|
+
return {
|
|
104
|
+
value: agent.value,
|
|
105
|
+
disabled: !agent.steerable,
|
|
106
|
+
label: agent.attached
|
|
105
107
|
? `${agent.label}${polling}${queued}`
|
|
106
108
|
: agent.steerable
|
|
107
109
|
? `${agent.label} · writer${polling}${queued}`
|
|
108
|
-
: `${agent.label} · no inbox
|
|
109
|
-
|
|
110
|
-
)
|
|
111
|
-
}
|
|
112
|
-
|
|
110
|
+
: `${agent.label} · no inbox`,
|
|
111
|
+
};
|
|
112
|
+
}),
|
|
113
|
+
]}
|
|
114
|
+
/>
|
|
113
115
|
)}
|
|
114
116
|
<input
|
|
115
117
|
class="command-bar-input"
|
|
@@ -16,6 +16,7 @@ import { startSession } from '../state/session-store';
|
|
|
16
16
|
import { TYPE_LABELS, type CanvasNodeState } from '../types';
|
|
17
17
|
import { invalidateTokenCache } from '../theme/tokens';
|
|
18
18
|
import { clearThemeOverride } from '../state/theme-override';
|
|
19
|
+
import { canvasThemeScheme } from '../../shared/themes.js';
|
|
19
20
|
import { getNodeIcon, IconArrange, IconFitAll, IconMinimap, IconMoon, IconNodeMarkdown, IconSteer } from '../icons';
|
|
20
21
|
import { activeBoardId, openBoard } from '../state/boards-store';
|
|
21
22
|
import { requestJson } from '../state/intent-bridge';
|
|
@@ -269,11 +270,11 @@ export function CommandPalette({ onClose, onToggleMinimap }: { onClose: () => vo
|
|
|
269
270
|
},
|
|
270
271
|
},
|
|
271
272
|
{
|
|
272
|
-
label: 'Toggle theme (
|
|
273
|
+
label: 'Toggle theme (Harbor/Daylight)',
|
|
273
274
|
icon: IconMoon,
|
|
274
275
|
iconTone: 'muted',
|
|
275
276
|
action: () => {
|
|
276
|
-
const next = canvasTheme.value === 'dark' ? '
|
|
277
|
+
const next = canvasThemeScheme(canvasTheme.value) === 'dark' ? 'daylight' : 'harbor';
|
|
277
278
|
// An explicit pick ends any ?theme= session override, same as the
|
|
278
279
|
// toolbar picker — otherwise the session stays sticky-overridden.
|
|
279
280
|
clearThemeOverride();
|
|
@@ -26,6 +26,7 @@ import {
|
|
|
26
26
|
updateEdgeFromClient,
|
|
27
27
|
updateNodeFromClient,
|
|
28
28
|
} from '../state/intent-bridge';
|
|
29
|
+
import { canOpenAsSite, openNodeAsSite } from '../nodes/surface-url';
|
|
29
30
|
import { askText } from './TextPrompt';
|
|
30
31
|
import { EXPANDABLE_TYPES } from '../types';
|
|
31
32
|
import type { CanvasNodeState } from '../types';
|
|
@@ -498,6 +499,16 @@ function buildNodeMenuItems(node: CanvasNodeState): MenuItem[] {
|
|
|
498
499
|
action: () => toggleCollapsed(node.id),
|
|
499
500
|
});
|
|
500
501
|
|
|
502
|
+
// Open as site — full-page standalone view of this node's surface, served
|
|
503
|
+
// from /api/canvas/surface/:id (same document as the canvas iframe). Opens
|
|
504
|
+
// via the system browser so embedded hosts do not trap it in their webview.
|
|
505
|
+
if (canOpenAsSite(node)) {
|
|
506
|
+
items.push({
|
|
507
|
+
label: 'Open in new tab',
|
|
508
|
+
action: () => void openNodeAsSite(node),
|
|
509
|
+
});
|
|
510
|
+
}
|
|
511
|
+
|
|
501
512
|
// Context pin — add/remove from the human-curated agent context (report #63).
|
|
502
513
|
// This is the PRIMARY "pin" in PMX's model ("pin nodes to curate context"); it
|
|
503
514
|
// matches the SelectionBar's "Pin as context" and updates the context count + the
|
|
@@ -12,7 +12,7 @@ import type { CanvasEdge, CanvasNodeState } from '../types';
|
|
|
12
12
|
// ── Edge type visual styles ──────────────────────────────────
|
|
13
13
|
const EDGE_COLORS: Record<CanvasEdge['type'], string> = {
|
|
14
14
|
relation: 'var(--c-muted)',
|
|
15
|
-
'depends-on': 'var(--c-
|
|
15
|
+
'depends-on': 'var(--c-rel-informs)',
|
|
16
16
|
flow: 'var(--c-accent)',
|
|
17
17
|
// --c-dim is a very low-contrast hairline in dark palettes (forest: #5D7566
|
|
18
18
|
// on #0C1712); the dashed style is what distinguishes `references`.
|
|
@@ -2,6 +2,7 @@ import { signal } from '@preact/signals';
|
|
|
2
2
|
import { useEffect, useState } from 'preact/hooks';
|
|
3
3
|
import { requestJson } from '../state/intent-bridge';
|
|
4
4
|
import { activeBoardId } from '../state/boards-store';
|
|
5
|
+
import { downloadWorkbenchExport, isHostedWorkbench } from '../state/workbench-transport';
|
|
5
6
|
|
|
6
7
|
interface ExportManifest {
|
|
7
8
|
boardId: string;
|
|
@@ -43,6 +44,7 @@ export function ExportDialog() {
|
|
|
43
44
|
const [manifest, setManifest] = useState<ExportManifest | null>(null);
|
|
44
45
|
const [result, setResult] = useState<ExportResult | null>(null);
|
|
45
46
|
const [busy, setBusy] = useState(false);
|
|
47
|
+
const [downloadError, setDownloadError] = useState('');
|
|
46
48
|
const open = exportDialogOpen.value;
|
|
47
49
|
const boardId = activeBoardId.value;
|
|
48
50
|
|
|
@@ -50,6 +52,7 @@ export function ExportDialog() {
|
|
|
50
52
|
if (!open || !boardId) return;
|
|
51
53
|
let current = true;
|
|
52
54
|
setResult(null);
|
|
55
|
+
setDownloadError('');
|
|
53
56
|
setManifest(null);
|
|
54
57
|
void requestJson<{ manifest?: ExportManifest } | null>(
|
|
55
58
|
'exportPreview',
|
|
@@ -174,13 +177,37 @@ export function ExportDialog() {
|
|
|
174
177
|
Saved ({sizeLabel(result.bytes)}): <code>{result.path}</code>
|
|
175
178
|
</div>
|
|
176
179
|
<div class="export-dialog-links">
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
180
|
+
{isHostedWorkbench() ? (
|
|
181
|
+
<button
|
|
182
|
+
type="button"
|
|
183
|
+
disabled={busy}
|
|
184
|
+
onClick={async () => {
|
|
185
|
+
setBusy(true);
|
|
186
|
+
setDownloadError('');
|
|
187
|
+
try {
|
|
188
|
+
await downloadWorkbenchExport(result.url);
|
|
189
|
+
} catch (error) {
|
|
190
|
+
setDownloadError(error instanceof Error ? error.message : 'Download failed. Try again.');
|
|
191
|
+
} finally {
|
|
192
|
+
setBusy(false);
|
|
193
|
+
}
|
|
194
|
+
}}
|
|
195
|
+
>
|
|
196
|
+
{busy ? 'Downloading…' : 'Download'}
|
|
197
|
+
</button>
|
|
198
|
+
) : (
|
|
199
|
+
<>
|
|
200
|
+
<a href={result.url} target="_blank" rel="noopener noreferrer">
|
|
201
|
+
Open
|
|
202
|
+
</a>
|
|
203
|
+
<a class="is-primary" href={`${result.url}?download=1`}>
|
|
204
|
+
Download
|
|
205
|
+
</a>
|
|
206
|
+
</>
|
|
207
|
+
)}
|
|
183
208
|
</div>
|
|
209
|
+
{isHostedWorkbench() && <p>Download the HTML file to open it outside ChatGPT.</p>}
|
|
210
|
+
{downloadError && <p role="alert">{downloadError}</p>}
|
|
184
211
|
</div>
|
|
185
212
|
)}
|
|
186
213
|
<div class="text-prompt-actions">
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { useRef, useState } from 'preact/hooks';
|
|
2
2
|
import { useFocusTrap } from './use-focus-trap';
|
|
3
3
|
import { IconExternalLink } from '../icons';
|
|
4
|
+
import { ChoiceMenu } from './ChoiceMenu';
|
|
4
5
|
|
|
5
6
|
/** Draft only: GitHub owns authentication and the final public submission. */
|
|
6
7
|
export function FeedbackDialog({ onClose }: { onClose: () => void }) {
|
|
@@ -40,14 +41,15 @@ export function FeedbackDialog({ onClose }: { onClose: () => void }) {
|
|
|
40
41
|
public. Only what you enter below is included; no board contents, workspace paths, screenshots, or logs are
|
|
41
42
|
attached.
|
|
42
43
|
</p>
|
|
43
|
-
<
|
|
44
|
-
Feedback type
|
|
45
|
-
<
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
44
|
+
<div class="feedback-field">
|
|
45
|
+
<span>Feedback type</span>
|
|
46
|
+
<ChoiceMenu
|
|
47
|
+
label="Feedback type"
|
|
48
|
+
value={kind}
|
|
49
|
+
onChange={setKind}
|
|
50
|
+
options={['Bug report', 'Feature request', 'General feedback'].map((value) => ({ value, label: value }))}
|
|
51
|
+
/>
|
|
52
|
+
</div>
|
|
51
53
|
<label>
|
|
52
54
|
Title
|
|
53
55
|
<input
|
|
@@ -8,8 +8,8 @@ import type { CanvasNodeState } from '../types';
|
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* Ghost Cursor of Intent overlay. Renders the agent's pre-commit moves as faint
|
|
11
|
-
* placeholders in world space (it lives inside the canvas transform,
|
|
12
|
-
*
|
|
11
|
+
* placeholders in world space (it lives inside the canvas transform, so positions
|
|
12
|
+
* are world coords). Five kinds:
|
|
13
13
|
* create → dashed ghost node with icon + type badge
|
|
14
14
|
* move → ghost at the destination + a dashed trail from the current node
|
|
15
15
|
* connect → dashed bezier in the edge-type color
|
|
@@ -1,12 +1,16 @@
|
|
|
1
|
-
import { agentIdentityHue } from '../../shared/agent-presence.js';
|
|
2
1
|
import type { Signal } from '@preact/signals';
|
|
3
2
|
import { useCallback, useRef } from 'preact/hooks';
|
|
4
3
|
import { fenceRectFromNodes } from '../../shared/scope-fence.js';
|
|
5
|
-
import { selectedNodeIds } from '../state/canvas-store';
|
|
6
|
-
import {
|
|
4
|
+
import { contextPinnedNodeIds, selectedNodeIds } from '../state/canvas-store';
|
|
5
|
+
import {
|
|
6
|
+
agentPresences,
|
|
7
|
+
mutatingNodeIds,
|
|
8
|
+
presenceWorldPosition,
|
|
9
|
+
sessionActive,
|
|
10
|
+
writerColor,
|
|
11
|
+
} from '../state/presence-store';
|
|
7
12
|
import { scopeFence } from '../state/session-store';
|
|
8
13
|
import type { CanvasEdge, CanvasNodeState, ViewportState } from '../types';
|
|
9
|
-
import { KIND_COLOR } from './kind-colors';
|
|
10
14
|
|
|
11
15
|
/**
|
|
12
16
|
* Minimap v2 (rail-chrome-v2 phase 7, design item 19): a true-scale node map
|
|
@@ -141,6 +145,8 @@ export function Minimap({ viewport, nodes, onNavigate, containerWidth, container
|
|
|
141
145
|
);
|
|
142
146
|
|
|
143
147
|
const selected = selectedNodeIds.value;
|
|
148
|
+
const pinned = contextPinnedNodeIds.value;
|
|
149
|
+
const agentAtWork = sessionActive.value ? mutatingNodeIds.value : new Set<string>();
|
|
144
150
|
const fence = scopeFence.value;
|
|
145
151
|
const fenceRect = fence
|
|
146
152
|
? fenceRectFromNodes(
|
|
@@ -177,14 +183,13 @@ export function Minimap({ viewport, nodes, onNavigate, containerWidth, container
|
|
|
177
183
|
return (
|
|
178
184
|
<span
|
|
179
185
|
key={node.id}
|
|
180
|
-
class={`minimap-node${isGroup ? ' is-group' : ''}${selected.has(node.id) ? ' is-selected' : ''}`}
|
|
186
|
+
class={`minimap-node${isGroup ? ' is-group' : ''}${selected.has(node.id) ? ' is-selected' : ''}${pinned.has(node.id) ? ' is-pinned' : ''}${agentAtWork.has(node.id) ? ' is-agent' : ''}`}
|
|
181
187
|
data-kind={node.type}
|
|
182
188
|
style={{
|
|
183
189
|
left: `${toX(node.position.x)}px`,
|
|
184
190
|
top: `${toY(node.position.y)}px`,
|
|
185
191
|
width: `${Math.max(isGroup ? 6 : 4, node.size.width * scale)}px`,
|
|
186
192
|
height: `${Math.max(isGroup ? 5 : 3, node.size.height * scale)}px`,
|
|
187
|
-
'--kind': KIND_COLOR[node.type],
|
|
188
193
|
}}
|
|
189
194
|
/>
|
|
190
195
|
);
|
|
@@ -207,7 +212,7 @@ export function Minimap({ viewport, nodes, onNavigate, containerWidth, container
|
|
|
207
212
|
style={{
|
|
208
213
|
left: `${toX(dot.at.x) - 4}px`,
|
|
209
214
|
top: `${toY(dot.at.y) - 4}px`,
|
|
210
|
-
'--identity-color':
|
|
215
|
+
'--identity-color': writerColor(dot.id),
|
|
211
216
|
}}
|
|
212
217
|
/>
|
|
213
218
|
))}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { useState } from 'preact/hooks';
|
|
2
|
+
import { contextPinMeta, pinnedReadState, writerName } from '../state/context-status-store';
|
|
3
|
+
import { centerDistance } from '../../server/spatial-analysis.js';
|
|
4
|
+
import { nodes } from '../state/canvas-store';
|
|
5
|
+
import { nearPins } from '../state/near-pin-store';
|
|
6
|
+
import type { CanvasNodeState } from '../types';
|
|
7
|
+
import { BarHint } from './BarHint';
|
|
8
|
+
|
|
9
|
+
// Glyphs from docs/design/AgentContext.dc.html (24-unit paths).
|
|
10
|
+
export const GLYPHS = {
|
|
11
|
+
eye: 'M2 12s3.5-6 10-6 10 6 10 6-3.5 6-10 6S2 12 2 12zM12 9.5a2.5 2.5 0 1 0 0 5a2.5 2.5 0 1 0 0-5',
|
|
12
|
+
eyeOff:
|
|
13
|
+
'M3 3l18 18M10.6 6.1A10 10 0 0 1 12 6c6.5 0 10 6 10 6a17 17 0 0 1-3 3.6M6.6 6.6C3.8 8.3 2 12 2 12s3.5 6 10 6a9.6 9.6 0 0 0 4.4-1',
|
|
14
|
+
spark: 'M12 3l1.8 5.2L19 10l-5.2 1.8L12 17l-1.8-5.2L5 10l5.2-1.8z',
|
|
15
|
+
pen: 'M4 20l4.2-1L19 8.2 15.8 5 5 15.8z',
|
|
16
|
+
warn: 'M12 4l9 16H3zM12 10v4M12 17v.5',
|
|
17
|
+
} as const;
|
|
18
|
+
|
|
19
|
+
interface Mark {
|
|
20
|
+
tone: 'agent' | 'muted' | 'warn';
|
|
21
|
+
glyph: keyof typeof GLYPHS;
|
|
22
|
+
word: string;
|
|
23
|
+
label: string;
|
|
24
|
+
body: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function clock(iso: string): string {
|
|
28
|
+
const date = new Date(iso);
|
|
29
|
+
return Number.isNaN(date.getTime()) ? iso : date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function markFor(node: CanvasNodeState, pinned: boolean): Mark | null {
|
|
33
|
+
if (pinned) {
|
|
34
|
+
const pin = contextPinMeta.value[node.id];
|
|
35
|
+
const pinnedBy =
|
|
36
|
+
pin?.pinnedBy.actor === 'agent'
|
|
37
|
+
? ` Pinned by ${writerName(pin.pinnedBy)}${pin.reason ? `: ${pin.reason}` : ''}.`
|
|
38
|
+
: '';
|
|
39
|
+
const state = pinnedReadState(node.id, node.contentRevision ?? 0);
|
|
40
|
+
if (state.kind === 'not-read') {
|
|
41
|
+
return {
|
|
42
|
+
tone: 'muted',
|
|
43
|
+
glyph: 'eyeOff',
|
|
44
|
+
word: 'not read',
|
|
45
|
+
label: 'Not read yet',
|
|
46
|
+
body: `In context, but no agent has loaded it since it was pinned.${pinnedBy}`,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
if (state.kind === 'changed') {
|
|
50
|
+
return {
|
|
51
|
+
tone: 'warn',
|
|
52
|
+
glyph: 'warn',
|
|
53
|
+
word: 'changed since read',
|
|
54
|
+
label: "The agent's copy is out of date",
|
|
55
|
+
body: `Changed after ${state.by} read it at ${clock(state.at)}.${pinnedBy}`,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
return {
|
|
59
|
+
tone: 'agent',
|
|
60
|
+
glyph: 'eye',
|
|
61
|
+
word: 'read',
|
|
62
|
+
label: 'Read by the agent',
|
|
63
|
+
body: `${state.count > 1 ? `Read ${state.count} times · last` : 'Read'} ${clock(state.at)} by ${state.by}.${pinnedBy}`,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
// A person's edit ends the agent's byline (decided 2026-10-05).
|
|
67
|
+
if (node.lastEditedBy?.actor === 'human') return null;
|
|
68
|
+
if (node.createdBy?.actor === 'agent') {
|
|
69
|
+
const name = writerName(node.createdBy);
|
|
70
|
+
return {
|
|
71
|
+
tone: 'agent',
|
|
72
|
+
glyph: 'spark',
|
|
73
|
+
word: `by ${name}`,
|
|
74
|
+
label: `Created by ${name}`,
|
|
75
|
+
body: 'The byline stays until you edit the node.',
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
if (node.lastEditedBy?.actor === 'agent') {
|
|
79
|
+
const name = writerName(node.lastEditedBy);
|
|
80
|
+
return {
|
|
81
|
+
tone: 'agent',
|
|
82
|
+
glyph: 'pen',
|
|
83
|
+
word: 'edited',
|
|
84
|
+
label: `Edited by ${name}`,
|
|
85
|
+
body: 'The mark stays until you edit the node.',
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** The header chip saying what agents did with this node (one per node; glyph + word). */
|
|
92
|
+
export function NodeContextMark({ node, pinned }: { node: CanvasNodeState; pinned: boolean }) {
|
|
93
|
+
const mark = markFor(node, pinned);
|
|
94
|
+
if (!mark) return null;
|
|
95
|
+
return (
|
|
96
|
+
<BarHint label={mark.label} body={mark.body} align="end" tapToOpen>
|
|
97
|
+
<span class={`node-context-mark is-${mark.tone}`} data-mark={mark.word}>
|
|
98
|
+
<svg viewBox="0 0 24 24" aria-hidden="true">
|
|
99
|
+
<path d={GLYPHS[mark.glyph]} />
|
|
100
|
+
</svg>
|
|
101
|
+
{mark.word}
|
|
102
|
+
</span>
|
|
103
|
+
</BarHint>
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** Pinned by an agent: the violet dot on the pin badge. */
|
|
108
|
+
export function isAgentPin(nodeId: string): boolean {
|
|
109
|
+
return contextPinMeta.value[nodeId]?.pinnedBy.actor === 'agent';
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Near a pin (docs/design/NearPin.dc.html): a dotted pin-blue chip on an
|
|
114
|
+
* unpinned node the brief carries as title + short summary. Weaker than "in
|
|
115
|
+
* context" on purpose: the agent never gets this node's full content.
|
|
116
|
+
*/
|
|
117
|
+
export function NearPinMark({ node, pinned }: { node: CanvasNodeState; pinned: boolean }) {
|
|
118
|
+
// The pins' titles join this node's DOM only while the chip is hovered or
|
|
119
|
+
// focused: at rest the node must not contain other nodes' titles.
|
|
120
|
+
const [open, setOpen] = useState(false);
|
|
121
|
+
const near = pinned ? undefined : nearPins.value.get(node.id);
|
|
122
|
+
if (!near || near.length === 0) return null;
|
|
123
|
+
const word = near.length > 1 ? `near ${near.length}` : 'near';
|
|
124
|
+
// Distances change every drag frame; read them only while the hint is open.
|
|
125
|
+
const where = open
|
|
126
|
+
? near
|
|
127
|
+
.map((pin) => {
|
|
128
|
+
const pinNode = nodes.value.get(pin.pinNodeId);
|
|
129
|
+
return pinNode ? `“${pin.pinTitle}” (${Math.round(centerDistance(node, pinNode))} px)` : `“${pin.pinTitle}”`;
|
|
130
|
+
})
|
|
131
|
+
.join(', ')
|
|
132
|
+
: '';
|
|
133
|
+
return (
|
|
134
|
+
<BarHint
|
|
135
|
+
label={open ? `Near ${where}` : 'Near a pin'}
|
|
136
|
+
body="The agent gets its title and a short summary, not its full content. Pin it to send its content."
|
|
137
|
+
align="end"
|
|
138
|
+
tapToOpen
|
|
139
|
+
>
|
|
140
|
+
<span
|
|
141
|
+
class="node-near-mark"
|
|
142
|
+
data-near={near.map((pin) => pin.pinNodeId).join(' ')}
|
|
143
|
+
onMouseEnter={() => setOpen(true)}
|
|
144
|
+
onMouseLeave={() => setOpen(false)}
|
|
145
|
+
onFocusIn={() => setOpen(true)}
|
|
146
|
+
onFocusOut={() => setOpen(false)}
|
|
147
|
+
>
|
|
148
|
+
<svg viewBox="0 0 24 24" aria-hidden="true">
|
|
149
|
+
<circle cx="12" cy="12" r="8" class="node-near-ring" />
|
|
150
|
+
<circle cx="12" cy="12" r="2.2" class="node-near-dot" />
|
|
151
|
+
</svg>
|
|
152
|
+
{word}
|
|
153
|
+
</span>
|
|
154
|
+
</BarHint>
|
|
155
|
+
);
|
|
156
|
+
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { useEffect, useState } from 'preact/hooks';
|
|
2
2
|
import { IconChevronRight, IconUndo } from '../icons';
|
|
3
3
|
import { useNow } from './use-now';
|
|
4
|
-
import {
|
|
4
|
+
import { agentPhaseLabel } from '../../shared/agent-presence.js';
|
|
5
5
|
import { formatCountdown, gateRemainingMs } from '../../shared/approval-gates.js';
|
|
6
6
|
import { focusNode, selectedNodeIds } from '../state/canvas-store';
|
|
7
|
-
import { activeSession, agentPresences } from '../state/presence-store';
|
|
7
|
+
import { activeSession, agentPresences, writerColor } from '../state/presence-store';
|
|
8
8
|
import {
|
|
9
9
|
type ApprovalGateView,
|
|
10
10
|
endSession,
|
|
@@ -281,12 +281,9 @@ function TimelineRow({ entry }: { entry: TimelineEntry }) {
|
|
|
281
281
|
{entry.label}
|
|
282
282
|
</span>
|
|
283
283
|
{/* With several assistants on one board, "Assistant · 23:11" answers
|
|
284
|
-
nothing — every row names its writer
|
|
284
|
+
nothing — every row names its writer. */}
|
|
285
285
|
{entry.who && (
|
|
286
|
-
<span
|
|
287
|
-
class="session-timeline-who"
|
|
288
|
-
style={{ '--identity-color': `hsl(${agentIdentityHue(entry.who)} 65% 62%)` }}
|
|
289
|
-
>
|
|
286
|
+
<span class="session-timeline-who" style={{ '--identity-color': writerColor(entry.who) }}>
|
|
290
287
|
{writerDisplay(entry.who)}
|
|
291
288
|
</span>
|
|
292
289
|
)}
|