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:
2026-10-21 15:47:00 +03:00
parent 265f8c57f6
commit c8645b228b
20 changed files with 1551 additions and 30 deletions
+63
View File
@@ -0,0 +1,63 @@
/** Page styles of the graph view. VS Code's theme variables, Archify's type colours. */
export const GRAPH_PAGE_STYLE = `
:root { --ag-bg: var(--vscode-editor-background, #0b1020); --ag-fg: var(--vscode-foreground, #e5e7eb); --ag-muted: var(--vscode-descriptionForeground, #9ca3af);
--ag-panel: var(--vscode-editorWidget-background, #111a2e); --ag-border: var(--vscode-editorWidget-border, rgba(128,128,128,.35)); --ag-accent: var(--vscode-textLink-foreground, #22d3ee);
--ag-input: var(--vscode-input-background, #1f2937); --ag-input-fg: var(--vscode-input-foreground, #e5e7eb); --ag-input-border: var(--vscode-input-border, var(--ag-border));
--ag-error: var(--vscode-errorForeground, #f87171); --ag-font: var(--vscode-font-family, system-ui, sans-serif); --ag-mono: var(--vscode-editor-font-family, ui-monospace, Menlo, monospace);
--ag-frontend: #22d3ee; --ag-backend: #34d399; --ag-database: #a78bfa; --ag-cloud: #fbbf24; --ag-security: #fb7185; --ag-messagebus: #fb923c; --ag-external: #94a3b8; }
html, body { margin: 0; height: 100%; overflow: hidden; background: var(--ag-bg); color: var(--ag-fg); font-family: var(--ag-font); font-size: 13px; }
.ag { display: grid; grid-template-rows: auto auto minmax(0, 1fr); height: 100%; }
.ag-top { display: flex; align-items: center; gap: 1rem; padding: 0.75rem 1.25rem; border-bottom: 1px solid var(--ag-border); }
.ag-kicker { font-size: 0.6875rem; font-weight: 700; letter-spacing: 0.12em; text-transform: uppercase; color: var(--ag-accent); }
.ag-name { font-weight: 700; font-size: 1.125rem; letter-spacing: -0.01em; }
.ag-actions { margin-left: auto; display: flex; align-items: center; gap: 0.5rem; }
.ag-switch { display: inline-flex; padding: 0; border: 1px solid var(--ag-border); border-radius: 999px; background: var(--ag-panel); color: var(--ag-muted); font: inherit; cursor: pointer; overflow: hidden; }
.ag-switch:focus-visible { outline: 2px solid var(--ag-accent); outline-offset: 2px; }
.ag-seg { display: inline-flex; align-items: center; height: 1.75rem; padding: 0 0.8rem; border-radius: 999px; font-weight: 600; font-size: 0.8125rem; transition: background 0.15s, color 0.15s; }
.ag-seg.on { color: var(--ag-bg); background: var(--ag-fg); }
.ag-switch:hover .ag-seg:not(.on) { color: var(--ag-fg); }
.ag-button { font: inherit; font-size: 0.8125rem; font-weight: 600; height: 1.9rem; padding: 0 0.85rem; border-radius: 999px; border: 1px solid var(--ag-border); background: var(--ag-panel); color: var(--ag-fg); cursor: pointer; white-space: nowrap; }
.ag-button:hover { border-color: var(--ag-accent); }
.ag-button[aria-pressed="true"] { background: var(--ag-fg); color: var(--ag-bg); border-color: var(--ag-fg); }
.ag-button:focus-visible { outline: 2px solid var(--ag-accent); outline-offset: 2px; }
.ag-bar { display: flex; align-items: center; gap: 0.6rem; padding: 0.6rem 1.25rem; border-bottom: 1px solid var(--ag-border); flex-wrap: wrap; }
.ag-query-wrap { flex: 1 1 18rem; display: flex; align-items: center; gap: 0.5rem; min-width: 0; background: var(--ag-input); border: 1px solid var(--ag-input-border); border-radius: 0.6rem; padding: 0 0.6rem; }
.ag-query-wrap:focus-within { border-color: var(--ag-accent); }
.ag-query { flex: 1; min-width: 0; height: 2rem; border: 0; background: transparent; color: var(--ag-input-fg); font-family: var(--ag-mono); font-size: 0.875rem; outline: none; }
.ag-query::placeholder { color: var(--ag-muted); opacity: 0.8; }
.ag-clear { border: 0; background: transparent; color: var(--ag-muted); font-size: 1rem; cursor: pointer; padding: 0 0.1rem; }
.ag-clear:hover { color: var(--ag-fg); }
.ag-count { font-family: var(--ag-mono); font-size: 0.8125rem; color: var(--ag-muted); white-space: nowrap; }
.ag-count.is-error { color: var(--ag-error); }
.ag-main { position: relative; display: grid; grid-template-columns: minmax(0, 1fr) 20rem; min-height: 0; }
.ag-main.is-side-hidden { grid-template-columns: minmax(0, 1fr); }
.ag-canvas { position: relative; min-width: 0; min-height: 0; }
.ag-cy { position: absolute; inset: 0; }
.ag-legend { position: absolute; left: 1rem; bottom: 0.9rem; display: flex; align-items: center; gap: 0.4rem; flex-wrap: wrap; z-index: 2; max-width: calc(100% - 2rem); }
.ag-chip { display: inline-flex; align-items: center; gap: 0.4rem; font: inherit; font-size: 0.75rem; padding: 0.2rem 0.6rem; border-radius: 999px; border: 1px solid var(--ag-border); background: color-mix(in srgb, var(--ag-panel) 85%, transparent); color: var(--ag-fg); cursor: pointer; backdrop-filter: blur(6px); }
.ag-chip::before { content: ""; width: 0.55rem; height: 0.55rem; border-radius: 2px; background: var(--c); }
.ag-chip[aria-pressed="false"] { opacity: 0.45; text-decoration: line-through; }
.ag-chip small { color: var(--ag-muted); font-family: var(--ag-mono); }
.ag-side { border-left: 1px solid var(--ag-border); background: var(--ag-panel); overflow: auto; padding: 1rem 1.1rem 1.5rem; min-width: 0; }
.ag-side h2 { margin: 0; font-size: 1.0625rem; font-weight: 700; display: flex; align-items: center; gap: 0.5rem; }
.ag-dot { display: inline-block; width: 0.6rem; height: 0.6rem; border-radius: 50%; background: var(--c, var(--ag-external)); flex: none; }
.ag-id { font-family: var(--ag-mono); color: var(--ag-muted); font-size: 0.75rem; margin: 0.25rem 0 0.8rem; word-break: break-all; }
.ag-side p { margin: 0 0 0.8rem; line-height: 1.5; white-space: pre-line; }
.ag-side h3 { margin: 0.9rem 0 0.4rem; font-size: 0.6875rem; letter-spacing: 0.1em; text-transform: uppercase; color: var(--ag-muted); font-weight: 700; }
.ag-link { display: inline-flex; align-items: baseline; gap: 0.35rem; line-height: 1.4; font: inherit; font-family: var(--ag-mono); font-size: 0.75rem; padding: 0.15rem 0.55rem; margin: 0 0.3rem 0.35rem 0; border: 1px solid var(--ag-border); border-radius: 999px; background: transparent; color: var(--ag-fg); cursor: pointer; text-align: left; max-width: 100%; }
.ag-link:hover { border-color: var(--ag-accent); }
.ag-link.is-static { cursor: default; }
.ag-link.is-static:hover { border-color: var(--ag-border); }
.ag-link.is-accepted .ag-dot { background: var(--ag-backend); } .ag-link.is-proposed .ag-dot { background: var(--ag-cloud); } .ag-link.is-rejected .ag-dot { background: var(--ag-security); } .ag-link.is-superseded .ag-dot { background: var(--ag-external); }
.ag-empty { color: var(--ag-muted); }
.ag-side-actions { display: flex; flex-wrap: wrap; gap: 0.4rem; margin-top: 0.6rem; }
.ag-help { position: absolute; top: 0.5rem; right: 20.75rem; z-index: 5; width: min(26rem, calc(100% - 2rem)); max-height: calc(100% - 1rem); overflow: auto; padding: 0.9rem 1rem; border: 1px solid var(--ag-border); border-radius: 0.75rem; background: var(--ag-panel); box-shadow: 0 16px 40px rgba(0,0,0,.35); }
.ag-main.is-side-hidden .ag-help { right: 0.75rem; }
.ag-help h3 { margin: 0 0 0.5rem; font-size: 0.8125rem; }
.ag-help table { border-collapse: collapse; width: 100%; font-size: 0.8125rem; }
.ag-help td { padding: 0.25rem 0.4rem 0.25rem 0; vertical-align: top; border-top: 1px solid var(--ag-border); }
.ag-help td:first-child { font-family: var(--ag-mono); white-space: nowrap; color: var(--ag-accent); cursor: pointer; }
.ag-help td:first-child:hover { text-decoration: underline; }
.ag-help p { margin: 0.6rem 0 0; color: var(--ag-muted); font-size: 0.75rem; }
@media (max-width: 760px) { .ag-main { grid-template-columns: minmax(0, 1fr); grid-template-rows: minmax(0, 1fr) auto; } .ag-side { border-left: 0; border-top: 1px solid var(--ag-border); max-height: 40%; } .ag-help { right: 0.75rem; } }
`;