0.7.1: Registry panel lists registry files #23
@@ -2,6 +2,7 @@
|
||||
|
||||
## 0.7.1 — 2026-10-19
|
||||
|
||||
- **No generated cards.** The cards 0.7.0 generated under model diagrams from registry descriptions are gone; they repeated the Elements panel. The description now shows as the tooltip of the element's row in the Elements panel. Hand-written `cards:` are unchanged.
|
||||
- **Model page, Registry panel:** lists the registry files of the model, one collapsible header per file with its element count and an **Open** button that opens the registry view; expand a file to see its elements as cards. Before, all elements of all files were shown in one grid.
|
||||
|
||||
## 0.7.0 — 2026-10-19
|
||||
|
||||
@@ -119,7 +119,7 @@ online-shop/
|
||||
- **The model page.** Click `main.archify` and you get the model's page: its name and description, the overview diagram if the file has one, then two collapsible panels: **Diagrams**, with a collapsible header per folder and a card per diagram (click to open), and **Registry**, one collapsible header per registry file (with an **Open** button for its view) and the file's elements as cards inside, each with the diagrams it is used in. Open or closed is remembered per page.
|
||||
- **Shared elements.** A registry file defines an element once (`type`, `label`, `sublabel`, `brand`, `description`, …). A diagram draws it with `ref: api` plus `pos` and `size`; it may override `label`, `sublabel` and `tag`, and may still define unshared elements inline. Hover a `ref` for the description, F12 jumps to the registry, completion after `ref:` lists the elements, unknown names appear in Problems.
|
||||
- **Links.** A registry element (or a component) with `diagram: 0300-components/api.archify` points at the diagram that explains it: double-click the node in the 2D view to open it.
|
||||
- **Cards.** A model diagram without `cards` of its own shows one card per shared element it draws, from the registry descriptions. The model page shows the registry as the same cards, each with the diagrams it is used in.
|
||||
- **Descriptions.** Under a model diagram, hovering a row of the **Elements** panel shows the registry description of that element.
|
||||
- **Registry view.** A `.archify-registry` file opens as its elements, as cards. Click one and a dialog shows its description, where it is defined and the diagrams that use it; click a diagram to open it. **Show Source** opens the YAML beside it.
|
||||
- **Elements and Connectors.** Under every diagram, Archify's node index is titled **Elements**, and a **Connectors** panel next to it lists every connection as "from → to" with its label. Hovering highlights the connector in the diagram; clicking keeps it highlighted and scrolls to it.
|
||||
- **Starting one.** *Archify | New Model…* (right-click a folder) copies one of the shipped examples into a new folder, named as you choose: **C4 - Standard Web App** (single-page app, API, database, cache, storage, identity provider), **C4 - E-Shop** (web shop, API, worker, queue, payment and email providers) or **C4 - Event-Based Integration** (event bus, router, dead-letter queue, event store, consumers). Each has a registry and context, container and component diagrams. For an empty model, *Archify | New Diagram… → Model* creates just a `main.archify`.
|
||||
|
||||
+2
-2
@@ -64,9 +64,9 @@ components:
|
||||
- Before Archify runs, the extension replaces each `ref` component with the registry element plus the diagram's own fields, drops `description` and `diagram`, and hands Archify an ordinary component.
|
||||
- Connections, boundaries and everything else stay as they are and use the ids as before.
|
||||
|
||||
## Cards under a model diagram
|
||||
## Descriptions under a model diagram
|
||||
|
||||
Archify shows `cards` (a coloured dot, a title, a few lines) below a diagram. A diagram inside a model that draws shared elements and defines no `cards` of its own gets one card per drawn element that has a `description` in the registry, in drawing order, with the dot colour of the element's type. Write your own `cards:` in the diagram to replace them.
|
||||
Archify's **Elements** panel under a diagram lists its nodes. For a `ref` component the row's tooltip is the element's registry `description`. Archify's own `cards:` stay what they are: write them in the diagram when you want text blocks below it; nothing is generated.
|
||||
|
||||
## Links between diagrams
|
||||
|
||||
|
||||
+7
-48
@@ -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. */
|
||||
|
||||
@@ -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 };
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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); });
|
||||
|
||||
+5
-17
@@ -3,8 +3,6 @@ import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { test } from 'node:test';
|
||||
import {
|
||||
cardsFromElements,
|
||||
dotForType,
|
||||
groupOf,
|
||||
hasDiagramBody,
|
||||
isMainFile,
|
||||
@@ -83,22 +81,12 @@ test('resolve: refs become full components, links and usage are collected', () =
|
||||
assert.deepEqual([...r.used].sort(), ['api', 'cache', 'customer', 'db', 'queue', 'web', 'worker']);
|
||||
assert.equal(r.refs.get(2)?.id, 'api');
|
||||
assert.ok(!('model' in r.diagram));
|
||||
// Cards from the registry descriptions, in drawing order, one per element.
|
||||
const cards = r.diagram.cards as { dot: string; title: string; items: string[] }[];
|
||||
assert.equal(cards.length, 7);
|
||||
assert.deepEqual(cards[0], { dot: 'slate', title: 'Customer', items: ['Buys products; signs in with email or a social account.'] });
|
||||
assert.equal(cards.find((c) => c.title === 'API')?.dot, 'emerald');
|
||||
});
|
||||
|
||||
test('resolve: own cards win, no cards without refs or descriptions', () => {
|
||||
const registry = registryOfModel();
|
||||
// Descriptions travel with the node ids, for the Elements panel; nothing is added to the diagram itself.
|
||||
assert.ok(!('cards' in r.diagram));
|
||||
assert.equal(r.descriptions.get('customer'), 'Buys products; signs in with email or a social account.');
|
||||
assert.equal(r.descriptions.size, 7);
|
||||
const own = resolveRefs({ components: [{ ref: 'api', pos: [0, 0] }], cards: [{ dot: 'rose', title: 'Mine', items: ['kept'] }] }, { registry, fileName: 'x.archify' });
|
||||
assert.deepEqual(own.diagram.cards, [{ dot: 'rose', title: 'Mine', items: ['kept'] }]);
|
||||
const inline = resolveRefs({ components: [{ id: 'a', type: 'backend', label: 'A', pos: [0, 0] }] }, { registry, fileName: 'x.archify' });
|
||||
assert.ok(!('cards' in inline.diagram));
|
||||
assert.equal(cardsFromElements([{ id: 'x', type: 'cloud', label: 'X', file: 'r', line: 0 }]).length, 0);
|
||||
assert.equal(dotForType('messagebus'), 'orange');
|
||||
assert.equal(dotForType(undefined), 'slate');
|
||||
assert.deepEqual(own.diagram.cards, [{ dot: 'rose', title: 'Mine', items: ['kept'] }], 'hand-written cards pass through');
|
||||
});
|
||||
|
||||
test('resolve: overrides, second id for the same element, inline elements and links', () => {
|
||||
|
||||
Reference in New Issue
Block a user