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
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import type { Lifecycle } from '../../server/knowledge-lifecycle.js';
|
|
2
|
+
import { ChoiceMenu } from './ChoiceMenu';
|
|
3
|
+
|
|
4
|
+
export const DEFAULT_LIFECYCLE: Lifecycle = {
|
|
5
|
+
decayType: 'reality',
|
|
6
|
+
rate: 'medium',
|
|
7
|
+
cadenceDays: 90,
|
|
8
|
+
triggers: [],
|
|
9
|
+
revalidationMethod: '',
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export function LifecycleFields({ value, onChange }: { value: Lifecycle; onChange: (value: Lifecycle) => void }) {
|
|
13
|
+
return (
|
|
14
|
+
<div class="lifecycle-fields">
|
|
15
|
+
<div class="knowledge-field">
|
|
16
|
+
<span>Why review?</span>
|
|
17
|
+
<ChoiceMenu
|
|
18
|
+
label="Why review?"
|
|
19
|
+
value={value.decayType}
|
|
20
|
+
onChange={(decayType) => onChange({ ...value, decayType: decayType as Lifecycle['decayType'] })}
|
|
21
|
+
options={[
|
|
22
|
+
{ value: 'reality', label: 'Reality · facts change' },
|
|
23
|
+
{ value: 'decision', label: 'Decision · superseded' },
|
|
24
|
+
{ value: 'dependency', label: 'Dependency · prerequisites change' },
|
|
25
|
+
{ value: 'relevance', label: 'Relevance · task changes' },
|
|
26
|
+
]}
|
|
27
|
+
/>
|
|
28
|
+
</div>
|
|
29
|
+
<div class="knowledge-field">
|
|
30
|
+
<span>Review frequency</span>
|
|
31
|
+
<ChoiceMenu
|
|
32
|
+
label="Review frequency"
|
|
33
|
+
value={value.rate}
|
|
34
|
+
onChange={(rate) => onChange({ ...value, rate: rate as Lifecycle['rate'] })}
|
|
35
|
+
options={[
|
|
36
|
+
{ value: 'fast', label: 'Fast · at use' },
|
|
37
|
+
{ value: 'medium', label: 'Medium · on a cadence' },
|
|
38
|
+
{ value: 'slow', label: 'Slow · on resume' },
|
|
39
|
+
{ value: 'durable', label: 'Durable · no passive expiry' },
|
|
40
|
+
]}
|
|
41
|
+
/>
|
|
42
|
+
</div>
|
|
43
|
+
{value.rate === 'medium' && (
|
|
44
|
+
<label>
|
|
45
|
+
Cadence in days
|
|
46
|
+
<input
|
|
47
|
+
type="number"
|
|
48
|
+
min="1"
|
|
49
|
+
max="36500"
|
|
50
|
+
required
|
|
51
|
+
value={value.cadenceDays ?? ''}
|
|
52
|
+
onInput={(event) =>
|
|
53
|
+
onChange({
|
|
54
|
+
...value,
|
|
55
|
+
cadenceDays: event.currentTarget.value ? Number(event.currentTarget.value) : undefined,
|
|
56
|
+
})
|
|
57
|
+
}
|
|
58
|
+
/>
|
|
59
|
+
</label>
|
|
60
|
+
)}
|
|
61
|
+
<details class="lifecycle-triggers">
|
|
62
|
+
<summary>Additional review triggers</summary>
|
|
63
|
+
<label>
|
|
64
|
+
Also review when · one trigger per line
|
|
65
|
+
<textarea
|
|
66
|
+
value={value.triggers.join('\n')}
|
|
67
|
+
onInput={(event) => onChange({ ...value, triggers: event.currentTarget.value.split('\n') })}
|
|
68
|
+
/>
|
|
69
|
+
</label>
|
|
70
|
+
<p>
|
|
71
|
+
Source changes, contradictions and superseding decisions are observed through explicit relations. Other
|
|
72
|
+
triggers describe what the reviewer should check.
|
|
73
|
+
</p>
|
|
74
|
+
</details>
|
|
75
|
+
<label>
|
|
76
|
+
How to check
|
|
77
|
+
<input
|
|
78
|
+
required
|
|
79
|
+
pattern={'.*\\S.*'}
|
|
80
|
+
value={value.revalidationMethod}
|
|
81
|
+
onInput={(event) => onChange({ ...value, revalidationMethod: event.currentTarget.value })}
|
|
82
|
+
/>
|
|
83
|
+
</label>
|
|
84
|
+
</div>
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export const cleanLifecycle = (value: Lifecycle): Lifecycle => ({
|
|
89
|
+
...value,
|
|
90
|
+
triggers: value.triggers.map((s) => s.trim()).filter(Boolean),
|
|
91
|
+
});
|
|
@@ -0,0 +1,533 @@
|
|
|
1
|
+
import { useEffect, useLayoutEffect, useRef, useState } from 'preact/hooks';
|
|
2
|
+
import { knowledgeLifecycleEnabled } from '../state/workspace-settings';
|
|
3
|
+
import type { Lifecycle, LifecycleResults, ReviewRecord } from '../../server/knowledge-lifecycle.js';
|
|
4
|
+
import {
|
|
5
|
+
activeBoardId,
|
|
6
|
+
boardIncarnation,
|
|
7
|
+
browsingMemory,
|
|
8
|
+
lifecycleUiGeneration,
|
|
9
|
+
openMemoryTarget,
|
|
10
|
+
} from '../state/boards-store';
|
|
11
|
+
import { focusNode, nodes } from '../state/canvas-store';
|
|
12
|
+
import { requestJson } from '../state/intent-bridge';
|
|
13
|
+
import {
|
|
14
|
+
cardQualification,
|
|
15
|
+
knowledgeUnavailable,
|
|
16
|
+
lifecycleTarget,
|
|
17
|
+
refreshKnowledge,
|
|
18
|
+
retirementUndo,
|
|
19
|
+
} from '../state/knowledge-store';
|
|
20
|
+
import { sendSteering } from '../state/session-store';
|
|
21
|
+
import { hasUnsavedMarkdown } from '../nodes/MarkdownNode';
|
|
22
|
+
import { cleanLifecycle, DEFAULT_LIFECYCLE, LifecycleFields } from './LifecycleFields';
|
|
23
|
+
|
|
24
|
+
export function LifecyclePanel() {
|
|
25
|
+
const target = lifecycleTarget.value;
|
|
26
|
+
if (
|
|
27
|
+
!target ||
|
|
28
|
+
browsingMemory.value ||
|
|
29
|
+
activeBoardId.value !== target.boardId ||
|
|
30
|
+
boardIncarnation !== target.incarnation ||
|
|
31
|
+
!nodes.value.has(target.nodeId)
|
|
32
|
+
)
|
|
33
|
+
return null;
|
|
34
|
+
return <LifecycleDialog key={`${target.boardId}:${target.nodeId}:${target.incarnation}`} target={target} />;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function LifecycleDialog({ target }: { target: NonNullable<typeof lifecycleTarget.value> }) {
|
|
38
|
+
const enabled = knowledgeLifecycleEnabled.value;
|
|
39
|
+
const ref = useRef<HTMLDivElement>(null);
|
|
40
|
+
const loadSequence = useRef(0);
|
|
41
|
+
const [detail, setDetail] = useState<LifecycleResults['get'] | null>(null);
|
|
42
|
+
const [override, setOverride] = useState(false);
|
|
43
|
+
const [policy, setPolicy] = useState<Lifecycle>(DEFAULT_LIFECYCLE);
|
|
44
|
+
const [outcome, setOutcome] = useState<Exclude<ReviewRecord['outcome'], 'restore'>>('retain');
|
|
45
|
+
const [evidence, setEvidence] = useState('');
|
|
46
|
+
const [applied, setApplied] = useState(false);
|
|
47
|
+
const [busy, setBusy] = useState(false);
|
|
48
|
+
const [error, setError] = useState('');
|
|
49
|
+
const [retiredHere, setRetiredHere] = useState(false);
|
|
50
|
+
const [needsReload, setNeedsReload] = useState(false);
|
|
51
|
+
const node = nodes.value.get(target.nodeId);
|
|
52
|
+
const unsavedContent = hasUnsavedMarkdown(target.boardId, target.nodeId);
|
|
53
|
+
const current = cardQualification(target.nodeId) ?? detail?.qualification;
|
|
54
|
+
const path = `/api/canvas/node/${encodeURIComponent(target.nodeId)}`;
|
|
55
|
+
const sameBoard = () => activeBoardId.value === target.boardId && boardIncarnation === target.incarnation;
|
|
56
|
+
const ownsDialog = () => knowledgeLifecycleEnabled.value && sameBoard() && lifecycleTarget.value === target;
|
|
57
|
+
const load = async () => {
|
|
58
|
+
if (!ownsDialog()) return;
|
|
59
|
+
const generation = lifecycleUiGeneration;
|
|
60
|
+
const sequence = ++loadSequence.current;
|
|
61
|
+
const result = await requestJson<LifecycleResults['get'] | null>(
|
|
62
|
+
'lifecycle',
|
|
63
|
+
`${path}/lifecycle?board=${encodeURIComponent(target.boardId)}`,
|
|
64
|
+
null,
|
|
65
|
+
);
|
|
66
|
+
if (!ownsDialog() || generation !== lifecycleUiGeneration || sequence !== loadSequence.current) return;
|
|
67
|
+
if (result?.ok !== true) {
|
|
68
|
+
setError('Could not load lifecycle. Retry before reviewing.');
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
setDetail(result);
|
|
72
|
+
setNeedsReload(false);
|
|
73
|
+
setError('');
|
|
74
|
+
setOverride(result.lifecycleSource === 'card');
|
|
75
|
+
setPolicy(result.lifecycle ?? DEFAULT_LIFECYCLE);
|
|
76
|
+
// New preconditions must never be attached to evidence for an earlier observation.
|
|
77
|
+
setEvidence('');
|
|
78
|
+
setApplied(false);
|
|
79
|
+
};
|
|
80
|
+
useEffect(() => {
|
|
81
|
+
const previous = document.activeElement;
|
|
82
|
+
const dock = ref.current;
|
|
83
|
+
if (knowledgeLifecycleEnabled.value) {
|
|
84
|
+
dock?.querySelector<HTMLElement>('button')?.focus({ preventScroll: true });
|
|
85
|
+
// The dock has committed its new geometry before focusing the real card.
|
|
86
|
+
focusNode(target.nodeId, { recordHistory: false });
|
|
87
|
+
}
|
|
88
|
+
return () => {
|
|
89
|
+
if (
|
|
90
|
+
dock?.contains(document.activeElement) &&
|
|
91
|
+
sameBoard() &&
|
|
92
|
+
previous instanceof HTMLElement &&
|
|
93
|
+
previous.isConnected
|
|
94
|
+
)
|
|
95
|
+
previous.focus({ preventScroll: true });
|
|
96
|
+
};
|
|
97
|
+
}, []);
|
|
98
|
+
useLayoutEffect(() => {
|
|
99
|
+
if (!enabled && ref.current?.contains(document.activeElement))
|
|
100
|
+
document.querySelector<HTMLElement>('button[aria-label="Settings"]')?.focus({ preventScroll: true });
|
|
101
|
+
}, [enabled]);
|
|
102
|
+
useEffect(() => {
|
|
103
|
+
if (!enabled) return;
|
|
104
|
+
// Refresh qualification, not the observation to which the existing draft refers.
|
|
105
|
+
void refreshKnowledge();
|
|
106
|
+
if (!detail || needsReload) void load();
|
|
107
|
+
}, [enabled]);
|
|
108
|
+
const mutate = async (suffix: string, body: Record<string, unknown>) => {
|
|
109
|
+
if (!ownsDialog() || busy || needsReload) return false;
|
|
110
|
+
setBusy(true);
|
|
111
|
+
setError('');
|
|
112
|
+
const result = await requestJson<{ ok: boolean } | null>('lifecycleWrite', `${path}${suffix}`, null, {
|
|
113
|
+
method: 'POST',
|
|
114
|
+
headers: { 'Content-Type': 'application/json' },
|
|
115
|
+
body: JSON.stringify({ ...body, expectedBoardId: target.boardId }),
|
|
116
|
+
});
|
|
117
|
+
if (!sameBoard() || lifecycleTarget.value !== target) {
|
|
118
|
+
setBusy(false);
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
if (!result?.ok) {
|
|
122
|
+
setBusy(false);
|
|
123
|
+
setError(
|
|
124
|
+
'Not saved. The card or its sources may have changed. Reload and check the current evidence before retrying.',
|
|
125
|
+
);
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
// Invalidate acknowledged preconditions without unmounting focused controls.
|
|
129
|
+
// If suspended, resume reloads before another mutation is allowed.
|
|
130
|
+
setNeedsReload(true);
|
|
131
|
+
await load();
|
|
132
|
+
await refreshKnowledge();
|
|
133
|
+
setBusy(false);
|
|
134
|
+
return sameBoard() && lifecycleTarget.value === target;
|
|
135
|
+
};
|
|
136
|
+
const recordReview = async (reviewOutcome: typeof outcome, wasApplied: boolean) => {
|
|
137
|
+
if (!detail) return false;
|
|
138
|
+
if (
|
|
139
|
+
wasApplied &&
|
|
140
|
+
['retain', 'revise', 'externalize'].includes(reviewOutcome) &&
|
|
141
|
+
(!evidence.trim() ||
|
|
142
|
+
hasUnsavedMarkdown(target.boardId, target.nodeId) ||
|
|
143
|
+
node?.contentRevision !== detail.contentRevision ||
|
|
144
|
+
error)
|
|
145
|
+
)
|
|
146
|
+
return false;
|
|
147
|
+
const ok = await mutate('/review', {
|
|
148
|
+
outcome: reviewOutcome,
|
|
149
|
+
evidence,
|
|
150
|
+
applied: wasApplied,
|
|
151
|
+
expectedContentRevision: detail.contentRevision,
|
|
152
|
+
expectedSources: detail.qualification.observations,
|
|
153
|
+
});
|
|
154
|
+
if (ok) {
|
|
155
|
+
setRetiredHere(wasApplied && reviewOutcome === 'retire');
|
|
156
|
+
if (wasApplied && reviewOutcome === 'retire') retirementUndo.value = target;
|
|
157
|
+
setEvidence('');
|
|
158
|
+
setApplied(false);
|
|
159
|
+
}
|
|
160
|
+
return ok;
|
|
161
|
+
};
|
|
162
|
+
const recipe = node?.data.provenance as { recipe?: { tool: string; input?: unknown; prompt: string } } | undefined;
|
|
163
|
+
const date = (value: string | null | undefined) => (value ? new Date(value).toLocaleString() : 'Unknown');
|
|
164
|
+
return (
|
|
165
|
+
<div
|
|
166
|
+
class="lifecycle-dock"
|
|
167
|
+
hidden={!enabled}
|
|
168
|
+
style={enabled ? undefined : { display: 'none' }}
|
|
169
|
+
onKeyDown={(event) => {
|
|
170
|
+
if (!ownsDialog()) return;
|
|
171
|
+
// This dialog owns its keys, including Space on the disclosure summary.
|
|
172
|
+
event.stopPropagation();
|
|
173
|
+
if (event.key === 'Escape') {
|
|
174
|
+
event.preventDefault();
|
|
175
|
+
lifecycleTarget.value = null;
|
|
176
|
+
}
|
|
177
|
+
}}
|
|
178
|
+
>
|
|
179
|
+
<div ref={ref} class="knowledge-dialog lifecycle-dialog" role="dialog" aria-label="Review card">
|
|
180
|
+
<header>
|
|
181
|
+
<h2>Review card · {String(node?.data.title ?? 'Card')}</h2>
|
|
182
|
+
<button
|
|
183
|
+
type="button"
|
|
184
|
+
aria-label="Close review"
|
|
185
|
+
onClick={() => {
|
|
186
|
+
if (!ownsDialog()) return;
|
|
187
|
+
lifecycleTarget.value = null;
|
|
188
|
+
}}
|
|
189
|
+
>
|
|
190
|
+
×
|
|
191
|
+
</button>
|
|
192
|
+
</header>
|
|
193
|
+
{error && <p role="alert">{error}</p>}
|
|
194
|
+
{!detail && error && (
|
|
195
|
+
<button
|
|
196
|
+
type="button"
|
|
197
|
+
aria-disabled={busy}
|
|
198
|
+
onClick={() => {
|
|
199
|
+
if (!busy) void load();
|
|
200
|
+
}}
|
|
201
|
+
>
|
|
202
|
+
Reload and recheck
|
|
203
|
+
</button>
|
|
204
|
+
)}
|
|
205
|
+
{knowledgeUnavailable.value && (
|
|
206
|
+
<p role="status">Live qualification unavailable. Last loaded evidence is shown.</p>
|
|
207
|
+
)}
|
|
208
|
+
{!detail ? (
|
|
209
|
+
<p>Loading review…</p>
|
|
210
|
+
) : (
|
|
211
|
+
<>
|
|
212
|
+
{detail.retired ? (
|
|
213
|
+
<div class="lifecycle-review">
|
|
214
|
+
<p>This card is retired. Restoring it does not mark it checked.</p>
|
|
215
|
+
<button
|
|
216
|
+
type="button"
|
|
217
|
+
aria-disabled={busy || needsReload}
|
|
218
|
+
onClick={() => void mutate('/lifecycle/restore', { evidence: 'Restored by the human reviewer.' })}
|
|
219
|
+
>
|
|
220
|
+
Restore card
|
|
221
|
+
</button>
|
|
222
|
+
</div>
|
|
223
|
+
) : (
|
|
224
|
+
<>
|
|
225
|
+
<h3>Why review?</h3>
|
|
226
|
+
{current?.due ? (
|
|
227
|
+
<p class="lifecycle-warning">
|
|
228
|
+
⧗{' '}
|
|
229
|
+
{current.reasons.some((reason) => reason.startsWith('Content changed'))
|
|
230
|
+
? 'This card changed since its last check.'
|
|
231
|
+
: 'This card needs a fresh check.'}
|
|
232
|
+
</p>
|
|
233
|
+
) : (
|
|
234
|
+
<p>Last checked: {date(detail.lastValidation?.when)}</p>
|
|
235
|
+
)}
|
|
236
|
+
{current?.reasons.length ? <p>{current.reasons.join(' ')}</p> : null}
|
|
237
|
+
{(current?.sources ?? detail.qualification.sources).map((source) => (
|
|
238
|
+
<div class="lifecycle-warning" key={`${source.boardId}:${source.cardId}`}>
|
|
239
|
+
⧗ Source needs a check: {source.title}
|
|
240
|
+
{!source.unavailable && (
|
|
241
|
+
<button
|
|
242
|
+
type="button"
|
|
243
|
+
onClick={() => {
|
|
244
|
+
if (!ownsDialog()) return;
|
|
245
|
+
lifecycleTarget.value = null;
|
|
246
|
+
void openMemoryTarget(source);
|
|
247
|
+
}}
|
|
248
|
+
>
|
|
249
|
+
Open source →
|
|
250
|
+
</button>
|
|
251
|
+
)}
|
|
252
|
+
</div>
|
|
253
|
+
))}
|
|
254
|
+
<form
|
|
255
|
+
class="lifecycle-quick-review"
|
|
256
|
+
noValidate
|
|
257
|
+
onSubmit={(event) => {
|
|
258
|
+
event.preventDefault();
|
|
259
|
+
void recordReview('retain', true).then((ok) => {
|
|
260
|
+
if (ok && ownsDialog()) lifecycleTarget.value = null;
|
|
261
|
+
});
|
|
262
|
+
}}
|
|
263
|
+
>
|
|
264
|
+
<p>Check the card and its sources before confirming.</p>
|
|
265
|
+
{unsavedContent && (
|
|
266
|
+
<p role="status">Save the card's edits, then reload and recheck before validating.</p>
|
|
267
|
+
)}
|
|
268
|
+
<label>
|
|
269
|
+
What did you check?
|
|
270
|
+
<textarea
|
|
271
|
+
required
|
|
272
|
+
rows={2}
|
|
273
|
+
placeholder="A short note or source link"
|
|
274
|
+
value={evidence}
|
|
275
|
+
onInput={(event) => setEvidence(event.currentTarget.value)}
|
|
276
|
+
/>
|
|
277
|
+
</label>
|
|
278
|
+
<div class="lifecycle-review-actions">
|
|
279
|
+
<button
|
|
280
|
+
type="submit"
|
|
281
|
+
aria-disabled={
|
|
282
|
+
busy ||
|
|
283
|
+
needsReload ||
|
|
284
|
+
!!error ||
|
|
285
|
+
unsavedContent ||
|
|
286
|
+
!evidence.trim() ||
|
|
287
|
+
node?.contentRevision !== detail.contentRevision
|
|
288
|
+
}
|
|
289
|
+
>
|
|
290
|
+
Checked, still correct
|
|
291
|
+
</button>
|
|
292
|
+
<button
|
|
293
|
+
type="button"
|
|
294
|
+
aria-disabled={busy}
|
|
295
|
+
onClick={() => {
|
|
296
|
+
if (!ownsDialog() || busy) return;
|
|
297
|
+
lifecycleTarget.value = null;
|
|
298
|
+
}}
|
|
299
|
+
>
|
|
300
|
+
Not now
|
|
301
|
+
</button>
|
|
302
|
+
</div>
|
|
303
|
+
</form>
|
|
304
|
+
</>
|
|
305
|
+
)}
|
|
306
|
+
{(error || node?.contentRevision !== detail.contentRevision) && (
|
|
307
|
+
<div role="status">
|
|
308
|
+
<p>Reload, check the current card and sources, then write a fresh note.</p>
|
|
309
|
+
<button
|
|
310
|
+
type="button"
|
|
311
|
+
aria-disabled={busy}
|
|
312
|
+
onClick={() => {
|
|
313
|
+
if (!busy) void load();
|
|
314
|
+
}}
|
|
315
|
+
>
|
|
316
|
+
Reload and recheck
|
|
317
|
+
</button>
|
|
318
|
+
</div>
|
|
319
|
+
)}
|
|
320
|
+
<details class="lifecycle-advanced">
|
|
321
|
+
<summary>Settings and history</summary>
|
|
322
|
+
<button
|
|
323
|
+
type="button"
|
|
324
|
+
aria-disabled={busy}
|
|
325
|
+
onClick={() => {
|
|
326
|
+
if (!busy) void load();
|
|
327
|
+
}}
|
|
328
|
+
>
|
|
329
|
+
Reload lifecycle and source revisions
|
|
330
|
+
</button>
|
|
331
|
+
<p class="lifecycle-category">
|
|
332
|
+
{detail.category
|
|
333
|
+
? `${detail.category.glyph} ${detail.category.name} · from ${detail.categorySourceFolder}`
|
|
334
|
+
: 'No inherited category'}{' '}
|
|
335
|
+
· category belongs to the folder
|
|
336
|
+
</p>
|
|
337
|
+
<form
|
|
338
|
+
onSubmit={(event) => {
|
|
339
|
+
event.preventDefault();
|
|
340
|
+
void mutate('/lifecycle', { lifecycleOverride: override ? cleanLifecycle(policy) : null });
|
|
341
|
+
}}
|
|
342
|
+
>
|
|
343
|
+
<label>
|
|
344
|
+
<input
|
|
345
|
+
type="checkbox"
|
|
346
|
+
checked={override}
|
|
347
|
+
onChange={(event) => setOverride(event.currentTarget.checked)}
|
|
348
|
+
/>{' '}
|
|
349
|
+
Set a card lifecycle override
|
|
350
|
+
</label>
|
|
351
|
+
{override ? (
|
|
352
|
+
<LifecycleFields value={policy} onChange={setPolicy} />
|
|
353
|
+
) : (
|
|
354
|
+
<p>
|
|
355
|
+
Inherit from {detail.lifecycleSourceFolder ?? 'the folder'} · {detail.lifecycle?.rate ?? 'unknown'}
|
|
356
|
+
</p>
|
|
357
|
+
)}
|
|
358
|
+
<button type="submit" aria-disabled={busy || needsReload}>
|
|
359
|
+
Save lifecycle
|
|
360
|
+
</button>
|
|
361
|
+
</form>
|
|
362
|
+
<div class="lifecycle-clocks">
|
|
363
|
+
<div class="is-read">
|
|
364
|
+
<span>Read by agent</span>
|
|
365
|
+
<strong>{date(detail.qualification.lastReadAt)}</strong>
|
|
366
|
+
<small>Delivery only, not validation</small>
|
|
367
|
+
</div>
|
|
368
|
+
<div>
|
|
369
|
+
<span>Content changed</span>
|
|
370
|
+
<strong>{date(detail.qualification.contentChangedAt)}</strong>
|
|
371
|
+
<small>Revision {detail.contentRevision}</small>
|
|
372
|
+
</div>
|
|
373
|
+
<div>
|
|
374
|
+
<span>Last validated</span>
|
|
375
|
+
<strong>{date(detail.lastValidation?.when)}</strong>
|
|
376
|
+
<small>{detail.lastValidation?.evidence ?? 'No evidence-backed validation'}</small>
|
|
377
|
+
</div>
|
|
378
|
+
<div>
|
|
379
|
+
<span>Task relevance</span>
|
|
380
|
+
<strong>
|
|
381
|
+
{detail.qualification.relevance === 'card-pinned'
|
|
382
|
+
? 'Card pinned'
|
|
383
|
+
: detail.qualification.relevance === 'board-pinned'
|
|
384
|
+
? 'Board pinned'
|
|
385
|
+
: 'Not explicitly selected'}
|
|
386
|
+
</strong>
|
|
387
|
+
<small>Curation, not a truth claim</small>
|
|
388
|
+
</div>
|
|
389
|
+
</div>
|
|
390
|
+
<p>Reading, refreshing and regeneration never count as validating.</p>
|
|
391
|
+
{current?.due && <p class="lifecycle-warning">⧗ review due · {current.reasons.join(' ')}</p>}
|
|
392
|
+
{(current?.sources ?? detail.qualification.sources).map((source) => (
|
|
393
|
+
<div class="lifecycle-warning" key={`${source.boardId}:${source.cardId}`}>
|
|
394
|
+
⧗ {source.title} · {source.reasons.join(' ')}
|
|
395
|
+
{source.observedAt && <span> · observed {date(source.observedAt)}</span>}
|
|
396
|
+
{source.observedRevision !== undefined && <span> · source revision {source.observedRevision}</span>}
|
|
397
|
+
{!source.unavailable && (
|
|
398
|
+
<button
|
|
399
|
+
type="button"
|
|
400
|
+
onClick={() => {
|
|
401
|
+
if (!ownsDialog()) return;
|
|
402
|
+
lifecycleTarget.value = null;
|
|
403
|
+
void openMemoryTarget(source);
|
|
404
|
+
}}
|
|
405
|
+
>
|
|
406
|
+
Open source →
|
|
407
|
+
</button>
|
|
408
|
+
)}
|
|
409
|
+
</div>
|
|
410
|
+
))}
|
|
411
|
+
{node?.contentRevision !== detail.contentRevision && (
|
|
412
|
+
<p role="alert">Content changed while this review was open. Reload and check it before applying.</p>
|
|
413
|
+
)}
|
|
414
|
+
{detail.retired ? (
|
|
415
|
+
<div class="lifecycle-review">
|
|
416
|
+
<p role="status">Retired from active context. Content and history remain.</p>
|
|
417
|
+
<label>
|
|
418
|
+
Restoration note
|
|
419
|
+
<input value={evidence} onInput={(event) => setEvidence(event.currentTarget.value)} />
|
|
420
|
+
</label>
|
|
421
|
+
<button
|
|
422
|
+
type="button"
|
|
423
|
+
aria-disabled={busy || needsReload}
|
|
424
|
+
onClick={() =>
|
|
425
|
+
void mutate('/lifecycle/restore', {
|
|
426
|
+
evidence: evidence || 'Restored by the human reviewer.',
|
|
427
|
+
}).then((ok) => {
|
|
428
|
+
if (ok) setRetiredHere(false);
|
|
429
|
+
})
|
|
430
|
+
}
|
|
431
|
+
>
|
|
432
|
+
{retiredHere ? 'Undo retirement' : 'Restore to active context'}
|
|
433
|
+
</button>
|
|
434
|
+
<p>Restore is recorded and does not validate.</p>
|
|
435
|
+
</div>
|
|
436
|
+
) : (
|
|
437
|
+
<form
|
|
438
|
+
class="lifecycle-review"
|
|
439
|
+
onSubmit={(event) => {
|
|
440
|
+
event.preventDefault();
|
|
441
|
+
void recordReview(outcome, applied);
|
|
442
|
+
}}
|
|
443
|
+
>
|
|
444
|
+
<label>
|
|
445
|
+
Outcome
|
|
446
|
+
<select
|
|
447
|
+
value={outcome}
|
|
448
|
+
onChange={(event) => setOutcome(event.currentTarget.value as typeof outcome)}
|
|
449
|
+
>
|
|
450
|
+
<option value="retain">Retain</option>
|
|
451
|
+
<option value="revise">Revise</option>
|
|
452
|
+
<option value="externalize">Externalize</option>
|
|
453
|
+
<option value="retire">Retire from active context</option>
|
|
454
|
+
<option value="unresolved">Unresolved</option>
|
|
455
|
+
</select>
|
|
456
|
+
</label>
|
|
457
|
+
<p>Uses the check note above. Applied retain, revise and externalize require evidence.</p>
|
|
458
|
+
<label>
|
|
459
|
+
<input
|
|
460
|
+
type="checkbox"
|
|
461
|
+
checked={applied}
|
|
462
|
+
onChange={(event) => setApplied(event.currentTarget.checked)}
|
|
463
|
+
/>{' '}
|
|
464
|
+
I applied this outcome and checked the content and sources
|
|
465
|
+
</label>
|
|
466
|
+
<p>Revise and externalize record work already performed. They do not edit or execute anything.</p>
|
|
467
|
+
<button
|
|
468
|
+
type="submit"
|
|
469
|
+
aria-disabled={
|
|
470
|
+
busy ||
|
|
471
|
+
needsReload ||
|
|
472
|
+
(applied &&
|
|
473
|
+
['retain', 'revise', 'externalize'].includes(outcome) &&
|
|
474
|
+
(!evidence.trim() ||
|
|
475
|
+
unsavedContent ||
|
|
476
|
+
!!error ||
|
|
477
|
+
node?.contentRevision !== detail.contentRevision))
|
|
478
|
+
}
|
|
479
|
+
>
|
|
480
|
+
Record review
|
|
481
|
+
</button>
|
|
482
|
+
</form>
|
|
483
|
+
)}
|
|
484
|
+
{recipe?.recipe && (
|
|
485
|
+
<section>
|
|
486
|
+
<h3>Recipe</h3>
|
|
487
|
+
<p>
|
|
488
|
+
{recipe.recipe.tool} · {recipe.recipe.prompt}
|
|
489
|
+
</p>
|
|
490
|
+
<button
|
|
491
|
+
type="button"
|
|
492
|
+
aria-disabled={busy}
|
|
493
|
+
onClick={async () => {
|
|
494
|
+
if (!ownsDialog() || busy) return;
|
|
495
|
+
setBusy(true);
|
|
496
|
+
const ok = await sendSteering(
|
|
497
|
+
`Refresh this board using the recorded recipe for ${target.boardId}/${target.nodeId}. Review the recipe before running it. Tool: ${recipe.recipe!.tool}\nInput: ${JSON.stringify(recipe.recipe!.input ?? null)}\nPrompt: ${recipe.recipe!.prompt}\nRegeneration is not validation. Preserve source observations and report evidence separately.`,
|
|
498
|
+
);
|
|
499
|
+
setBusy(false);
|
|
500
|
+
setError(
|
|
501
|
+
ok
|
|
502
|
+
? 'Recipe request sent. No tool was executed by Canvas.'
|
|
503
|
+
: 'Could not send the request. No tool was executed.',
|
|
504
|
+
);
|
|
505
|
+
}}
|
|
506
|
+
>
|
|
507
|
+
Ask agent to refresh this board
|
|
508
|
+
</button>
|
|
509
|
+
</section>
|
|
510
|
+
)}
|
|
511
|
+
<section>
|
|
512
|
+
<h3>Review history</h3>
|
|
513
|
+
{detail.records.length === 0 && <p>No reviews recorded.</p>}
|
|
514
|
+
{[...detail.records].reverse().map((record) => (
|
|
515
|
+
<div class="lifecycle-history" key={record.id}>
|
|
516
|
+
<strong>
|
|
517
|
+
{record.outcome}
|
|
518
|
+
{!record.applied ? ' · not applied' : ''}
|
|
519
|
+
</strong>
|
|
520
|
+
<span>
|
|
521
|
+
{date(record.when)} · {record.who.actor} · {record.who.source}
|
|
522
|
+
</span>
|
|
523
|
+
<p>{record.evidence || 'No evidence'}</p>
|
|
524
|
+
</div>
|
|
525
|
+
))}
|
|
526
|
+
</section>
|
|
527
|
+
</details>
|
|
528
|
+
</>
|
|
529
|
+
)}
|
|
530
|
+
</div>
|
|
531
|
+
</div>
|
|
532
|
+
);
|
|
533
|
+
}
|