skynet-graph 1.2.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/CONTRIBUTING.md +22 -0
- package/LICENSE +661 -0
- package/README.md +245 -0
- package/bin/sg +20 -0
- package/concepts/README.md +23 -0
- package/concepts/_substrate/Claim/Confidence/Trusted.json +6 -0
- package/concepts/_substrate/Claim/Confidence.json +10 -0
- package/concepts/_substrate/Claim/Freshness/Stale.json +7 -0
- package/concepts/_substrate/Claim/Freshness.json +5 -0
- package/concepts/_substrate/Claim/Verification/Refuted.json +6 -0
- package/concepts/_substrate/Claim/Verification.json +6 -0
- package/concepts/_substrate/Claim.json +4 -0
- package/concepts/_substrate/Frontier/InBeam.json +6 -0
- package/concepts/_substrate/Frontier/Stuck.json +6 -0
- package/concepts/_substrate/Frontier.json +5 -0
- package/concepts/_substrate/Intake/ToTask.json +22 -0
- package/concepts/_substrate/Intake/Typed.json +10 -0
- package/concepts/_substrate/Intake.json +27 -0
- package/concepts/_substrate/Task/Answer.json +12 -0
- package/concepts/_substrate/Task/Complexity/Atomic.json +5 -0
- package/concepts/_substrate/Task/Complexity/Compound/Expansion.json +7 -0
- package/concepts/_substrate/Task/Complexity/Compound.json +6 -0
- package/concepts/_substrate/Task/Complexity.json +6 -0
- package/concepts/_substrate/Task/EvalComplexity.json +13 -0
- package/concepts/_substrate/Task/ReportUp.json +6 -0
- package/concepts/_substrate/Task/Rollup.json +10 -0
- package/concepts/_substrate/Task.json +5 -0
- package/concepts/clinical/Diagnosis/Medication.json +6 -0
- package/concepts/clinical/Diagnosis.json +15 -0
- package/concepts/clinical/Observation/LabValue/OutOfRange.json +6 -0
- package/concepts/clinical/Observation/LabValue.json +5 -0
- package/concepts/clinical/Observation.json +6 -0
- package/concepts/common/Document/pathBasket.json +6 -0
- package/concepts/common/Document.json +3 -0
- package/concepts/common/Edge/Distance.json +13 -0
- package/concepts/common/Edge/Stay/LongStay.json +8 -0
- package/concepts/common/Edge/Stay.json +8 -0
- package/concepts/common/Edge/Travel/LongTravel.json +16 -0
- package/concepts/common/Edge/Travel/ShortTravel.json +11 -0
- package/concepts/common/Edge/Travel/targetNode.json +7 -0
- package/concepts/common/Edge/Travel.json +7 -0
- package/concepts/common/Edge.json +3 -0
- package/concepts/common/Vertice.json +3 -0
- package/concepts/supply/Fulfillment.json +10 -0
- package/concepts/supply/Inventory/Reorder.json +7 -0
- package/concepts/supply/Inventory.json +5 -0
- package/concepts/supply/Procurement/SupplierConfirm.json +6 -0
- package/concepts/supply/Procurement.json +8 -0
- package/concepts/supply/Transport.json +5 -0
- package/doc/API.md +556 -0
- package/doc/CAPABILITIES.md +373 -0
- package/doc/MODELISATION.md +511 -0
- package/doc/architecture.md +267 -0
- package/doc/concept-as-graph.md +379 -0
- package/doc/concept-learning.md +168 -0
- package/doc/creative-loop-map.md +202 -0
- package/doc/original-2016-doc.md +148 -0
- package/doc/usage.md +501 -0
- package/lib/authoring/abstract.js +401 -0
- package/lib/authoring/abstraction.js +190 -0
- package/lib/authoring/adapt.js +393 -0
- package/lib/authoring/ancestry.js +216 -0
- package/lib/authoring/author.js +147 -0
- package/lib/authoring/bounded-merge.js +57 -0
- package/lib/authoring/canon.js +158 -0
- package/lib/authoring/clock.js +123 -0
- package/lib/authoring/combinator.js +92 -0
- package/lib/authoring/compete.js +76 -0
- package/lib/authoring/compose-hotspot.js +314 -0
- package/lib/authoring/compress.js +279 -0
- package/lib/authoring/concept-net.js +254 -0
- package/lib/authoring/concepts.js +66 -0
- package/lib/authoring/context-project.js +277 -0
- package/lib/authoring/contract.js +476 -0
- package/lib/authoring/corpus-pack.js +125 -0
- package/lib/authoring/cost-probe.js +86 -0
- package/lib/authoring/crystallize.js +555 -0
- package/lib/authoring/dag-decompose.js +256 -0
- package/lib/authoring/dataset-adapter.js +253 -0
- package/lib/authoring/debug-provider.js +92 -0
- package/lib/authoring/decompose.js +343 -0
- package/lib/authoring/emittability.js +236 -0
- package/lib/authoring/equilibrium.js +160 -0
- package/lib/authoring/extract.js +153 -0
- package/lib/authoring/forest.js +55 -0
- package/lib/authoring/forge-fallback.js +116 -0
- package/lib/authoring/givens.js +121 -0
- package/lib/authoring/glossary.js +177 -0
- package/lib/authoring/grammar-graph.js +191 -0
- package/lib/authoring/granularity.js +80 -0
- package/lib/authoring/graph-net.js +108 -0
- package/lib/authoring/ground.js +148 -0
- package/lib/authoring/higher-order.js +85 -0
- package/lib/authoring/hotspot.js +132 -0
- package/lib/authoring/hysteresis.js +85 -0
- package/lib/authoring/index.js +106 -0
- package/lib/authoring/lattice-morphism.js +61 -0
- package/lib/authoring/lattice-pack.js +148 -0
- package/lib/authoring/leaf-io.js +77 -0
- package/lib/authoring/library.js +156 -0
- package/lib/authoring/lifecycle.js +92 -0
- package/lib/authoring/loop.js +193 -0
- package/lib/authoring/master-loop.js +132 -0
- package/lib/authoring/mdl.js +209 -0
- package/lib/authoring/memo-stability.js +122 -0
- package/lib/authoring/method-explorer.js +138 -0
- package/lib/authoring/method-pack.js +181 -0
- package/lib/authoring/method.js +202 -0
- package/lib/authoring/mine.js +510 -0
- package/lib/authoring/mount.js +119 -0
- package/lib/authoring/negotiate.js +100 -0
- package/lib/authoring/parametric.js +139 -0
- package/lib/authoring/reaggregate.js +75 -0
- package/lib/authoring/rebalance.js +157 -0
- package/lib/authoring/recall.js +135 -0
- package/lib/authoring/registry.js +350 -0
- package/lib/authoring/relearn.js +147 -0
- package/lib/authoring/retention.js +89 -0
- package/lib/authoring/segment-proxy.js +114 -0
- package/lib/authoring/serve-leaf.js +64 -0
- package/lib/authoring/slot-aware-serve.js +77 -0
- package/lib/authoring/sound-invoke.js +59 -0
- package/lib/authoring/split-serve.js +74 -0
- package/lib/authoring/ste.js +239 -0
- package/lib/authoring/stock.js +101 -0
- package/lib/authoring/store.js +94 -0
- package/lib/authoring/supervise.js +138 -0
- package/lib/authoring/support.js +131 -0
- package/lib/authoring/task-mirror.js +51 -0
- package/lib/authoring/typed-loop.js +160 -0
- package/lib/authoring/validate.js +569 -0
- package/lib/authoring/widen.js +88 -0
- package/lib/combos/appliance.js +189 -0
- package/lib/combos/critique.js +377 -0
- package/lib/combos/defaults.js +86 -0
- package/lib/combos/durable-runner.js +115 -0
- package/lib/combos/forge.js +209 -0
- package/lib/combos/index.js +86 -0
- package/lib/combos/learning-library.js +253 -0
- package/lib/combos/mixture-serve.js +193 -0
- package/lib/combos/plan-loop.js +192 -0
- package/lib/combos/proxy-cache.js +272 -0
- package/lib/combos/self-mod.js +94 -0
- package/lib/durable/audit.js +102 -0
- package/lib/durable/checkpoint-store.js +550 -0
- package/lib/durable/fold.js +62 -0
- package/lib/durable/interpreter.js +242 -0
- package/lib/durable/xlate.js +185 -0
- package/lib/graph/Graph.js +2486 -0
- package/lib/graph/expr.js +307 -0
- package/lib/graph/index.js +26 -0
- package/lib/graph/log.js +134 -0
- package/lib/graph/objects/Concept.js +382 -0
- package/lib/graph/objects/Entity.js +503 -0
- package/lib/graph/objects/Node.js +57 -0
- package/lib/graph/objects/PathMap.js +557 -0
- package/lib/graph/objects/Segment.js +100 -0
- package/lib/graph/tasks/stabilize.js +59 -0
- package/lib/graph/tasks/taskflow.js +247 -0
- package/lib/index.js +122 -0
- package/lib/load.js +132 -0
- package/lib/providers/backends.js +117 -0
- package/lib/providers/borderline.js +129 -0
- package/lib/providers/cache.js +226 -0
- package/lib/providers/canonicalize.js +198 -0
- package/lib/providers/constat.js +107 -0
- package/lib/providers/geo.js +53 -0
- package/lib/providers/index.js +116 -0
- package/lib/providers/intake.js +263 -0
- package/lib/providers/llm-local.js +88 -0
- package/lib/providers/llm.js +236 -0
- package/lib/providers/local-host.js +197 -0
- package/lib/providers/merge-consistency.js +162 -0
- package/lib/providers/nogood.js +149 -0
- package/lib/providers/reason-loop.js +223 -0
- package/lib/providers/semiring.js +285 -0
- package/lib/providers/solver-fork.js +163 -0
- package/lib/providers/stats.js +228 -0
- package/lib/providers/verify.js +120 -0
- package/lib/runtime/index.js +135 -0
- package/lib/runtime/invoke-pool.js +92 -0
- package/lib/runtime/protocol.js +210 -0
- package/lib/runtime/transport-socket.js +77 -0
- package/lib/runtime/worker-entry.js +31 -0
- package/lib/sg/cli.js +601 -0
- package/lib/sg/log-sinks.js +214 -0
- package/lib/sg/mcp.js +395 -0
- package/lib/sg/proxy-run.js +66 -0
- package/lib/sg/serve-demo.js +185 -0
- package/lib/sg/serve.js +205 -0
- package/lib/sg/trace.js +118 -0
- package/lib/studio/protocol.js +45 -0
- package/lib/studio/public/app.js +310 -0
- package/lib/studio/public/components/ConceptEditor.js +43 -0
- package/lib/studio/public/components/ConceptTree.js +43 -0
- package/lib/studio/public/components/CorpusPanel.js +68 -0
- package/lib/studio/public/components/ForkTree.js +36 -0
- package/lib/studio/public/components/GrammarGraph.js +99 -0
- package/lib/studio/public/components/GraphCanvas.js +156 -0
- package/lib/studio/public/components/Inspector.js +48 -0
- package/lib/studio/public/components/LearningPanel.js +85 -0
- package/lib/studio/public/components/PromptConsole.js +46 -0
- package/lib/studio/public/components/ProviderTrace.js +39 -0
- package/lib/studio/public/components/SessionSplit.js +47 -0
- package/lib/studio/public/components/TilingOverlay.js +40 -0
- package/lib/studio/public/components/Timeline.js +57 -0
- package/lib/studio/public/index.html +26 -0
- package/lib/studio/public/styles.css +185 -0
- package/lib/studio/public/ws.js +58 -0
- package/lib/studio/server.js +130 -0
- package/lib/studio/session.js +397 -0
- package/lib/studio/studio.js +149 -0
- package/package.json +42 -0
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2026 Nathanael Braun
|
|
3
|
+
* @author : Nathanael BRAUN <pp9ping@gmail.com>
|
|
4
|
+
*
|
|
5
|
+
* This program is free software: you can redistribute it and/or modify
|
|
6
|
+
* it under the terms of the GNU Affero General Public License as published by
|
|
7
|
+
* the Free Software Foundation, either version 3 of the License, or
|
|
8
|
+
* (at your option) any later version.
|
|
9
|
+
*
|
|
10
|
+
* This program is distributed in the hope that it will be useful,
|
|
11
|
+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
* GNU Affero General Public License for more details.
|
|
14
|
+
*
|
|
15
|
+
* You should have received a copy of the GNU Affero General Public License
|
|
16
|
+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
|
+
*/
|
|
18
|
+
import React, { useRef, useEffect } from 'react';
|
|
19
|
+
import { html } from 'htm/react';
|
|
20
|
+
import cytoscape from 'cytoscape';
|
|
21
|
+
import fcose from 'cytoscape-fcose';
|
|
22
|
+
import dagre from 'cytoscape-dagre';
|
|
23
|
+
import ELK from 'elkjs';
|
|
24
|
+
|
|
25
|
+
// register the cytoscape-native layout extensions once (idempotent)
|
|
26
|
+
let _registered = false;
|
|
27
|
+
function registerLayouts() {
|
|
28
|
+
if ( _registered ) return;
|
|
29
|
+
try { cytoscape.use(fcose); } catch ( e ) {}
|
|
30
|
+
try { cytoscape.use(dagre); } catch ( e ) {}
|
|
31
|
+
_registered = true;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// ELK is driven DIRECTLY (the bundled build = no web worker) rather than via the
|
|
35
|
+
// cytoscape-elk wrapper, whose `new ELK()` breaks under esm.sh interop. Tuning carried
|
|
36
|
+
// over from the rocinante workflow editor: layered + NETWORK_SIMPLEX, cleaner than dagre
|
|
37
|
+
// for directed/DAG graphs.
|
|
38
|
+
const elk = new ELK();
|
|
39
|
+
const ELK_OPTS = {
|
|
40
|
+
'elk.algorithm' : 'layered',
|
|
41
|
+
'elk.direction' : 'RIGHT',
|
|
42
|
+
'elk.spacing.nodeNode' : '60',
|
|
43
|
+
'elk.layered.spacing.nodeNodeBetweenLayers': '120',
|
|
44
|
+
'elk.edgeRouting' : 'ORTHOGONAL',
|
|
45
|
+
'elk.layered.nodePlacement.strategy' : 'NETWORK_SIMPLEX'
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
async function runElk( cy ) {
|
|
49
|
+
const children = cy.nodes().map(( n ) => ({ id: n.id(), width: Math.max(n.width() || 40, 40), height: Math.max(n.height() || 40, 40) }));
|
|
50
|
+
if ( !children.length ) return;
|
|
51
|
+
const edges = cy.edges().map(( e, i ) => ({ id: e.id() || ('e' + i), sources: [e.source().id()], targets: [e.target().id()] }));
|
|
52
|
+
const res = await elk.layout({ id: 'root', layoutOptions: ELK_OPTS, children, edges });
|
|
53
|
+
const pos = {};
|
|
54
|
+
(res.children || []).forEach(( n ) => { pos[n.id] = { x: n.x, y: n.y }; });
|
|
55
|
+
cy.nodes().forEach(( n ) => { const p = pos[n.id()]; if ( p ) n.position(p); });
|
|
56
|
+
cy.fit(undefined, 40);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// cytoscape-native layouts (ELK is handled separately above)
|
|
60
|
+
const CY_LAYOUTS = {
|
|
61
|
+
fcose : { name: 'fcose', animate: true, animationDuration: 400, randomize: false, padding: 40, nodeSeparation: 90, idealEdgeLength: 90 },
|
|
62
|
+
dagre : { name: 'dagre', rankDir: 'LR', nodeSep: 30, rankSep: 70, padding: 40, animate: true, animationDuration: 400 },
|
|
63
|
+
breadthfirst: { name: 'breadthfirst', directed: true, padding: 40, animate: true, spacingFactor: 1.2 },
|
|
64
|
+
concentric : { name: 'concentric', animate: true, padding: 40, minNodeSpacing: 40 },
|
|
65
|
+
circle : { name: 'circle', animate: true, padding: 40 },
|
|
66
|
+
grid : { name: 'grid', animate: true, padding: 40 }
|
|
67
|
+
};
|
|
68
|
+
export const LAYOUT_NAMES = ['elk', ...Object.keys(CY_LAYOUTS)];
|
|
69
|
+
|
|
70
|
+
// concept self-flags on an object (keys whose value === true) = "what cast here"
|
|
71
|
+
function castFlags( o ) { return Object.keys(o).filter(( k ) => k[0] !== '_' && o[k] === true); }
|
|
72
|
+
|
|
73
|
+
function toElements( objects ) {
|
|
74
|
+
const nodeIds = new Set(objects.filter(( o ) => !o.Segment).map(( o ) => o._id));
|
|
75
|
+
const els = [];
|
|
76
|
+
for ( const o of objects ) {
|
|
77
|
+
const flags = castFlags(o).filter(( k ) => k !== 'Node' && k !== 'Segment');
|
|
78
|
+
if ( o.Segment && nodeIds.has(o.originNode) && nodeIds.has(o.targetNode) ) {
|
|
79
|
+
els.push({ group: 'edges', data: { id: o._id, source: o.originNode, target: o.targetNode, label: flags.join(' ') } });
|
|
80
|
+
} else {
|
|
81
|
+
els.push({ group: 'nodes', data: { id: o._id, label: o._id }, classes: o.Segment ? 'dangling' : '' });
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return els;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const STYLE = [
|
|
88
|
+
{ selector: 'node', style: { 'background-color': '#6ea8fe', 'label': 'data(label)', 'color': '#cdd6e0', 'font-size': '10px', 'text-valign': 'center', 'text-halign': 'center', 'width': 34, 'height': 34, 'text-outline-color': '#0f1419', 'text-outline-width': 2 } },
|
|
89
|
+
{ selector: 'node.dangling', style: { 'background-color': '#f5a524', 'shape': 'round-rectangle' } },
|
|
90
|
+
{ selector: 'node:selected', style: { 'border-color': '#fff', 'border-width': 2 } },
|
|
91
|
+
{ selector: 'edge', style: { 'curve-style': 'bezier', 'target-arrow-shape': 'triangle', 'line-color': '#3ad29f', 'target-arrow-color': '#3ad29f', 'width': 2, 'label': 'data(label)', 'font-size': '9px', 'color': '#9fb0c0', 'text-rotation': 'autorotate', 'text-background-color': '#0f1419', 'text-background-opacity': 0.85, 'text-background-padding': 2 } },
|
|
92
|
+
{ selector: 'edge:selected', style: { 'line-color': '#fff', 'target-arrow-color': '#fff' } },
|
|
93
|
+
{ selector: '.pulse', style: { 'background-color': '#ff6b6b', 'line-color': '#ff6b6b', 'target-arrow-color': '#ff6b6b', 'width': 5 } },
|
|
94
|
+
{ selector: '.retract-flash', style: { 'background-color': '#fb7185', 'border-color': '#fb7185', 'border-width': 4, 'line-color': '#fb7185', 'target-arrow-color': '#fb7185' } }
|
|
95
|
+
];
|
|
96
|
+
|
|
97
|
+
export function GraphCanvas( { objects, lastApply, lastRetract, onSelect, layout = 'elk', relayout = 0 } ) {
|
|
98
|
+
const ref = useRef(null);
|
|
99
|
+
const cyRef = useRef(null);
|
|
100
|
+
const layoutRef = useRef(layout);
|
|
101
|
+
useEffect(() => { layoutRef.current = layout; }, [layout]);
|
|
102
|
+
|
|
103
|
+
function runLayout() {
|
|
104
|
+
const cy = cyRef.current;
|
|
105
|
+
if ( !cy || cy.elements().length === 0 ) return;
|
|
106
|
+
if ( layoutRef.current === 'elk' ) { runElk(cy).catch(( e ) => console.warn('elk layout failed:', e && e.message)); return; }
|
|
107
|
+
cy.layout(CY_LAYOUTS[layoutRef.current] || CY_LAYOUTS.fcose).run();
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
useEffect(() => {
|
|
111
|
+
registerLayouts();
|
|
112
|
+
const cy = cytoscape({ container: ref.current, style: STYLE, wheelSensitivity: 0.2 });
|
|
113
|
+
cy.on('tap', 'node, edge', ( e ) => onSelect && onSelect(e.target.id()));
|
|
114
|
+
cy.on('tap', ( e ) => { if ( e.target === cy ) onSelect && onSelect(null); });
|
|
115
|
+
cyRef.current = cy;
|
|
116
|
+
try { window.__sgCy = cy; } catch ( e ) {} // test hook (puppeteer introspection)
|
|
117
|
+
return () => cy.destroy();
|
|
118
|
+
}, []);
|
|
119
|
+
|
|
120
|
+
// reconcile elements on each state update (keep positions; re-layout on topology change)
|
|
121
|
+
useEffect(() => {
|
|
122
|
+
const cy = cyRef.current;
|
|
123
|
+
if ( !cy ) return;
|
|
124
|
+
const want = toElements(objects);
|
|
125
|
+
const wantIds = new Set(want.map(( e ) => e.data.id));
|
|
126
|
+
let topo = false;
|
|
127
|
+
cy.elements().forEach(( el ) => { if ( !wantIds.has(el.id()) ) { el.remove(); topo = true; } });
|
|
128
|
+
want.forEach(( e ) => {
|
|
129
|
+
const ex = cy.getElementById(e.data.id);
|
|
130
|
+
if ( ex.length ) ex.data(e.data);
|
|
131
|
+
else { cy.add(e); topo = true; }
|
|
132
|
+
});
|
|
133
|
+
if ( topo ) runLayout();
|
|
134
|
+
}, [objects]);
|
|
135
|
+
|
|
136
|
+
// explicit re-layout (selector change or the relayout button)
|
|
137
|
+
useEffect(() => { runLayout(); }, [layout, relayout]);
|
|
138
|
+
|
|
139
|
+
// pulse the target of the most recent concept-apply
|
|
140
|
+
useEffect(() => {
|
|
141
|
+
const cy = cyRef.current;
|
|
142
|
+
if ( !cy || !lastApply ) return;
|
|
143
|
+
const el = cy.getElementById(lastApply.targetId);
|
|
144
|
+
if ( el && el.length ) { el.addClass('pulse'); setTimeout(() => el.removeClass('pulse'), 600); }
|
|
145
|
+
}, [lastApply]);
|
|
146
|
+
|
|
147
|
+
// flash red the object a concept was just RETRACTED from (JTMS defeasance / cascade)
|
|
148
|
+
useEffect(() => {
|
|
149
|
+
const cy = cyRef.current;
|
|
150
|
+
if ( !cy || !lastRetract ) return;
|
|
151
|
+
const el = cy.getElementById(lastRetract.targetId);
|
|
152
|
+
if ( el && el.length ) { el.addClass('retract-flash'); setTimeout(() => el.removeClass('retract-flash'), 900); }
|
|
153
|
+
}, [lastRetract]);
|
|
154
|
+
|
|
155
|
+
return html`<div class="cy" ref=${ref}></div>`;
|
|
156
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2026 Nathanael Braun
|
|
3
|
+
* @author : Nathanael BRAUN <pp9ping@gmail.com>
|
|
4
|
+
*
|
|
5
|
+
* This program is free software: you can redistribute it and/or modify
|
|
6
|
+
* it under the terms of the GNU Affero General Public License as published by
|
|
7
|
+
* the Free Software Foundation, either version 3 of the License, or
|
|
8
|
+
* (at your option) any later version.
|
|
9
|
+
*
|
|
10
|
+
* This program is distributed in the hope that it will be useful,
|
|
11
|
+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
* GNU Affero General Public License for more details.
|
|
14
|
+
*
|
|
15
|
+
* You should have received a copy of the GNU Affero General Public License
|
|
16
|
+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
|
+
*/
|
|
18
|
+
import React from 'react';
|
|
19
|
+
import { html } from 'htm/react';
|
|
20
|
+
|
|
21
|
+
function fmt( v ) {
|
|
22
|
+
if ( v === true ) return '✓';
|
|
23
|
+
if ( v && typeof v === 'object' ) return JSON.stringify(v);
|
|
24
|
+
return String(v);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function Inspector( { object, applies } ) {
|
|
28
|
+
if ( !object ) {
|
|
29
|
+
return html`<aside class="inspector"><div class="empty">select a node or segment</div></aside>`;
|
|
30
|
+
}
|
|
31
|
+
const facts = Object.keys(object).filter(( k ) => k[0] !== '_');
|
|
32
|
+
const castHere = applies.filter(( a ) => a.targetId === object._id);
|
|
33
|
+
return html`
|
|
34
|
+
<aside class="inspector">
|
|
35
|
+
<h3>${object._id}${object.Segment ? ' (segment)' : ''}</h3>
|
|
36
|
+
<div class="facts">
|
|
37
|
+
${facts.map(( k ) => html`
|
|
38
|
+
<div key=${k} class="fact"><span class="k">${k}</span><span class="v">${fmt(object[k])}</span></div>`)}
|
|
39
|
+
</div>
|
|
40
|
+
<h4>cast here ${castHere.length ? '(' + castHere.length + ')' : ''}</h4>
|
|
41
|
+
${castHere.length === 0 ? html`<div class="empty">—</div>` : castHere.map(( a, i ) => html`
|
|
42
|
+
<div key=${i} class="cast">
|
|
43
|
+
<div class="cn">${a.conceptName} ✓ <span class="ms">${Math.round(a.ms || 0)}ms</span></div>
|
|
44
|
+
${(a.why || []).map(( w, j ) => html`
|
|
45
|
+
<div key=${j} class="why">${w.require} = ${fmt(w.value)}</div>`)}
|
|
46
|
+
</div>`)}
|
|
47
|
+
</aside>`;
|
|
48
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2026 Nathanael Braun
|
|
3
|
+
* @author : Nathanael BRAUN <pp9ping@gmail.com>
|
|
4
|
+
*
|
|
5
|
+
* This program is free software: you can redistribute it and/or modify
|
|
6
|
+
* it under the terms of the GNU Affero General Public License as published by
|
|
7
|
+
* the Free Software Foundation, either version 3 of the License, or
|
|
8
|
+
* (at your option) any later version.
|
|
9
|
+
*
|
|
10
|
+
* This program is distributed in the hope that it will be useful,
|
|
11
|
+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
* GNU Affero General Public License for more details.
|
|
14
|
+
*
|
|
15
|
+
* You should have received a copy of the GNU Affero General Public License
|
|
16
|
+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
|
+
*/
|
|
18
|
+
import React, { useState } from 'react';
|
|
19
|
+
import { html } from 'htm/react';
|
|
20
|
+
|
|
21
|
+
// The LEARNING panel (track 4) — the session's typed lattice registry, visually: declare a vocab key
|
|
22
|
+
// (authoring), PROPOSE an alias through THE admission gate (member ∈ enum ∧ confluence — a rejection shows
|
|
23
|
+
// its reason), RETRACT an alias (the recoverability guarantee). Every admitted ring row shows its
|
|
24
|
+
// provenance; nothing here writes the registry outside the gate.
|
|
25
|
+
export function LearningPanel( { learning, onDeclare, onPropose, onRetract, verdict } ) {
|
|
26
|
+
const reg = (learning && learning.registry) || { keys: {} };
|
|
27
|
+
const rings = (learning && learning.rings) || [];
|
|
28
|
+
const keys = Object.keys(reg.keys || {});
|
|
29
|
+
const [kName, setKName] = useState('');
|
|
30
|
+
const [kEnum, setKEnum] = useState('');
|
|
31
|
+
const [pKey, setPKey] = useState('');
|
|
32
|
+
const [pMember, setPMember] = useState('');
|
|
33
|
+
const [pAlias, setPAlias] = useState('');
|
|
34
|
+
const prov = reg.ringProvenance || {};
|
|
35
|
+
const provOf = ( r ) => { const p = prov[r.key + '::' + String(r.alias).trim().toLowerCase()]; return p ? p.via : ''; };
|
|
36
|
+
|
|
37
|
+
return html`
|
|
38
|
+
<div class="learning">
|
|
39
|
+
<div class="lp-head">learning — typed lattice registry <span class="lp-ver">${reg.version || ''}</span></div>
|
|
40
|
+
|
|
41
|
+
<div class="lp-block">
|
|
42
|
+
<div class="lp-title">declare a vocab key <span class="lp-note">(authoring — enums are host-declared)</span></div>
|
|
43
|
+
<div class="lp-row">
|
|
44
|
+
<input class="lp-key" placeholder="key (e.g. unit)" value=${kName} onChange=${( e ) => setKName(e.target.value)} />
|
|
45
|
+
<input class="lp-enum" placeholder="members, comma-separated" value=${kEnum} onChange=${( e ) => setKEnum(e.target.value)} />
|
|
46
|
+
<button class="lp-declare" disabled=${!kName.trim() || !kEnum.trim()}
|
|
47
|
+
onClick=${() => { onDeclare(kName.trim(), kEnum); setKName(''); setKEnum(''); }}>declare</button>
|
|
48
|
+
</div>
|
|
49
|
+
${keys.length ? html`<div class="lp-keys">${keys.map(( k ) => html`
|
|
50
|
+
<span key=${k} class="lp-kchip"><b>${k}</b>: ${(reg.keys[k].enum || []).join(' · ')}</span>`)}</div>` : null}
|
|
51
|
+
</div>
|
|
52
|
+
|
|
53
|
+
<div class="lp-block">
|
|
54
|
+
<div class="lp-title">propose an alias <span class="lp-note">(through THE gate: member ∈ enum ∧ confluence)</span></div>
|
|
55
|
+
<div class="lp-row">
|
|
56
|
+
<select class="lp-pkey" value=${pKey} onChange=${( e ) => { setPKey(e.target.value); setPMember(''); }}>
|
|
57
|
+
<option value="" disabled>key…</option>
|
|
58
|
+
${keys.map(( k ) => html`<option key=${k} value=${k}>${k}</option>`)}
|
|
59
|
+
</select>
|
|
60
|
+
<select class="lp-pmember" value=${pMember} onChange=${( e ) => setPMember(e.target.value)}>
|
|
61
|
+
<option value="" disabled>member…</option>
|
|
62
|
+
${(pKey && reg.keys[pKey] && reg.keys[pKey].enum || []).map(( m ) => html`<option key=${m} value=${m}>${m}</option>`)}
|
|
63
|
+
</select>
|
|
64
|
+
<input class="lp-palias" placeholder="alias" value=${pAlias} onChange=${( e ) => setPAlias(e.target.value)} />
|
|
65
|
+
<button class="lp-propose" disabled=${!pKey || !pMember || !pAlias.trim()}
|
|
66
|
+
onClick=${() => { onPropose(pKey, pMember, pAlias.trim()); setPAlias(''); }}>propose</button>
|
|
67
|
+
</div>
|
|
68
|
+
${verdict ? html`<div class="lp-verdict ${verdict.ok ? 'ok' : 'ko'}">${verdict.text}</div>` : null}
|
|
69
|
+
</div>
|
|
70
|
+
|
|
71
|
+
<div class="lp-block lp-grow">
|
|
72
|
+
<div class="lp-title">admitted rings <span class="lp-note">(${rings.length} — retract = the recoverability guarantee)</span></div>
|
|
73
|
+
<div class="lp-rings">
|
|
74
|
+
${rings.length === 0 ? html`<div class="lp-empty">no admitted alias yet — declare a key, then propose</div>`
|
|
75
|
+
: rings.map(( r, i ) => html`
|
|
76
|
+
<div key=${i} class="lp-ring">
|
|
77
|
+
<span class="lp-rkey">${r.key}</span>
|
|
78
|
+
<span class="lp-ralias">${r.alias}</span> → <span class="lp-rmember">${r.member}</span>
|
|
79
|
+
${provOf(r) ? html`<span class="lp-rvia">via ${provOf(r)}</span>` : null}
|
|
80
|
+
<button class="lp-retract" title="retract this alias" onClick=${() => onRetract(r.key, r.alias)}>✕</button>
|
|
81
|
+
</div>`)}
|
|
82
|
+
</div>
|
|
83
|
+
</div>
|
|
84
|
+
</div>`;
|
|
85
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2026 Nathanael Braun
|
|
3
|
+
* @author : Nathanael BRAUN <pp9ping@gmail.com>
|
|
4
|
+
*
|
|
5
|
+
* This program is free software: you can redistribute it and/or modify
|
|
6
|
+
* it under the terms of the GNU Affero General Public License as published by
|
|
7
|
+
* the Free Software Foundation, either version 3 of the License, or
|
|
8
|
+
* (at your option) any later version.
|
|
9
|
+
*
|
|
10
|
+
* This program is distributed in the hope that it will be useful,
|
|
11
|
+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
* GNU Affero General Public License for more details.
|
|
14
|
+
*
|
|
15
|
+
* You should have received a copy of the GNU Affero General Public License
|
|
16
|
+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
|
+
*/
|
|
18
|
+
import React, { useState } from 'react';
|
|
19
|
+
import { html } from 'htm/react';
|
|
20
|
+
|
|
21
|
+
// bottom bar: type a prompt -> decompose & synthesize (answer-loop). The decomposition
|
|
22
|
+
// builds live on the canvas; steps stream here and the synthesized answer lands at the end.
|
|
23
|
+
export function PromptConsole( { onRun, progress, answer, running, error } ) {
|
|
24
|
+
const [text, setText] = useState('');
|
|
25
|
+
const hasOut = (progress && progress.length) || answer || error;
|
|
26
|
+
return html`
|
|
27
|
+
<div class="prompt">
|
|
28
|
+
${hasOut ? html`
|
|
29
|
+
<div class="pr-out">
|
|
30
|
+
${error ? html`<div class="pr-err">${error}</div>` : null}
|
|
31
|
+
${(progress || []).map(( p, i ) => html`
|
|
32
|
+
<div key=${i} class="pr-step pr-${p.kind}">
|
|
33
|
+
[${p.kind}${p.depth != null ? ' d' + p.depth : ''}] ${p.label || ''}
|
|
34
|
+
${p.atomic != null ? (p.atomic ? '→ atomic' : '→ split') : ''}
|
|
35
|
+
${p.into ? '→ ' + p.into.join(' | ') : ''}
|
|
36
|
+
${p.children != null ? '← ' + p.children + ' children' : ''}
|
|
37
|
+
</div>`)}
|
|
38
|
+
${answer ? html`<div class="pr-answer"><b>answer</b><div>${answer}</div></div>` : null}
|
|
39
|
+
</div>` : null}
|
|
40
|
+
<form class="pr-bar" onSubmit=${( e ) => { e.preventDefault(); if ( text.trim() ) onRun(text.trim()); }}>
|
|
41
|
+
<input placeholder="prompt → decompose & synthesize (needs LLM_BASE)" value=${text}
|
|
42
|
+
onInput=${( e ) => setText(e.target.value)} disabled=${running} />
|
|
43
|
+
<button type="submit" disabled=${running || !text.trim()}>${running ? 'running…' : 'run'}</button>
|
|
44
|
+
</form>
|
|
45
|
+
</div>`;
|
|
46
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2026 Nathanael Braun
|
|
3
|
+
* @author : Nathanael BRAUN <pp9ping@gmail.com>
|
|
4
|
+
*
|
|
5
|
+
* This program is free software: you can redistribute it and/or modify
|
|
6
|
+
* it under the terms of the GNU Affero General Public License as published by
|
|
7
|
+
* the Free Software Foundation, either version 3 of the License, or
|
|
8
|
+
* (at your option) any later version.
|
|
9
|
+
*
|
|
10
|
+
* This program is distributed in the hope that it will be useful,
|
|
11
|
+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
* GNU Affero General Public License for more details.
|
|
14
|
+
*
|
|
15
|
+
* You should have received a copy of the GNU Affero General Public License
|
|
16
|
+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
|
+
*/
|
|
18
|
+
import React from 'react';
|
|
19
|
+
import { html } from 'htm/react';
|
|
20
|
+
|
|
21
|
+
// Apply-correlated provider / log records (graph.logger.tail) — joins an apply's logs by applyId.
|
|
22
|
+
export function ProviderTrace( { records, onRefresh } ) {
|
|
23
|
+
const recs = records || [];
|
|
24
|
+
return html`
|
|
25
|
+
<div class="ptrace">
|
|
26
|
+
<div class="pt-head">provider trace <button class="pt-refresh" onClick=${onRefresh}>↻</button></div>
|
|
27
|
+
<div class="pt-rows">
|
|
28
|
+
${recs.length === 0 ? html`<div class="pt-empty">no records (raise the log level / run a provider)</div>`
|
|
29
|
+
: recs.slice(-60).map(( r, i ) => html`
|
|
30
|
+
<div key=${i} class="pt-row pt-${r.level || 'log'}">
|
|
31
|
+
<span class="pt-lvl">${r.level || 'log'}</span>
|
|
32
|
+
${r.concept ? html`<span class="pt-concept">${r.concept}</span>` : null}
|
|
33
|
+
${r.target ? html`<span class="pt-target">→${r.target}</span>` : null}
|
|
34
|
+
<span class="pt-msg">${r.message || r.msg || ''}</span>
|
|
35
|
+
${r.applyId ? html`<span class="pt-aid">#${String(r.applyId).slice(-4)}</span>` : null}
|
|
36
|
+
</div>`)}
|
|
37
|
+
</div>
|
|
38
|
+
</div>`;
|
|
39
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2026 Nathanael Braun
|
|
3
|
+
* @author : Nathanael BRAUN <pp9ping@gmail.com>
|
|
4
|
+
*
|
|
5
|
+
* This program is free software: you can redistribute it and/or modify
|
|
6
|
+
* it under the terms of the GNU Affero General Public License as published by
|
|
7
|
+
* the Free Software Foundation, either version 3 of the License, or
|
|
8
|
+
* (at your option) any later version.
|
|
9
|
+
*
|
|
10
|
+
* This program is distributed in the hope that it will be useful,
|
|
11
|
+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
* GNU Affero General Public License for more details.
|
|
14
|
+
*
|
|
15
|
+
* You should have received a copy of the GNU Affero General Public License
|
|
16
|
+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
|
+
*/
|
|
18
|
+
import React from 'react';
|
|
19
|
+
import { html } from 'htm/react';
|
|
20
|
+
import { GraphCanvas } from '/components/GraphCanvas.js';
|
|
21
|
+
|
|
22
|
+
// Side-by-side parent ↔ fork sub-graphs (track 5 / G3). Shows both graphs at once + the
|
|
23
|
+
// merge-projection PREVIEW: what the projection crosses the frontier, and any frontier-leak.
|
|
24
|
+
export function SessionSplit( { open, parentId, forkId, parentObjects, forkObjects, preview, onClose, onMerge } ) {
|
|
25
|
+
if ( !open ) return null;
|
|
26
|
+
const warns = (preview && preview.warnings) || [];
|
|
27
|
+
const leaks = warns.filter(( w ) => w.kind === 'frontier-leak');
|
|
28
|
+
return html`
|
|
29
|
+
<div class="split-modal" onClick=${onClose}>
|
|
30
|
+
<div class="split-box" onClick=${( e ) => e.stopPropagation()}>
|
|
31
|
+
<div class="split-head">
|
|
32
|
+
<span>sub-graphs: <b>${parentId}</b> ↔ <b>${forkId}</b></span>
|
|
33
|
+
<button onClick=${onClose}>close</button>
|
|
34
|
+
</div>
|
|
35
|
+
<div class="split-canvases">
|
|
36
|
+
<div class="split-pane"><div class="split-label">${parentId} (parent)</div>
|
|
37
|
+
<${GraphCanvas} objects=${parentObjects || []} layout="elk" /></div>
|
|
38
|
+
<div class="split-pane"><div class="split-label">${forkId} (fork)</div>
|
|
39
|
+
<${GraphCanvas} objects=${forkObjects || []} layout="elk" /></div>
|
|
40
|
+
</div>
|
|
41
|
+
<div class="split-merge">
|
|
42
|
+
${preview ? html`<span class="split-prev">${leaks.length ? '⚠ ' + leaks.length + ' frontier-leak(s): ' + leaks.map(( l ) => l.ref).join(', ') : 'projection clean — frontier respected'}</span>` : null}
|
|
43
|
+
<button class="split-do" onClick=${onMerge}>merge fork → parent</button>
|
|
44
|
+
</div>
|
|
45
|
+
</div>
|
|
46
|
+
</div>`;
|
|
47
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2026 Nathanael Braun
|
|
3
|
+
* @author : Nathanael BRAUN <pp9ping@gmail.com>
|
|
4
|
+
*
|
|
5
|
+
* This program is free software: you can redistribute it and/or modify
|
|
6
|
+
* it under the terms of the GNU Affero General Public License as published by
|
|
7
|
+
* the Free Software Foundation, either version 3 of the License, or
|
|
8
|
+
* (at your option) any later version.
|
|
9
|
+
*
|
|
10
|
+
* This program is distributed in the hope that it will be useful,
|
|
11
|
+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
* GNU Affero General Public License for more details.
|
|
14
|
+
*
|
|
15
|
+
* You should have received a copy of the GNU Affero General Public License
|
|
16
|
+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
|
+
*/
|
|
18
|
+
import React from 'react';
|
|
19
|
+
import { html } from 'htm/react';
|
|
20
|
+
|
|
21
|
+
// the tree-decomposition TILING (pavage) of the active corpus: the derived separator
|
|
22
|
+
// interface (the narrow waist) + each tile/fork with its frontier alphabet. Fed by the
|
|
23
|
+
// `forkPlan` Session op (a pure derivation off the concept-dependency graph).
|
|
24
|
+
export function TilingOverlay( { plan } ) {
|
|
25
|
+
if ( !plan ) return null;
|
|
26
|
+
return html`
|
|
27
|
+
<div class="tiling">
|
|
28
|
+
<div class="tl-head">tiling
|
|
29
|
+
<span class="tl-meta">tw ${plan.treewidth} · ${plan.nTiles} tiles · ${plan.partitionPays ? 'partition pays' : 'one tile'}</span>
|
|
30
|
+
</div>
|
|
31
|
+
<div class="tl-seps">separators:${(plan.separators || []).map(( s ) => html`<span key=${s} class="tl-sep">${s}</span>`)}</div>
|
|
32
|
+
<div class="tl-forks">
|
|
33
|
+
${(plan.forks || []).map(( f, i ) => html`
|
|
34
|
+
<div key=${i} class="tl-fork">
|
|
35
|
+
<span class="tl-fconc">${(f.concepts || []).join(', ')}</span>
|
|
36
|
+
<span class="tl-ffront">⟶ ${(f.frontier || []).join(', ') || '∅'}</span>
|
|
37
|
+
</div>`)}
|
|
38
|
+
</div>
|
|
39
|
+
</div>`;
|
|
40
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2026 Nathanael Braun
|
|
3
|
+
* @author : Nathanael BRAUN <pp9ping@gmail.com>
|
|
4
|
+
*
|
|
5
|
+
* This program is free software: you can redistribute it and/or modify
|
|
6
|
+
* it under the terms of the GNU Affero General Public License as published by
|
|
7
|
+
* the Free Software Foundation, either version 3 of the License, or
|
|
8
|
+
* (at your option) any later version.
|
|
9
|
+
*
|
|
10
|
+
* This program is distributed in the hope that it will be useful,
|
|
11
|
+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
* GNU Affero General Public License for more details.
|
|
14
|
+
*
|
|
15
|
+
* You should have received a copy of the GNU Affero General Public License
|
|
16
|
+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
|
+
*/
|
|
18
|
+
import React, { useState, useEffect } from 'react';
|
|
19
|
+
import { html } from 'htm/react';
|
|
20
|
+
|
|
21
|
+
// revision scrubber. Steps through the CHECKPOINTED revs only (getRevisions —
|
|
22
|
+
// snapshots are captured per settle, not per intermediate rev), so rollback/diff
|
|
23
|
+
// never target a rev that has no snapshot.
|
|
24
|
+
export function Timeline( { revs, currentRev, onRollback, onDiff } ) {
|
|
25
|
+
const list = (revs && revs.length) ? revs : [currentRev];
|
|
26
|
+
const [idx, setIdx] = useState(list.length - 1);
|
|
27
|
+
useEffect(() => { setIdx(list.length - 1); }, [currentRev, list.length]);
|
|
28
|
+
const i = Math.min(idx, list.length - 1);
|
|
29
|
+
const at = list[i] != null ? list[i] : currentRev;
|
|
30
|
+
const isLast = at >= currentRev;
|
|
31
|
+
return html`
|
|
32
|
+
<div class="timeline">
|
|
33
|
+
<span class="tl-lbl">timeline</span>
|
|
34
|
+
<input class="tl-range" type="range" min="0" max=${Math.max(list.length - 1, 0)} value=${i}
|
|
35
|
+
onInput=${( e ) => setIdx(Number(e.target.value))} />
|
|
36
|
+
<span class="tl-cur">rev ${at} / ${currentRev} · ${list.length} ckpt</span>
|
|
37
|
+
<button disabled=${isLast} onClick=${() => onRollback(at)}>rollback</button>
|
|
38
|
+
<button disabled=${isLast} onClick=${() => onDiff(at, currentRev)}>diff → ${currentRev}</button>
|
|
39
|
+
</div>`;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// added/removed/changed between two revs (diffRevisions output)
|
|
43
|
+
export function DiffPanel( { result, onClose } ) {
|
|
44
|
+
if ( !result ) return null;
|
|
45
|
+
const { a, b, d } = result;
|
|
46
|
+
const ids = ( o ) => Object.keys(o || {});
|
|
47
|
+
return html`
|
|
48
|
+
<div class="diffpanel">
|
|
49
|
+
<div class="dp-head">diff rev ${a} → ${b}<button onClick=${onClose}>✕</button></div>
|
|
50
|
+
<div class="dp-body">
|
|
51
|
+
<div class="dp-sec added">+ added: ${ids(d.added).join(', ') || '—'}</div>
|
|
52
|
+
<div class="dp-sec removed">− removed: ${ids(d.removed).join(', ') || '—'}</div>
|
|
53
|
+
<div class="dp-sec changed">~ changed: ${ids(d.changed).length === 0 ? '—' : ids(d.changed).map(( id ) => html`
|
|
54
|
+
<div key=${id} class="dp-chg">${id}: ${Object.keys(d.changed[id]).join(', ')}</div>`)}</div>
|
|
55
|
+
</div>
|
|
56
|
+
</div>`;
|
|
57
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
|
+
<title>sg studio</title>
|
|
7
|
+
<link rel="stylesheet" href="/styles.css" />
|
|
8
|
+
<script type="importmap">
|
|
9
|
+
{
|
|
10
|
+
"imports": {
|
|
11
|
+
"react": "https://esm.sh/react@18.3.1",
|
|
12
|
+
"react-dom/client": "https://esm.sh/react-dom@18.3.1/client?deps=react@18.3.1",
|
|
13
|
+
"htm/react": "https://esm.sh/htm@3.1.1/react?deps=react@18.3.1",
|
|
14
|
+
"cytoscape": "https://esm.sh/cytoscape@3.30.2",
|
|
15
|
+
"cytoscape-fcose": "https://esm.sh/cytoscape-fcose@2.2.0?deps=cytoscape@3.30.2",
|
|
16
|
+
"cytoscape-dagre": "https://esm.sh/cytoscape-dagre@2.5.0?deps=cytoscape@3.30.2",
|
|
17
|
+
"elkjs": "https://esm.sh/elkjs@0.9.3/lib/elk.bundled.js"
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
</script>
|
|
21
|
+
</head>
|
|
22
|
+
<body>
|
|
23
|
+
<div id="app">booting sg studio…</div>
|
|
24
|
+
<script type="module" src="/app.js"></script>
|
|
25
|
+
</body>
|
|
26
|
+
</html>
|