Model graph: the whole model as one graph with queries; 0.9.0

- Model | Graph switch on main.archify (Diagram | Graph on diagram pages, narrowed to that diagram)
- union graph of every element and connection, registry and inline, built from the model index
- query box: text, type:, diagram:, decision:, uses:, near:, from: to:, unused, shared, -shared
- side panel per element and connection with diagrams, registry line and decisions one click away
- legend chips, Fit, Layout, Labels, Export PNG; Cytoscape.js bundled as dist/graph.js
- command Archify | Open Model Graph; integration check; unit tests for graph and queries
This commit is contained in:
root-at-skic committed 2026-10-21 15:47:00 +03:00
1 parent 265f8c57f6
commit c8645b228b
20 files changed
+1551 -30

No files matched your search

+49 -2
View File
@@ -309,6 +309,23 @@ export function groupOf(relativePath: string): string {
return parts.join('/');
}
/** A node as a diagram declares it: a `ref` to the registry, or an inline element. */
export interface DiagramNode {
id: string;
ref?: string;
type?: string;
label?: string;
sublabel?: string;
brand?: string;
}
/** A connection, edge, flow, transition or message between two nodes of a diagram. */
export interface DiagramEdge {
from: string;
to: string;
label?: string;
}
export interface DiagramSummary {
relativePath: string;
group: string;
@@ -317,6 +334,10 @@ export interface DiagramSummary {
used: string[];
/** Decisions the diagram points at. */
decisions: DecisionRef[];
/** Every node the diagram draws, shared or inline, in file order. */
nodes: DiagramNode[];
/** Every edge between two of those nodes. */
edges: DiagramEdge[];
parseError?: string;
}
@@ -326,14 +347,40 @@ export function summarizeDiagram(text: string, relativePath: string): DiagramSum
try {
doc = parseYaml(text, { prettyErrors: false, uniqueKeys: false });
} catch (error) {
return { relativePath, group: groupOf(relativePath), used: [], decisions: [], parseError: error instanceof Error ? error.message.split('\n')[0] : String(error) };
return { relativePath, group: groupOf(relativePath), used: [], decisions: [], nodes: [], edges: [], parseError: error instanceof Error ? error.message.split('\n')[0] : String(error) };
}
const obj = isObj(doc) ? doc : {};
const meta = isObj(obj.meta) ? obj.meta : {};
const components = Array.isArray(obj.components) ? obj.components : [];
const used = [...new Set(components.map((c) => (isObj(c) ? str(c.ref) : undefined)).filter((r): r is string => !!r))];
const decisions = takeDecisionRefs({ ...obj }, { registry: { elements: new Map(), files: [], problems: [] }, fileName: relativePath }, []);
return { relativePath, group: groupOf(relativePath), kind: str(obj.diagram_type), title: str(meta.title), used, decisions };
const nodes: DiagramNode[] = [];
const seen = new Set<string>();
for (const key of NODE_COLLECTIONS) {
const items = obj[key];
if (!Array.isArray(items)) continue;
for (const item of items) {
if (!isObj(item)) continue;
const ref = str(item.ref);
const id = str(item.id) ?? ref;
if (!id || seen.has(id)) continue;
seen.add(id);
nodes.push({ id, ref, type: str(item.type), label: str(item.label), sublabel: str(item.sublabel), brand: str(item.brand) });
}
}
const edges: DiagramEdge[] = [];
for (const key of EDGE_COLLECTIONS) {
const items = obj[key];
if (!Array.isArray(items)) continue;
for (const item of items) {
if (!isObj(item)) continue;
const from = str(item.from);
const to = str(item.to);
if (!from || !to || !seen.has(from) || !seen.has(to)) continue;
edges.push({ from, to, label: str(item.label) });
}
}
return { relativePath, group: groupOf(relativePath), kind: str(obj.diagram_type), title: str(meta.title), used, decisions, nodes, edges };
}
/** Groups in display order: the root group first, then folders in path order. */