@ltm-blueverse/alpha-semantic-hub 0.7.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.
Files changed (146) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1674 -0
  3. package/explorer/README.md +282 -0
  4. package/explorer/src/App.css +0 -0
  5. package/explorer/src/App.tsx +2035 -0
  6. package/explorer/src/ErrorBoundary.tsx +112 -0
  7. package/explorer/src/ExploreWorkspaceTabs.tsx +29 -0
  8. package/explorer/src/assets/hero.png +0 -0
  9. package/explorer/src/assets/react.svg +1 -0
  10. package/explorer/src/assets/vite.svg +1 -0
  11. package/explorer/src/explorerCapabilities.ts +24 -0
  12. package/explorer/src/index.css +71 -0
  13. package/explorer/src/main.tsx +7 -0
  14. package/explorer/src/store/edgePairKeys.d.ts +2 -0
  15. package/explorer/src/store/edgePairKeys.js +7 -0
  16. package/explorer/src/store/graphStore.ts +183 -0
  17. package/explorer/src/store/registryStore.ts +78 -0
  18. package/explorer/src/types.d.ts +16 -0
  19. package/explorer/src/ui/primitives.css +414 -0
  20. package/explorer/src/ui/primitives.tsx +170 -0
  21. package/explorer/src/workspaces/DecisionWorkspace/DecisionWorkspace.tsx +279 -0
  22. package/explorer/src/workspaces/DiffMergeWorkspace/DiffMergeWorkspace.tsx +121 -0
  23. package/explorer/src/workspaces/EnrichWorkspace/EntityResolutionTab.tsx +454 -0
  24. package/explorer/src/workspaces/EnrichWorkspace/RegistryTab.tsx +285 -0
  25. package/explorer/src/workspaces/GraphWorkspace/GraphCanvas.tsx +2673 -0
  26. package/explorer/src/workspaces/GraphWorkspace/GraphInspectorPanel.tsx +771 -0
  27. package/explorer/src/workspaces/GraphWorkspace/GraphLoadingOverlay.tsx +410 -0
  28. package/explorer/src/workspaces/GraphWorkspace/GraphWorkspace.tsx +3584 -0
  29. package/explorer/src/workspaces/GraphWorkspace/MarkdownContentViewer.tsx +705 -0
  30. package/explorer/src/workspaces/GraphWorkspace/SigmaSceneAdapter.tsx +55 -0
  31. package/explorer/src/workspaces/GraphWorkspace/TimelinePanel.tsx +193 -0
  32. package/explorer/src/workspaces/GraphWorkspace/behaviors/clickSelectionBehavior.ts +29 -0
  33. package/explorer/src/workspaces/GraphWorkspace/behaviors/fitViewBehavior.ts +15 -0
  34. package/explorer/src/workspaces/GraphWorkspace/behaviors/focusCameraBehavior.ts +25 -0
  35. package/explorer/src/workspaces/GraphWorkspace/behaviors/hoverActivationBehavior.ts +15 -0
  36. package/explorer/src/workspaces/GraphWorkspace/behaviors/pathHighlightBehavior.ts +52 -0
  37. package/explorer/src/workspaces/GraphWorkspace/behaviors/searchFocusBehavior.ts +36 -0
  38. package/explorer/src/workspaces/GraphWorkspace/behaviors/types.ts +41 -0
  39. package/explorer/src/workspaces/GraphWorkspace/behaviors/viewModeSwitchBehavior.ts +29 -0
  40. package/explorer/src/workspaces/GraphWorkspace/graphAnalytics.ts +779 -0
  41. package/explorer/src/workspaces/GraphWorkspace/graphColorLegend.ts +36 -0
  42. package/explorer/src/workspaces/GraphWorkspace/graphConfig.ts +102 -0
  43. package/explorer/src/workspaces/GraphWorkspace/graphEntityShape.ts +34 -0
  44. package/explorer/src/workspaces/GraphWorkspace/graphLoading.ts +84 -0
  45. package/explorer/src/workspaces/GraphWorkspace/graphSceneLayers.ts +775 -0
  46. package/explorer/src/workspaces/GraphWorkspace/graphSceneState.ts +2753 -0
  47. package/explorer/src/workspaces/GraphWorkspace/graphStructureLayer.ts +335 -0
  48. package/explorer/src/workspaces/GraphWorkspace/graphTheme.ts +1078 -0
  49. package/explorer/src/workspaces/GraphWorkspace/graphViewCopy.ts +41 -0
  50. package/explorer/src/workspaces/GraphWorkspace/localGraphTransition.ts +9 -0
  51. package/explorer/src/workspaces/GraphWorkspace/markdownEditorState.ts +114 -0
  52. package/explorer/src/workspaces/GraphWorkspace/markdownResourceClient.ts +103 -0
  53. package/explorer/src/workspaces/GraphWorkspace/markdownUrlSafety.ts +29 -0
  54. package/explorer/src/workspaces/GraphWorkspace/nodeMarkdownSync.ts +81 -0
  55. package/explorer/src/workspaces/GraphWorkspace/pluginRegistryPredicates.ts +24 -0
  56. package/explorer/src/workspaces/GraphWorkspace/plugins/explorationEffectsPlugin.tsx +353 -0
  57. package/explorer/src/workspaces/GraphWorkspace/plugins/explorationEffectsPluginPhaseC.tsx +522 -0
  58. package/explorer/src/workspaces/GraphWorkspace/plugins/index.ts +16 -0
  59. package/explorer/src/workspaces/GraphWorkspace/plugins/legendPlugin.tsx +120 -0
  60. package/explorer/src/workspaces/GraphWorkspace/plugins/neighborhoodPanelPlugin.tsx +231 -0
  61. package/explorer/src/workspaces/GraphWorkspace/plugins/temporalOverlayPlugin.tsx +142 -0
  62. package/explorer/src/workspaces/GraphWorkspace/plugins/types.ts +106 -0
  63. package/explorer/src/workspaces/GraphWorkspace/realtimeGraphAttributes.ts +50 -0
  64. package/explorer/src/workspaces/GraphWorkspace/scene.ts +79 -0
  65. package/explorer/src/workspaces/GraphWorkspace/sigmaNativeRendering.ts +421 -0
  66. package/explorer/src/workspaces/GraphWorkspace/smallGraphLayout.ts +135 -0
  67. package/explorer/src/workspaces/GraphWorkspace/temporalLifecyclePredicates.ts +31 -0
  68. package/explorer/src/workspaces/GraphWorkspace/temporalScrubberBounds.ts +46 -0
  69. package/explorer/src/workspaces/GraphWorkspace/temporalSnapshotGuards.ts +113 -0
  70. package/explorer/src/workspaces/GraphWorkspace/types.ts +395 -0
  71. package/explorer/src/workspaces/GraphWorkspace/useLoadGraph.ts +746 -0
  72. package/explorer/src/workspaces/GraphWorkspace/useMarkdownEditor.ts +177 -0
  73. package/explorer/src/workspaces/ImportExportWorkspace/ImportExportWorkspace.tsx +196 -0
  74. package/explorer/src/workspaces/LineageWorkspace/LineageDiagram.tsx +177 -0
  75. package/explorer/src/workspaces/ManageWorkspace/KGOverviewTab.tsx +291 -0
  76. package/explorer/src/workspaces/ManageWorkspace/OntologySummaryTab.tsx +346 -0
  77. package/explorer/src/workspaces/MemoryWorkspace.tsx +464 -0
  78. package/explorer/src/workspaces/OntologyWorkspace/AlignmentsTab.tsx +437 -0
  79. package/explorer/src/workspaces/OntologyWorkspace/HealthTab.tsx +214 -0
  80. package/explorer/src/workspaces/OntologyWorkspace/OntologyEditor.tsx +770 -0
  81. package/explorer/src/workspaces/OntologyWorkspace/OntologyLoader.tsx +913 -0
  82. package/explorer/src/workspaces/OntologyWorkspace/OntologyManager.tsx +930 -0
  83. package/explorer/src/workspaces/OntologyWorkspace/OntologySearch.tsx +590 -0
  84. package/explorer/src/workspaces/OntologyWorkspace/ProposalReview.tsx +429 -0
  85. package/explorer/src/workspaces/OntologyWorkspace/SKOSVocabularyManager.tsx +696 -0
  86. package/explorer/src/workspaces/OntologyWorkspace/ShaclStudio.tsx +363 -0
  87. package/explorer/src/workspaces/OntologyWorkspace/VersionsTab.tsx +546 -0
  88. package/explorer/src/workspaces/OntologyWorkspace/api.ts +164 -0
  89. package/explorer/src/workspaces/OntologyWorkspace/index.tsx +101 -0
  90. package/explorer/src/workspaces/OntologyWorkspace/ontologyEditorModel.ts +87 -0
  91. package/explorer/src/workspaces/OntologyWorkspace/ontologyUrlState.ts +94 -0
  92. package/explorer/src/workspaces/OntologyWorkspace/types.ts +119 -0
  93. package/explorer/src/workspaces/ReasoningWorkspace.tsx +243 -0
  94. package/explorer/src/workspaces/SparqlWorkspace/SparqlWorkspace.tsx +271 -0
  95. package/explorer/src/workspaces/VocabularyWorkspace/ConceptTree.tsx +97 -0
  96. package/explorer/src/workspaces/VocabularyWorkspace/ImportDropzone.tsx +95 -0
  97. package/explorer/src/workspaces/VocabularyWorkspace/PropertyPanel.tsx +116 -0
  98. package/explorer/src/workspaces/VocabularyWorkspace/Sidebar.tsx +112 -0
  99. package/explorer/src/workspaces/VocabularyWorkspace/VocabularyWorkspace.tsx +70 -0
  100. package/explorer/src/workspaces/VocabularyWorkspace/queries.ts +52 -0
  101. package/explorer/src/workspaces/VocabularyWorkspace/types.ts +24 -0
  102. package/package.json +31 -0
  103. package/plugins/.claude-plugin/README.md +262 -0
  104. package/plugins/.claude-plugin/marketplace.json +20 -0
  105. package/plugins/.claude-plugin/plugin.json +33 -0
  106. package/plugins/.cline-plugin/README.md +32 -0
  107. package/plugins/.cline-plugin/marketplace.json +17 -0
  108. package/plugins/.cline-plugin/plugin.json +35 -0
  109. package/plugins/.codex-plugin/marketplace.json +21 -0
  110. package/plugins/.codex-plugin/plugin.json +35 -0
  111. package/plugins/.continue-plugin/README.md +34 -0
  112. package/plugins/.continue-plugin/marketplace.json +17 -0
  113. package/plugins/.continue-plugin/plugin.json +33 -0
  114. package/plugins/.cursor-plugin/marketplace.json +18 -0
  115. package/plugins/.cursor-plugin/plugin.json +32 -0
  116. package/plugins/.openclaw-plugin/README.md +62 -0
  117. package/plugins/.openclaw-plugin/marketplace.json +18 -0
  118. package/plugins/.openclaw-plugin/plugin.json +46 -0
  119. package/plugins/.pi-plugin/README.md +35 -0
  120. package/plugins/.vscode-plugin/README.md +48 -0
  121. package/plugins/.vscode-plugin/marketplace.json +17 -0
  122. package/plugins/.vscode-plugin/plugin.json +35 -0
  123. package/plugins/.windsurf-plugin/README.md +39 -0
  124. package/plugins/.windsurf-plugin/marketplace.json +17 -0
  125. package/plugins/.windsurf-plugin/plugin.json +35 -0
  126. package/plugins/agents/decision-advisor.md +126 -0
  127. package/plugins/agents/explainability.md +129 -0
  128. package/plugins/agents/kg-assistant.md +73 -0
  129. package/plugins/hooks/hooks.json +12 -0
  130. package/plugins/skills/causal/SKILL.md +74 -0
  131. package/plugins/skills/change/SKILL.md +80 -0
  132. package/plugins/skills/decision/SKILL.md +197 -0
  133. package/plugins/skills/deduplicate/SKILL.md +41 -0
  134. package/plugins/skills/embed/SKILL.md +230 -0
  135. package/plugins/skills/explain/SKILL.md +44 -0
  136. package/plugins/skills/export/SKILL.md +67 -0
  137. package/plugins/skills/extract/SKILL.md +93 -0
  138. package/plugins/skills/ingest/SKILL.md +38 -0
  139. package/plugins/skills/ontology/SKILL.md +89 -0
  140. package/plugins/skills/policy/SKILL.md +66 -0
  141. package/plugins/skills/provenance/SKILL.md +70 -0
  142. package/plugins/skills/query/SKILL.md +84 -0
  143. package/plugins/skills/reason/SKILL.md +201 -0
  144. package/plugins/skills/temporal/SKILL.md +164 -0
  145. package/plugins/skills/validate/SKILL.md +228 -0
  146. package/plugins/skills/visualize/SKILL.md +249 -0
@@ -0,0 +1,41 @@
1
+ import type { FocusedUnavailableReason, GroupedViewUnavailableReason } from "./types";
2
+
3
+ const GROUPED_VIEW_COPY = {
4
+ "communities-undetected": "Grouped view is unavailable until communities can be detected.",
5
+ "community-nodes-missing": "Grouped view is unavailable because no community nodes could be created.",
6
+ } as const;
7
+
8
+ const FOCUSED_MODE_COPY = {
9
+ "no-selection": "Select a node to inspect in Focused mode.",
10
+ "grouped-unresolvable": "Focused mode is unavailable for this grouped selection.",
11
+ "not-in-graph": "Selected item is not available in the current graph.",
12
+ } as const;
13
+
14
+ export function groupedViewReasonText(
15
+ reason: GroupedViewUnavailableReason | null | undefined,
16
+ ): string | null {
17
+ if (!reason) {
18
+ return null;
19
+ }
20
+
21
+ switch (reason.code) {
22
+ case "communities-undetected":
23
+ return GROUPED_VIEW_COPY["communities-undetected"];
24
+ case "community-nodes-missing":
25
+ return GROUPED_VIEW_COPY["community-nodes-missing"];
26
+ case "invalid-community-layout":
27
+ return `Grouped node ${reason.nodeId} has invalid coordinates.`;
28
+ case "missing-grouped-node":
29
+ return `Grouped edge ${reason.edgeId} references a missing grouped node.`;
30
+ }
31
+ }
32
+
33
+ export function focusedUnavailableReasonText(
34
+ reason: FocusedUnavailableReason | null | undefined,
35
+ ): string | null {
36
+ if (!reason) {
37
+ return null;
38
+ }
39
+
40
+ return FOCUSED_MODE_COPY[reason.code];
41
+ }
@@ -0,0 +1,9 @@
1
+ import type { GraphViewMode } from "./types";
2
+
3
+ export function localGraphRequiresDraftConfirm(
4
+ viewMode: GraphViewMode,
5
+ selectedNodeId: string,
6
+ resolvedNodeId: string,
7
+ ): boolean {
8
+ return viewMode !== "focused" || resolvedNodeId !== selectedNodeId;
9
+ }
@@ -0,0 +1,114 @@
1
+ export type MarkdownResourceRef =
2
+ | { kind: "context-node"; id: string }
3
+ | { kind: "agent-memory"; id: string };
4
+
5
+ export type EditorStatus =
6
+ | "viewing"
7
+ | "loading-document"
8
+ | "editing"
9
+ | "saving"
10
+ | "validation-error"
11
+ | "save-error"
12
+ | "conflict";
13
+
14
+ export interface MarkdownEditorError {
15
+ kind: "validation" | "conflict" | "save" | "network";
16
+ message: string;
17
+ field?: string;
18
+ currentRevision?: string;
19
+ }
20
+
21
+ export interface MarkdownEditSession {
22
+ resource: MarkdownResourceRef;
23
+ baseSource: string;
24
+ baseRevision: string;
25
+ draft: string;
26
+ status: EditorStatus;
27
+ error: MarkdownEditorError | null;
28
+ }
29
+
30
+ export interface MarkdownSavedDocument {
31
+ source: string;
32
+ revision: string;
33
+ }
34
+
35
+ export function createLoadingSession(resource: MarkdownResourceRef): MarkdownEditSession {
36
+ return {
37
+ resource,
38
+ baseSource: "",
39
+ baseRevision: "",
40
+ draft: "",
41
+ status: "loading-document",
42
+ error: null,
43
+ };
44
+ }
45
+
46
+ export function createEditSession(
47
+ resource: MarkdownResourceRef,
48
+ document: MarkdownSavedDocument,
49
+ ): MarkdownEditSession {
50
+ return {
51
+ resource,
52
+ baseSource: document.source,
53
+ baseRevision: document.revision,
54
+ draft: document.source,
55
+ status: "editing",
56
+ error: null,
57
+ };
58
+ }
59
+
60
+ export function updateDraft(
61
+ session: MarkdownEditSession,
62
+ draft: string,
63
+ ): MarkdownEditSession {
64
+ return {
65
+ ...session,
66
+ draft,
67
+ status: "editing",
68
+ error: null,
69
+ };
70
+ }
71
+
72
+ export function isDirty(session: MarkdownEditSession | null): boolean {
73
+ return session !== null && session.draft !== session.baseSource;
74
+ }
75
+
76
+ export function saveStarted(session: MarkdownEditSession): MarkdownEditSession {
77
+ if (!isDirty(session)) return session;
78
+ return { ...session, status: "saving", error: null };
79
+ }
80
+
81
+ export function saveSucceeded(
82
+ session: MarkdownEditSession,
83
+ document: MarkdownSavedDocument,
84
+ ): MarkdownEditSession {
85
+ return {
86
+ ...session,
87
+ baseSource: document.source,
88
+ baseRevision: document.revision,
89
+ draft: document.source,
90
+ status: "viewing",
91
+ error: null,
92
+ };
93
+ }
94
+
95
+ export function saveFailed(
96
+ session: MarkdownEditSession,
97
+ error: MarkdownEditorError,
98
+ ): MarkdownEditSession {
99
+ const status: EditorStatus =
100
+ error.kind === "validation"
101
+ ? "validation-error"
102
+ : error.kind === "conflict"
103
+ ? "conflict"
104
+ : "save-error";
105
+ return { ...session, status, error };
106
+ }
107
+
108
+ export function cancelEdit(): null {
109
+ return null;
110
+ }
111
+
112
+ export function shouldConfirmDiscard(session: MarkdownEditSession | null): boolean {
113
+ return isDirty(session) && session?.status !== "saving";
114
+ }
@@ -0,0 +1,103 @@
1
+ import type {
2
+ MarkdownEditorError,
3
+ MarkdownResourceRef,
4
+ } from "./markdownEditorState";
5
+
6
+ export interface MarkdownDocument {
7
+ resource: MarkdownResourceRef;
8
+ source: string;
9
+ body: string;
10
+ revision: string;
11
+ editable: boolean;
12
+ }
13
+
14
+ export interface MarkdownApplyResult extends MarkdownDocument {
15
+ changed: boolean;
16
+ }
17
+
18
+ type ErrorDetail = {
19
+ code?: string;
20
+ message?: string;
21
+ field?: string;
22
+ current_revision?: string;
23
+ };
24
+
25
+ export class MarkdownClientError extends Error implements MarkdownEditorError {
26
+ readonly kind: MarkdownEditorError["kind"];
27
+ readonly field?: string;
28
+ readonly currentRevision?: string;
29
+
30
+ constructor(error: MarkdownEditorError) {
31
+ super(error.message);
32
+ this.name = "MarkdownClientError";
33
+ this.kind = error.kind;
34
+ this.field = error.field;
35
+ this.currentRevision = error.currentRevision;
36
+ }
37
+ }
38
+
39
+ function resourceUrl(ref: MarkdownResourceRef): string {
40
+ return `/api/markdown/${ref.kind}/${encodeURIComponent(ref.id)}`;
41
+ }
42
+
43
+ async function responseError(response: Response): Promise<MarkdownClientError> {
44
+ let detail: ErrorDetail = {};
45
+ try {
46
+ const payload = (await response.json()) as { detail?: ErrorDetail };
47
+ if (payload.detail && typeof payload.detail === "object") {
48
+ detail = payload.detail;
49
+ }
50
+ } catch {
51
+ // A non-JSON response is mapped from its status below.
52
+ }
53
+
54
+ const kind: MarkdownEditorError["kind"] =
55
+ response.status === 422
56
+ ? "validation"
57
+ : response.status === 409
58
+ ? "conflict"
59
+ : "save";
60
+ return new MarkdownClientError({
61
+ kind,
62
+ message: detail.message || `Markdown request failed (${response.status}).`,
63
+ field: detail.field,
64
+ currentRevision: detail.current_revision,
65
+ });
66
+ }
67
+
68
+ async function request<T>(input: RequestInfo | URL, init?: RequestInit): Promise<T> {
69
+ try {
70
+ const response = await fetch(input, init);
71
+ if (!response.ok) {
72
+ throw await responseError(response);
73
+ }
74
+ return (await response.json()) as T;
75
+ } catch (error) {
76
+ if (error instanceof MarkdownClientError) throw error;
77
+ throw new MarkdownClientError({
78
+ kind: "network",
79
+ message: "The Markdown service could not be reached. Your draft was kept.",
80
+ });
81
+ }
82
+ }
83
+
84
+ export function readMarkdownResource(
85
+ ref: MarkdownResourceRef,
86
+ ): Promise<MarkdownDocument> {
87
+ return request<MarkdownDocument>(resourceUrl(ref));
88
+ }
89
+
90
+ export function applyMarkdownResource(
91
+ ref: MarkdownResourceRef,
92
+ markdown: string,
93
+ expectedRevision: string,
94
+ ): Promise<MarkdownApplyResult> {
95
+ return request<MarkdownApplyResult>(resourceUrl(ref), {
96
+ method: "PUT",
97
+ headers: { "Content-Type": "application/json" },
98
+ body: JSON.stringify({
99
+ markdown,
100
+ expected_revision: expectedRevision,
101
+ }),
102
+ });
103
+ }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * URL-safety predicate for the Markdown content viewer.
3
+ *
4
+ * Extracted into a pure module so the check can be unit-tested without
5
+ * importing the MarkdownContentViewer React component, and so the component
6
+ * module exports only components (react-refresh/only-export-components,
7
+ * issue #1119). The behaviour is unchanged from the original in-component
8
+ * implementation: only http, https, mailto, in-document fragments, and
9
+ * root-relative paths are permitted.
10
+ */
11
+
12
+ export function isSafeUrl(url?: string): boolean {
13
+ if (!url) return false;
14
+ const trimmed = url.trim();
15
+ // Reject whitespace-only strings — new URL("", base) would resolve to the base
16
+ // protocol and produce a false positive. This guards direct callers of the exported
17
+ // function; markdown parsers normalise whitespace-only destinations to "" which
18
+ // already fails the !url check above.
19
+ if (!trimmed) return false;
20
+ if (trimmed.startsWith("//")) return false;
21
+ if (trimmed.startsWith("#")) return true;
22
+ if (trimmed.startsWith("/")) return true;
23
+ try {
24
+ const parsed = new URL(trimmed, "http://localhost");
25
+ return ["http:", "https:", "mailto:"].includes(parsed.protocol);
26
+ } catch {
27
+ return false;
28
+ }
29
+ }
@@ -0,0 +1,81 @@
1
+ export interface NodeMarkdownAttributeUpdate {
2
+ label: string;
3
+ content: string;
4
+ properties: Record<string, unknown>;
5
+ }
6
+
7
+ export interface GraphNodeMarkdownSnapshot {
8
+ id: string;
9
+ type: string;
10
+ content: string;
11
+ properties: Record<string, unknown>;
12
+ valid_from: string | null;
13
+ valid_until: string | null;
14
+ }
15
+
16
+ export interface SavedNodeMarkdownAttributeUpdate extends NodeMarkdownAttributeUpdate {
17
+ nodeType: string;
18
+ valid_from: string | null;
19
+ valid_until: string | null;
20
+ }
21
+
22
+ export class NodeMarkdownRefreshGuard {
23
+ private readonly generations = new Map<string, number>();
24
+
25
+ begin(nodeId: string): number {
26
+ const generation = (this.generations.get(nodeId) ?? 0) + 1;
27
+ this.generations.set(nodeId, generation);
28
+ return generation;
29
+ }
30
+
31
+ invalidate(nodeId: string): void {
32
+ this.begin(nodeId);
33
+ }
34
+
35
+ isCurrent(nodeId: string, generation: number): boolean {
36
+ return this.generations.get(nodeId) === generation;
37
+ }
38
+ }
39
+
40
+ export function buildNodeMarkdownAttributeUpdate(
41
+ nodeId: string,
42
+ content: string,
43
+ properties: Record<string, unknown>,
44
+ ): NodeMarkdownAttributeUpdate {
45
+ return {
46
+ label: content || nodeId,
47
+ content,
48
+ properties: {
49
+ ...properties,
50
+ content,
51
+ },
52
+ };
53
+ }
54
+
55
+ export async function readNodeMarkdownAttributeUpdate(
56
+ nodeId: string,
57
+ fetcher: typeof fetch = fetch,
58
+ ): Promise<SavedNodeMarkdownAttributeUpdate> {
59
+ const response = await fetcher(
60
+ `/api/graph/node?node_id=${encodeURIComponent(nodeId)}`,
61
+ );
62
+ if (!response.ok) {
63
+ throw new Error(`Graph node refresh failed (${response.status}).`);
64
+ }
65
+
66
+ const node = await response.json() as GraphNodeMarkdownSnapshot;
67
+ if (node.id !== nodeId) {
68
+ throw new Error("Graph node refresh returned a different resource.");
69
+ }
70
+
71
+ return {
72
+ ...buildNodeMarkdownAttributeUpdate(
73
+ node.id,
74
+ node.content,
75
+ node.properties ?? {},
76
+ ),
77
+ nodeType: node.type,
78
+ valid_from: node.valid_from ?? null,
79
+ valid_until: node.valid_until ?? null,
80
+ };
81
+ }
@@ -0,0 +1,24 @@
1
+ /**
2
+ * shouldLoad predicates for the GraphWorkspace lazy plugin registry.
3
+ *
4
+ * Extracted into a pure module so the predicates can be unit-tested without
5
+ * importing the full GraphWorkspace React component. Each predicate gates
6
+ * whether a plugin's module is lazily imported; none reference temporalState
7
+ * so temporal scrubber updates never retrigger plugin loading (issue #830).
8
+ */
9
+
10
+ export type PluginShouldLoadContext = {
11
+ panelState: Record<string, boolean>;
12
+ };
13
+
14
+ export function explorationEffectsShouldLoad({ panelState }: PluginShouldLoadContext): boolean {
15
+ return Boolean(panelState["effects-panel"]);
16
+ }
17
+
18
+ export function neighborhoodPanelShouldLoad({ panelState }: PluginShouldLoadContext): boolean {
19
+ return Boolean(panelState["neighborhood-panel"]);
20
+ }
21
+
22
+ export function temporalOverlayShouldLoad({ panelState }: PluginShouldLoadContext): boolean {
23
+ return Boolean(panelState["temporal-panel"]);
24
+ }