Drop generated cards; registry descriptions become Elements tooltips

- resolveRefs no longer adds cards to a diagram; it returns node id →
  description, which the page sets as the tooltip of the matching row in
  Archify's Elements panel
- hand-written cards pass through unchanged; docs and changelog
This commit is contained in:
2026-10-19 20:44:09 +03:00
parent de1a57e4c6
commit 094ca2925f
8 changed files with 34 additions and 70 deletions
+7 -48
View File
@@ -55,42 +55,6 @@ export interface Registry {
problems: ModelProblem[];
}
/** Archify card dot colour for a component type (the colours Archify gives the nodes). */
const DOT_BY_TYPE: Record<string, string> = {
frontend: 'cyan',
backend: 'emerald',
database: 'violet',
cloud: 'amber',
security: 'rose',
messagebus: 'orange',
external: 'slate',
};
export function dotForType(type?: string): string {
return DOT_BY_TYPE[type ?? ''] ?? 'slate';
}
export interface Card {
dot: string;
title: string;
items: string[];
}
/**
* Archify info cards for registry elements: one per element that has a
* description, titled by its label. What a diagram gets below itself when
* it draws shared elements and defines no cards of its own.
*/
export function cardsFromElements(elements: Iterable<RegistryElement>): Card[] {
const cards: Card[] = [];
for (const element of elements) {
if (!element.description) continue;
const items = [element.description.trim()];
cards.push({ dot: dotForType(element.type), title: element.label ?? element.id, items });
}
return cards;
}
export function isMainFile(fileName: string): boolean {
return (fileName.split(/[\\/]/).pop() ?? '') === MAIN_FILE;
}
@@ -196,6 +160,8 @@ export interface ResolvedDiagram {
links: Map<string, string>;
/** Registry element ids used by this diagram. */
used: Set<string>;
/** Node id → registry description, for the Elements panel. */
descriptions: Map<string, string>;
}
export interface ResolveOptions {
@@ -208,20 +174,19 @@ export interface ResolveOptions {
* Expands `ref` components from the registry and strips what Archify does
* not know. Component order and indexes are kept, so Archify's diagnostics
* (JSON pointers) still point at the right component in the source file.
* A diagram without `cards` of its own gets cards from the descriptions of
* the shared elements it draws, so the registry text shows under the diagram.
*/
export function resolveRefs(doc: unknown, options: ResolveOptions): ResolvedDiagram {
const problems: ModelProblem[] = [];
const refs = new Map<number, RegistryElement>();
const links = new Map<string, string>();
const used = new Set<string>();
if (!isObj(doc)) return { diagram: {}, problems, refs, links, used };
const descriptions = new Map<string, string>();
if (!isObj(doc)) return { diagram: {}, problems, refs, links, used, descriptions };
const diagram: Obj = { ...doc };
delete diagram.model;
const { registry } = options;
const components = Array.isArray(doc.components) ? doc.components : undefined;
if (!components) return { diagram, problems, refs, links, used };
if (!components) return { diagram, problems, refs, links, used, descriptions };
diagram.components = components.map((raw, index) => {
if (!isObj(raw)) return raw;
@@ -248,6 +213,7 @@ export function resolveRefs(doc: unknown, options: ResolveOptions): ResolvedDiag
}
used.add(ref);
refs.set(index, element);
if (element.description) descriptions.set(id, element.description.trim());
for (const key of Object.keys(own)) {
if ((COPIED_FIELDS as readonly string[]).includes(key) && !(OVERRIDABLE_FIELDS as readonly string[]).includes(key)) {
problems.push({
@@ -267,14 +233,7 @@ export function resolveRefs(doc: unknown, options: ResolveOptions): ResolvedDiag
if (link) links.set(id, link);
return merged;
});
if (diagram.cards === undefined && refs.size) {
const seen = new Set<string>();
const elements: RegistryElement[] = [];
for (const element of refs.values()) if (!seen.has(element.id)) { seen.add(element.id); elements.push(element); }
const cards = cardsFromElements(elements);
if (cards.length) diagram.cards = cards;
}
return { diagram, problems, refs, links, used };
return { diagram, problems, refs, links, used, descriptions };
}
/** Group of a diagram inside a model: '' for the root folder, else the folder path with '/' separators. */
+3 -1
View File
@@ -16,6 +16,8 @@ export interface PreparedDiagram {
problems: ModelProblem[];
/** Node id → diagram path relative to the model root. */
links: Map<string, string>;
/** Node id → registry description. */
descriptions: Map<string, string>;
}
/**
@@ -33,5 +35,5 @@ export async function prepareDiagram(models: ModelService, uri: vscode.Uri, text
const prepared: DiagramSource = changed
? { ...source, diagram: resolved.diagram, json: JSON.stringify(resolved.diagram, null, 2) }
: source;
return { source: prepared, root: found?.root, registry: found?.registry, problems: resolved.problems, links: resolved.links };
return { source: prepared, root: found?.root, registry: found?.registry, problems: resolved.problems, links: resolved.links, descriptions: resolved.descriptions };
}
+2 -1
View File
@@ -199,10 +199,11 @@ class DiagramPreview implements vscode.Disposable {
notice: result.evidenceNotice ? { kind: 'warning', text: result.evidenceNotice } : undefined,
viewer3d: viewer3dOption(this.panel.webview, this.context.extensionUri),
links: Object.fromEntries(prepared.links),
descriptions: Object.fromEntries(prepared.descriptions),
model,
});
// Nonces differ per render, so compare the renderer output instead.
const fingerprint = `${viewerTheme()}\u0000${result.evidenceNotice ?? ''}\u0000${JSON.stringify([model ?? null, [...prepared.links]])}\u0000${result.html}`;
const fingerprint = `${viewerTheme()}\u0000${result.evidenceNotice ?? ''}\u0000${JSON.stringify([model ?? null, [...prepared.links], [...prepared.descriptions]])}\u0000${result.html}`;
if (!force && this.rendered && fingerprint === this.lastHtml) {
void this.panel.webview.postMessage({ type: 'notice', notice: undefined });
return;
+13
View File
@@ -18,6 +18,8 @@ export interface PageOptions {
links?: Record<string, string>;
/** The model page: metadata above the diagram, the diagram list and registry below. */
model?: ModelPanel;
/** Node id → registry description, shown as tooltips in the Elements panel. */
descriptions?: Record<string, string>;
/** Registry page: the elements of one .archify-registry file. */
registry?: RegistryPanel;
}
@@ -77,6 +79,7 @@ function bridgeScript(options: PageOptions): string {
links: options.links ?? {},
model: options.model,
registry: options.registry,
descriptions: options.descriptions || {},
};
return `(function () {
var config = ${scriptJson(config)};
@@ -322,6 +325,15 @@ function bridgeScript(options: PageOptions): string {
document.body.appendChild(body);
}
// Elements: registry descriptions as tooltips on Archify's node index rows.
function describeElements() {
var items = document.querySelectorAll('#node-outline .node-outline-item[data-outline-node]');
Array.prototype.forEach.call(items, function (item) {
var text = config.descriptions[item.getAttribute('data-outline-node')];
if (text) item.title = text;
});
}
// Connectors: a rail panel next to Archify's Elements index, read from the
// SVG's edges. Hover highlights the connector, click keeps it highlighted.
function renderConnectors() {
@@ -486,6 +498,7 @@ function bridgeScript(options: PageOptions): string {
if (config.model) onReady(function () { renderModel(config.model); });
if (config.registry) onReady(function () { renderRegistry(config.registry); });
onReady(renderConnectors);
onReady(describeElements);
if (config.notice) {
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', function () { showNotice(config.notice); });