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,16 +1,22 @@
|
|
|
1
|
-
import { useEffect, useState } from 'preact/hooks';
|
|
2
|
-
import {
|
|
1
|
+
import { useEffect, useLayoutEffect, useMemo, useRef, useState } from 'preact/hooks';
|
|
2
|
+
import { BarHint } from './BarHint';
|
|
3
|
+
import { nodes, removeContextPins } from '../state/canvas-store';
|
|
4
|
+
import { activeBoardId, boardList, openBoard } from '../state/boards-store';
|
|
3
5
|
import {
|
|
4
|
-
|
|
6
|
+
activityLens,
|
|
5
7
|
dismissSessionReceipt,
|
|
6
8
|
type ReceiptNode,
|
|
9
|
+
receiptNodeKey,
|
|
7
10
|
type SessionContextActivity,
|
|
11
|
+
sendSteering,
|
|
8
12
|
sessionReceipt,
|
|
9
13
|
setActivityLens,
|
|
14
|
+
undoSessionEdits,
|
|
10
15
|
} from '../state/session-store';
|
|
11
16
|
import { IconClose, IconPin } from '../icons';
|
|
12
17
|
import { GLYPHS } from './NodeContextMark';
|
|
13
18
|
import { workbenchFetch } from '../state/workbench-transport';
|
|
19
|
+
import { agentSessionsEnabled } from '../state/workspace-settings';
|
|
14
20
|
|
|
15
21
|
/**
|
|
16
22
|
* Session receipt (rail-chrome-v2 phase 5, design item 2): a dismissible card
|
|
@@ -42,12 +48,58 @@ export function summarizeDiff(diff: unknown): DiffSummary | null {
|
|
|
42
48
|
|
|
43
49
|
const titles = (nodes: ReceiptNode[]) => nodes.map((node) => node.title).join(', ');
|
|
44
50
|
|
|
51
|
+
function clock(iso: string): string {
|
|
52
|
+
const at = new Date(iso);
|
|
53
|
+
return Number.isNaN(at.getTime())
|
|
54
|
+
? ''
|
|
55
|
+
: `${String(at.getHours()).padStart(2, '0')}:${String(at.getMinutes()).padStart(2, '0')}`;
|
|
56
|
+
}
|
|
57
|
+
|
|
45
58
|
/**
|
|
46
59
|
* What the session did with context (docs/design/AgentContext.dc.html): an
|
|
47
60
|
* amber line for pins it read that changed since, one row per kind of touch,
|
|
48
61
|
* and the lens switch that dims every node the session did not touch.
|
|
49
62
|
*/
|
|
50
|
-
function ReceiptContext({
|
|
63
|
+
function ReceiptContext({
|
|
64
|
+
context,
|
|
65
|
+
label,
|
|
66
|
+
snapshotId,
|
|
67
|
+
}: {
|
|
68
|
+
context: SessionContextActivity;
|
|
69
|
+
label: string;
|
|
70
|
+
/** The pre-session snapshot: what Undo on an edit restores from (single sessions only). */
|
|
71
|
+
snapshotId: string | null;
|
|
72
|
+
}) {
|
|
73
|
+
const [told, setTold] = useState(false);
|
|
74
|
+
const [restoredIds, setRestoredIds] = useState<string[]>([]);
|
|
75
|
+
const [keptEdits, setKeptEdits] = useState(false);
|
|
76
|
+
const [openId, setOpenId] = useState<string | null>(null);
|
|
77
|
+
const [undoing, setUndoing] = useState(false);
|
|
78
|
+
const rowUndo =
|
|
79
|
+
context.edited.length > 0 && context.edited.every((node) => restoredIds.includes(node.id))
|
|
80
|
+
? 'undone'
|
|
81
|
+
: keptEdits
|
|
82
|
+
? 'kept'
|
|
83
|
+
: null;
|
|
84
|
+
useEffect(() => {
|
|
85
|
+
setRestoredIds([]);
|
|
86
|
+
setKeptEdits(false);
|
|
87
|
+
setOpenId(null);
|
|
88
|
+
}, [context]);
|
|
89
|
+
const undo = async (edits: ReceiptNode[]) => {
|
|
90
|
+
if (!snapshotId || undoing) return;
|
|
91
|
+
setUndoing(true);
|
|
92
|
+
try {
|
|
93
|
+
const result = await undoSessionEdits(
|
|
94
|
+
snapshotId,
|
|
95
|
+
edits.filter((node) => !restoredIds.includes(node.id)),
|
|
96
|
+
);
|
|
97
|
+
setRestoredIds((previous) => [...previous, ...result.restored]);
|
|
98
|
+
return result;
|
|
99
|
+
} finally {
|
|
100
|
+
setUndoing(false);
|
|
101
|
+
}
|
|
102
|
+
};
|
|
51
103
|
const rows: Array<{ key: keyof SessionContextActivity; name: string; glyph: keyof typeof GLYPHS | 'pin' }> = [
|
|
52
104
|
{ key: 'read', name: 'Read', glyph: 'eye' },
|
|
53
105
|
{ key: 'pinned', name: 'Pinned', glyph: 'pin' },
|
|
@@ -56,7 +108,7 @@ function ReceiptContext({ context, label }: { context: SessionContextActivity; l
|
|
|
56
108
|
];
|
|
57
109
|
const present = rows.filter((row) => context[row.key].length > 0);
|
|
58
110
|
if (present.length === 0 && context.changedSinceRead.length === 0) return null;
|
|
59
|
-
const lensOn =
|
|
111
|
+
const lensOn = activityLens.value?.kind === 'receipt';
|
|
60
112
|
return (
|
|
61
113
|
<div class="session-receipt-context" data-testid="session-receipt-context">
|
|
62
114
|
{context.changedSinceRead.length > 0 && (
|
|
@@ -68,6 +120,19 @@ function ReceiptContext({ context, label }: { context: SessionContextActivity; l
|
|
|
68
120
|
{titles(context.changedSinceRead)} changed after {label} read{' '}
|
|
69
121
|
{context.changedSinceRead.length === 1 ? 'it' : 'them'}.
|
|
70
122
|
</span>
|
|
123
|
+
<button
|
|
124
|
+
type="button"
|
|
125
|
+
class="session-receipt-mini"
|
|
126
|
+
disabled={told}
|
|
127
|
+
onClick={() => {
|
|
128
|
+
const them = context.changedSinceRead.length === 1 ? 'it' : 'them';
|
|
129
|
+
void sendSteering(
|
|
130
|
+
`${context.changedSinceRead.map((node) => `“${node.title}”`).join(', ')} changed after you read ${them} — read ${them} again before relying on ${them}.`,
|
|
131
|
+
).then((ok) => setTold(ok));
|
|
132
|
+
}}
|
|
133
|
+
>
|
|
134
|
+
{told ? 'Told' : `Tell ${label}`}
|
|
135
|
+
</button>
|
|
71
136
|
</div>
|
|
72
137
|
)}
|
|
73
138
|
{present.map((row) => (
|
|
@@ -86,19 +151,60 @@ function ReceiptContext({ context, label }: { context: SessionContextActivity; l
|
|
|
86
151
|
<button
|
|
87
152
|
type="button"
|
|
88
153
|
class="session-receipt-mini"
|
|
154
|
+
disabled={context.pinned.some((node) => node.boardId && node.boardId !== activeBoardId.value)}
|
|
155
|
+
onClick={() => removeContextPins(context.pinned.map((node) => node.id))}
|
|
156
|
+
>
|
|
157
|
+
Unpin
|
|
158
|
+
</button>
|
|
159
|
+
)}
|
|
160
|
+
{row.key === 'edited' && snapshotId && (
|
|
161
|
+
<button
|
|
162
|
+
type="button"
|
|
163
|
+
class="session-receipt-mini"
|
|
164
|
+
disabled={
|
|
165
|
+
rowUndo !== null ||
|
|
166
|
+
undoing ||
|
|
167
|
+
context.edited.some((node) => node.boardId && node.boardId !== activeBoardId.value)
|
|
168
|
+
}
|
|
89
169
|
onClick={() => {
|
|
90
|
-
|
|
170
|
+
void undo(context.edited).then((result) => {
|
|
171
|
+
if (result?.ok) setKeptEdits(true);
|
|
172
|
+
});
|
|
91
173
|
}}
|
|
92
174
|
>
|
|
93
|
-
|
|
175
|
+
{rowUndo === 'undone' ? '✓ Undone' : rowUndo === 'kept' ? 'Kept your edits' : 'Undo'}
|
|
94
176
|
</button>
|
|
95
177
|
)}
|
|
96
178
|
</div>
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
179
|
+
{row.key === 'edited' && rowUndo && (
|
|
180
|
+
<div class="session-receipt-context-items">
|
|
181
|
+
{rowUndo === 'undone' ? 'every card restored' : 'cards you edited since were left alone'}
|
|
182
|
+
</div>
|
|
183
|
+
)}
|
|
184
|
+
{row.key === 'edited' ? (
|
|
185
|
+
<ul class="session-receipt-edits">
|
|
186
|
+
{context.edited.map((node) => (
|
|
187
|
+
<EditLine
|
|
188
|
+
key={receiptNodeKey(node)}
|
|
189
|
+
node={node}
|
|
190
|
+
canUndo={snapshotId !== null}
|
|
191
|
+
undoing={undoing}
|
|
192
|
+
open={openId === receiptNodeKey(node)}
|
|
193
|
+
onToggle={() => setOpenId(openId === receiptNodeKey(node) ? null : receiptNodeKey(node))}
|
|
194
|
+
undone={restoredIds.includes(node.id)}
|
|
195
|
+
onUndo={() => void undo([node])}
|
|
196
|
+
/>
|
|
197
|
+
))}
|
|
198
|
+
</ul>
|
|
199
|
+
) : (
|
|
200
|
+
<div class="session-receipt-context-items">
|
|
201
|
+
{row.key === 'pinned'
|
|
202
|
+
? context.pinned
|
|
203
|
+
.map((node) => (node.reason ? `${node.title} — “${node.reason}”` : node.title))
|
|
204
|
+
.join('; ')
|
|
205
|
+
: titles(context[row.key])}
|
|
206
|
+
</div>
|
|
207
|
+
)}
|
|
102
208
|
</div>
|
|
103
209
|
))}
|
|
104
210
|
<button
|
|
@@ -117,16 +223,160 @@ function ReceiptContext({ context, label }: { context: SessionContextActivity; l
|
|
|
117
223
|
);
|
|
118
224
|
}
|
|
119
225
|
|
|
226
|
+
/** Align the bounded excerpts by word, preserving punctuation and whitespace verbatim. */
|
|
227
|
+
function changedWords(before: string, after: string) {
|
|
228
|
+
const tokens = (text: string) => text.match(/\s+|[\p{L}\p{N}_]+|[^\s\p{L}\p{N}_]/gu) ?? [];
|
|
229
|
+
const a = tokens(before);
|
|
230
|
+
const b = tokens(after);
|
|
231
|
+
const lengths = Array.from({ length: a.length + 1 }, () => new Uint16Array(b.length + 1));
|
|
232
|
+
for (let i = a.length - 1; i >= 0; i--) {
|
|
233
|
+
for (let j = b.length - 1; j >= 0; j--) {
|
|
234
|
+
lengths[i][j] = a[i] === b[j] ? lengths[i + 1][j + 1] + 1 : Math.max(lengths[i + 1][j], lengths[i][j + 1]);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
const was: Array<{ text: string; changed: boolean }> = [];
|
|
238
|
+
const now: Array<{ text: string; changed: boolean }> = [];
|
|
239
|
+
let i = 0;
|
|
240
|
+
let j = 0;
|
|
241
|
+
while (i < a.length || j < b.length) {
|
|
242
|
+
if (i < a.length && j < b.length && a[i] === b[j]) {
|
|
243
|
+
was.push({ text: a[i++], changed: false });
|
|
244
|
+
now.push({ text: b[j++], changed: false });
|
|
245
|
+
} else if (i < a.length && (j === b.length || lengths[i + 1][j] >= lengths[i][j + 1])) {
|
|
246
|
+
was.push({ text: a[i++], changed: true });
|
|
247
|
+
} else {
|
|
248
|
+
now.push({ text: b[j++], changed: true });
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
return [was, now];
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/** SeeChange.dc.html: one stacked, word-marked change, with Undo for the whole card. */
|
|
255
|
+
function EditLine({
|
|
256
|
+
node,
|
|
257
|
+
canUndo,
|
|
258
|
+
undoing,
|
|
259
|
+
open,
|
|
260
|
+
onToggle,
|
|
261
|
+
undone,
|
|
262
|
+
onUndo,
|
|
263
|
+
}: {
|
|
264
|
+
node: ReceiptNode;
|
|
265
|
+
canUndo: boolean;
|
|
266
|
+
undoing: boolean;
|
|
267
|
+
open: boolean;
|
|
268
|
+
onToggle: () => void;
|
|
269
|
+
undone: boolean;
|
|
270
|
+
onUndo: () => void;
|
|
271
|
+
}) {
|
|
272
|
+
const onBoard = !node.boardId || node.boardId === activeBoardId.value;
|
|
273
|
+
const current = onBoard ? nodes.value.get(node.id) : undefined;
|
|
274
|
+
const board = boardList.value.find((entry) => entry.id === node.boardId);
|
|
275
|
+
const sides = useMemo(
|
|
276
|
+
() => (open ? changedWords(node.before ?? '', node.after ?? '') : []),
|
|
277
|
+
[open, node.before, node.after],
|
|
278
|
+
);
|
|
279
|
+
const pair = useRef<HTMLSpanElement>(null);
|
|
280
|
+
useLayoutEffect(() => {
|
|
281
|
+
// Long excerpts retain context above the edit; start each scroll box at
|
|
282
|
+
// the first changed word rather than a screenful of identical preamble.
|
|
283
|
+
for (const text of pair.current?.querySelectorAll<HTMLElement>('.session-receipt-diff-text') ?? []) {
|
|
284
|
+
const change = text.querySelector('del, ins');
|
|
285
|
+
if (change) text.scrollTop = change.getBoundingClientRect().top - text.getBoundingClientRect().top - 24;
|
|
286
|
+
}
|
|
287
|
+
}, [sides]);
|
|
288
|
+
// Your own later edit wins: Undo would leave it, so it is not offered.
|
|
289
|
+
const yours = current?.lastEditedBy?.actor === 'human';
|
|
290
|
+
return (
|
|
291
|
+
<li class="session-receipt-edit">
|
|
292
|
+
<span class="session-receipt-edit-line">
|
|
293
|
+
<span>
|
|
294
|
+
{node.title}
|
|
295
|
+
{node.change ? <span class="session-receipt-edit-change"> — {node.change}</span> : null}
|
|
296
|
+
</span>
|
|
297
|
+
{node.before !== undefined && (
|
|
298
|
+
<button type="button" class="session-receipt-link" aria-expanded={open} onClick={onToggle}>
|
|
299
|
+
{open ? 'Hide change' : 'See change'}
|
|
300
|
+
</button>
|
|
301
|
+
)}
|
|
302
|
+
</span>
|
|
303
|
+
{open && (
|
|
304
|
+
<span ref={pair} class="session-receipt-diff-pair">
|
|
305
|
+
{sides.map((words, side) => (
|
|
306
|
+
<span class="session-receipt-diff-side" key={side}>
|
|
307
|
+
<span class="session-receipt-diff-label">{side === 0 ? 'Before' : 'After'}</span>
|
|
308
|
+
<span class="session-receipt-diff-text">
|
|
309
|
+
{words.length
|
|
310
|
+
? words.map(({ text, changed }, index) =>
|
|
311
|
+
text === '…' && (index === 0 || index === words.length - 1) ? (
|
|
312
|
+
<span key={index} class="session-receipt-diff-cut">
|
|
313
|
+
{text}
|
|
314
|
+
</span>
|
|
315
|
+
) : changed ? (
|
|
316
|
+
side === 0 ? (
|
|
317
|
+
<del key={index}>{text}</del>
|
|
318
|
+
) : (
|
|
319
|
+
<ins key={index}>{text}</ins>
|
|
320
|
+
)
|
|
321
|
+
) : (
|
|
322
|
+
text
|
|
323
|
+
),
|
|
324
|
+
)
|
|
325
|
+
: 'Empty'}
|
|
326
|
+
</span>
|
|
327
|
+
</span>
|
|
328
|
+
))}
|
|
329
|
+
{canUndo && (
|
|
330
|
+
<span class="session-receipt-undo">
|
|
331
|
+
{current ? (
|
|
332
|
+
<button
|
|
333
|
+
type="button"
|
|
334
|
+
class="session-receipt-mini"
|
|
335
|
+
disabled={undone || yours || undoing}
|
|
336
|
+
onClick={onUndo}
|
|
337
|
+
>
|
|
338
|
+
{undone ? '✓ Undone' : yours ? 'You edited it since' : 'Undo this card'}
|
|
339
|
+
</button>
|
|
340
|
+
) : board && !onBoard ? (
|
|
341
|
+
<button type="button" class="session-receipt-mini" onClick={() => void openBoard(board.id)}>
|
|
342
|
+
Open {board.name} to undo
|
|
343
|
+
</button>
|
|
344
|
+
) : (
|
|
345
|
+
<span class="session-receipt-edit-change">Card no longer on this board</span>
|
|
346
|
+
)}
|
|
347
|
+
<span class="session-receipt-edit-change">
|
|
348
|
+
{current
|
|
349
|
+
? undone
|
|
350
|
+
? 'the card shows Before again'
|
|
351
|
+
: yours
|
|
352
|
+
? 'undo would lose your edit'
|
|
353
|
+
: node.before?.includes('…') || node.after?.includes('…')
|
|
354
|
+
? 'undoes the whole card'
|
|
355
|
+
: 'puts back the text from before'
|
|
356
|
+
: board && !onBoard
|
|
357
|
+
? `it is on ${board.name}`
|
|
358
|
+
: ''}
|
|
359
|
+
</span>
|
|
360
|
+
</span>
|
|
361
|
+
)}
|
|
362
|
+
</span>
|
|
363
|
+
)}
|
|
364
|
+
</li>
|
|
365
|
+
);
|
|
366
|
+
}
|
|
367
|
+
|
|
120
368
|
export function SessionReceipt({ onOpenSnapshots }: { onOpenSnapshots: () => void }) {
|
|
121
369
|
const receipt = sessionReceipt.value;
|
|
122
370
|
const [diff, setDiff] = useState<DiffSummary | null>(null);
|
|
123
371
|
const [loadingDiff, setLoadingDiff] = useState(false);
|
|
124
372
|
// A new or merged receipt: the old diff no longer describes it.
|
|
125
373
|
useEffect(() => setDiff(null), [receipt]);
|
|
126
|
-
if (!receipt) return null;
|
|
374
|
+
if (!agentSessionsEnabled.value || !receipt) return null;
|
|
127
375
|
|
|
376
|
+
const onSnapshotBoard = receipt.snapshot?.boardId === activeBoardId.value;
|
|
377
|
+
const snapshotBoard = boardList.value.find((board) => board.id === receipt.snapshot?.boardId);
|
|
128
378
|
const viewDiff = async () => {
|
|
129
|
-
if (!receipt.snapshot) return;
|
|
379
|
+
if (!receipt.snapshot || !onSnapshotBoard) return;
|
|
130
380
|
setLoadingDiff(true);
|
|
131
381
|
try {
|
|
132
382
|
const response = await workbenchFetch(`/api/canvas/snapshots/${encodeURIComponent(receipt.snapshot.id)}/diff`, {
|
|
@@ -141,10 +391,16 @@ export function SessionReceipt({ onOpenSnapshots }: { onOpenSnapshots: () => voi
|
|
|
141
391
|
}
|
|
142
392
|
};
|
|
143
393
|
|
|
144
|
-
const
|
|
145
|
-
const
|
|
146
|
-
|
|
147
|
-
|
|
394
|
+
const endedLabel = clock(receipt.endedAt);
|
|
395
|
+
const startedLabel = receipt.startedAt ? clock(receipt.startedAt) : '';
|
|
396
|
+
const endedBy =
|
|
397
|
+
receipt.endedBy === 'human'
|
|
398
|
+
? 'ended by you'
|
|
399
|
+
: receipt.endedBy === 'idle-timeout'
|
|
400
|
+
? 'ended on idle timeout'
|
|
401
|
+
: receipt.endedBy === 'agent'
|
|
402
|
+
? 'ended by the agent'
|
|
403
|
+
: '';
|
|
148
404
|
|
|
149
405
|
return (
|
|
150
406
|
<div class="session-receipt" data-testid="session-receipt" role="status">
|
|
@@ -152,15 +408,8 @@ export function SessionReceipt({ onOpenSnapshots }: { onOpenSnapshots: () => voi
|
|
|
152
408
|
<span class="session-receipt-dot" aria-hidden="true" />
|
|
153
409
|
<span class="session-receipt-title">
|
|
154
410
|
{receipt.sessions > 1
|
|
155
|
-
? `${receipt.sessions} sessions ended`
|
|
156
|
-
: receipt.
|
|
157
|
-
? 'Session ended by you'
|
|
158
|
-
: receipt.endedBy === 'idle-timeout'
|
|
159
|
-
? 'Session ended — idle timeout'
|
|
160
|
-
: receipt.endedBy === 'agent'
|
|
161
|
-
? 'Session ended by the agent'
|
|
162
|
-
: 'Session ended'}
|
|
163
|
-
{endedLabel ? ` · ${endedLabel}` : ''}
|
|
411
|
+
? `${receipt.sessions} sessions ended${endedLabel ? ` · ${endedLabel}` : ''}`
|
|
412
|
+
: `What ${receipt.labels[0]} did`}
|
|
164
413
|
</span>
|
|
165
414
|
<button
|
|
166
415
|
type="button"
|
|
@@ -171,7 +420,20 @@ export function SessionReceipt({ onOpenSnapshots }: { onOpenSnapshots: () => voi
|
|
|
171
420
|
<IconClose />
|
|
172
421
|
</button>
|
|
173
422
|
</div>
|
|
174
|
-
{receipt.sessions > 1
|
|
423
|
+
{receipt.sessions > 1 ? (
|
|
424
|
+
<div class="session-receipt-who">{receipt.labels.join(', ')}</div>
|
|
425
|
+
) : (
|
|
426
|
+
// AgentContext.dc.html: "This session · 14:02–14:20 · saved as a snapshot".
|
|
427
|
+
<div class="session-receipt-who" data-testid="session-receipt-span">
|
|
428
|
+
{[
|
|
429
|
+
`This session · ${startedLabel ? `${startedLabel}–` : ''}${endedLabel}`,
|
|
430
|
+
receipt.snapshot ? 'saved as a snapshot' : '',
|
|
431
|
+
endedBy,
|
|
432
|
+
]
|
|
433
|
+
.filter(Boolean)
|
|
434
|
+
.join(' · ')}
|
|
435
|
+
</div>
|
|
436
|
+
)}
|
|
175
437
|
<div class="session-receipt-tiles">
|
|
176
438
|
<div class="session-receipt-tile">
|
|
177
439
|
<span class="session-receipt-tile-label">Items</span>
|
|
@@ -202,7 +464,11 @@ export function SessionReceipt({ onOpenSnapshots }: { onOpenSnapshots: () => voi
|
|
|
202
464
|
</div>
|
|
203
465
|
)}
|
|
204
466
|
</div>
|
|
205
|
-
<ReceiptContext
|
|
467
|
+
<ReceiptContext
|
|
468
|
+
context={receipt.context}
|
|
469
|
+
label={receipt.labels.join(', ')}
|
|
470
|
+
snapshotId={receipt.sessions === 1 ? (receipt.snapshot?.id ?? null) : null}
|
|
471
|
+
/>
|
|
206
472
|
<div class="session-receipt-note">
|
|
207
473
|
{receipt.sessions > 1
|
|
208
474
|
? 'History has each session’s diff and the snapshot from before it.'
|
|
@@ -221,18 +487,24 @@ export function SessionReceipt({ onOpenSnapshots }: { onOpenSnapshots: () => voi
|
|
|
221
487
|
{/* No snapshot → no diff will ever exist for this receipt; a forever-
|
|
222
488
|
disabled button is noise (the note above says why). */}
|
|
223
489
|
{receipt.snapshot && (
|
|
224
|
-
<button
|
|
225
|
-
|
|
490
|
+
<button
|
|
491
|
+
type="button"
|
|
492
|
+
class="session-receipt-primary"
|
|
493
|
+
disabled={loadingDiff || (!onSnapshotBoard && !snapshotBoard)}
|
|
494
|
+
onClick={() => (onSnapshotBoard ? void viewDiff() : snapshotBoard && void openBoard(snapshotBoard.id))}
|
|
495
|
+
>
|
|
496
|
+
{!onSnapshotBoard && snapshotBoard
|
|
497
|
+
? `Open ${snapshotBoard.name} to compare`
|
|
498
|
+
: loadingDiff
|
|
499
|
+
? 'Comparing…'
|
|
500
|
+
: 'View diff'}
|
|
226
501
|
</button>
|
|
227
502
|
)}
|
|
228
|
-
<
|
|
229
|
-
type="button"
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
>
|
|
234
|
-
History
|
|
235
|
-
</button>
|
|
503
|
+
<BarHint label="Open the History drawer" body="Saved boards and past sessions." side="up" align="end">
|
|
504
|
+
<button type="button" class="session-receipt-secondary" onClick={onOpenSnapshots}>
|
|
505
|
+
History
|
|
506
|
+
</button>
|
|
507
|
+
</BarHint>
|
|
236
508
|
</div>
|
|
237
509
|
</div>
|
|
238
510
|
);
|
|
@@ -45,7 +45,7 @@ const GROUPS: ShortcutGroup[] = [
|
|
|
45
45
|
{ keys: 'Drag empty space', desc: 'Lasso select multiple nodes' },
|
|
46
46
|
{ keys: isMac ? 'Delete / \u232b' : 'Delete / Backspace', desc: 'Delete the selection (or the focused node)' },
|
|
47
47
|
{ keys: isMac ? '\u2318Z / \u21e7\u2318Z' : 'Ctrl+Z / Ctrl+Y', desc: 'Undo / redo — agent edits included' },
|
|
48
|
-
{ keys: 'Esc', desc: '
|
|
48
|
+
{ keys: 'Esc', desc: 'Close overlay first, then clear selection and view filters' },
|
|
49
49
|
],
|
|
50
50
|
},
|
|
51
51
|
{
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { timeAgo } from '../utils/time-ago';
|
|
2
|
+
import { BarHint } from './BarHint';
|
|
1
3
|
import { isHostedWorkbench } from '../state/workbench-transport';
|
|
2
4
|
import { useCallback, useEffect, useRef, useState } from 'preact/hooks';
|
|
3
5
|
import { IconClose } from '../icons';
|
|
@@ -29,17 +31,6 @@ function timeLabel(iso: string): string {
|
|
|
29
31
|
return `${String(date.getHours()).padStart(2, '0')}:${String(date.getMinutes()).padStart(2, '0')}`;
|
|
30
32
|
}
|
|
31
33
|
|
|
32
|
-
function timeAgo(iso: string): string {
|
|
33
|
-
const diff = Date.now() - new Date(iso).getTime();
|
|
34
|
-
const mins = Math.floor(diff / 60000);
|
|
35
|
-
if (mins < 1) return 'just now';
|
|
36
|
-
if (mins < 60) return `${mins}m ago`;
|
|
37
|
-
const hrs = Math.floor(mins / 60);
|
|
38
|
-
if (hrs < 24) return `${hrs}h ago`;
|
|
39
|
-
const days = Math.floor(hrs / 24);
|
|
40
|
-
return `${days}d ago`;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
34
|
/** "Before session · Copilot · 14:00" → "Copilot". */
|
|
44
35
|
function sessionLabel(name: string): string | null {
|
|
45
36
|
if (!name.startsWith(SESSION_PREFIX)) return null;
|
|
@@ -151,7 +142,6 @@ export function SnapshotPanel({
|
|
|
151
142
|
type="button"
|
|
152
143
|
class={`snapshot-action-btn ${confirming.action === 'delete' ? 'snapshot-action-confirm' : 'snapshot-action-restore'}`}
|
|
153
144
|
onClick={() => (confirming.action === 'delete' ? handleDelete(snap.id) : handleRestore(snap.id))}
|
|
154
|
-
title={confirming.action === 'delete' ? 'Confirm delete' : 'Confirm restore'}
|
|
155
145
|
disabled={restoringId !== null}
|
|
156
146
|
>
|
|
157
147
|
{confirming.action === 'delete' ? 'Delete' : restoringId === snap.id ? 'Restoring…' : 'Confirm'}
|
|
@@ -160,7 +150,6 @@ export function SnapshotPanel({
|
|
|
160
150
|
type="button"
|
|
161
151
|
class="snapshot-action-btn"
|
|
162
152
|
onClick={() => setConfirming(null)}
|
|
163
|
-
title="Cancel"
|
|
164
153
|
disabled={restoringId !== null}
|
|
165
154
|
>
|
|
166
155
|
Cancel
|
|
@@ -168,29 +157,31 @@ export function SnapshotPanel({
|
|
|
168
157
|
</>
|
|
169
158
|
) : (
|
|
170
159
|
<>
|
|
171
|
-
<
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
title={
|
|
176
|
-
isHostedWorkbench()
|
|
177
|
-
? 'Restore this snapshot — replaces the current canvas'
|
|
178
|
-
: 'Restore this snapshot — replaces the current canvas (undoable)'
|
|
179
|
-
}
|
|
180
|
-
disabled={restoringId !== null}
|
|
160
|
+
<BarHint
|
|
161
|
+
label="Restore this snapshot"
|
|
162
|
+
body={isHostedWorkbench() ? 'Replaces the current canvas.' : 'Replaces the current canvas (undoable).'}
|
|
163
|
+
align="end"
|
|
181
164
|
>
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
>
|
|
192
|
-
<
|
|
193
|
-
|
|
165
|
+
<button
|
|
166
|
+
type="button"
|
|
167
|
+
class="snapshot-action-btn snapshot-action-restore"
|
|
168
|
+
onClick={() => setConfirming({ id: snap.id, action: 'restore' })}
|
|
169
|
+
disabled={restoringId !== null}
|
|
170
|
+
>
|
|
171
|
+
{restoringId === snap.id ? 'Restoring…' : restoreLabel}
|
|
172
|
+
</button>
|
|
173
|
+
</BarHint>
|
|
174
|
+
<BarHint label="Delete this snapshot" align="end">
|
|
175
|
+
<button
|
|
176
|
+
type="button"
|
|
177
|
+
class="snapshot-action-btn snapshot-action-delete"
|
|
178
|
+
onClick={() => setConfirming({ id: snap.id, action: 'delete' })}
|
|
179
|
+
aria-label="Delete snapshot"
|
|
180
|
+
disabled={restoringId !== null}
|
|
181
|
+
>
|
|
182
|
+
<IconClose size={14} />
|
|
183
|
+
</button>
|
|
184
|
+
</BarHint>
|
|
194
185
|
</>
|
|
195
186
|
);
|
|
196
187
|
|
|
@@ -200,7 +191,7 @@ export function SnapshotPanel({
|
|
|
200
191
|
<span class="snapshot-panel-title">History</span>
|
|
201
192
|
<span class="snapshot-panel-sub">snapshots + sessions</span>
|
|
202
193
|
<span class="snapshot-panel-spacer" />
|
|
203
|
-
<button type="button" class="snapshot-panel-close" onClick={onClose}
|
|
194
|
+
<button type="button" class="snapshot-panel-close" onClick={onClose} aria-label="Close history">
|
|
204
195
|
<IconClose />
|
|
205
196
|
</button>
|
|
206
197
|
</div>
|