Release 0.7.0: models (main.archify, registries, refs, links, model page) #22

Merged
root-at-skic merged 5 commits from feat/model into main 2026-10-19 04:46:19 -07:00
3 changed files with 52 additions and 10 deletions
Showing only changes of commit 36bccb69a5 - Show all commits
+1
View File
@@ -4,6 +4,7 @@
- **Models:** a folder with a `main.archify` at its top is a model, a set of diagrams that belong together. Opening `main.archify` shows the model page: name and description, the overview diagram if the file has one, the diagrams grouped by folder (click to open), and the registry with where each element is used.
- **Registry:** `*.archify-registry` files define shared elements once; diagrams draw them with `ref: <id>` plus position, and may override `label`, `sublabel` and `tag`. Hover, go-to-definition and completion on `ref`; unknown names, forbidden overrides, duplicate ids and broken registries in Problems. Registry edits re-render open diagrams.
- **Model page panels:** Diagrams and Registry are collapsible panels; inside Diagrams every folder has its own collapsible header above its diagram cards. Open or closed is remembered.
- **Cards:** a model diagram without `cards` of its own gets Archify cards from the registry descriptions of the elements it draws; the model page shows the registry as the same cards, each with the diagrams it is used in.
- **Links:** `diagram:` on a registry element or a component points at the diagram that explains it; double-click the node (2D view) to open it.
- **New Model…:** right-click a folder and choose *Archify | New Model…* (also in *File → New File…* and the Command Palette) to start from one of three complete C4 examples: **C4 - Standard Web App**, **C4 - E-Shop**, **C4 - Event-Based Integration**. Each creates a model folder with `main.archify`, a registry and context, container and component diagrams, named after the folder you choose, and opens the model page.
+1 -1
View File
@@ -116,7 +116,7 @@ online-shop/
0300-components/api.archify
```
- **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, the diagrams of the model grouped by folder (click one to open it), and the registry with "used in N diagrams".
- **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**, the shared elements as cards with the diagrams each 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.
+50 -9
View File
@@ -183,6 +183,25 @@ function bridgeScript(options: PageOptions): string {
}
}, true);
// Collapsible panels of the model page; open/closed is kept in the webview state.
function panelStates() { return (vscode.getState() || {}).modelPanels || {}; }
function panel(key, className, title, count, defaultOpen) {
var details = el('details', 'archify-model-panel ' + className);
var saved = panelStates()[key];
details.open = saved === undefined ? defaultOpen : saved;
var summary = el('summary', 'archify-model-summary');
summary.appendChild(el('span', 'archify-model-chevron'));
summary.appendChild(el('span', 'archify-model-summary-title', title));
if (count !== undefined) summary.appendChild(el('span', 'archify-model-count', String(count)));
details.appendChild(summary);
details.addEventListener('toggle', function () {
var states = panelStates();
states[key] = details.open;
vscode.setState(Object.assign({}, vscode.getState() || {}, { modelPanels: states }));
});
return details;
}
var DOT_BY_TYPE = { frontend: 'cyan', backend: 'emerald', database: 'violet', cloud: 'amber', security: 'rose', messagebus: 'orange', external: 'slate' };
function dotForType(type) { return DOT_BY_TYPE[type || ''] || 'slate'; }
@@ -208,10 +227,16 @@ function bridgeScript(options: PageOptions): string {
var body = el('section', 'archify-model archify-model-body');
var count = model.groups.reduce(function (n, g) { return n + g.diagrams.length; }, 0);
body.appendChild(el('h2', 'archify-model-title', 'Diagrams' + (count ? ' · ' + count : '')));
if (!count) body.appendChild(el('p', 'archify-model-empty', 'No diagrams yet. Add .archify files next to main.archify or in subfolders.'));
var diagramsPanel = panel('diagrams', 'archify-model-diagrams', 'Diagrams', count, true);
body.appendChild(diagramsPanel);
if (!count) diagramsPanel.appendChild(el('p', 'archify-model-empty', 'No diagrams yet. Add .archify files next to main.archify or in subfolders.'));
model.groups.forEach(function (group) {
if (group.group) body.appendChild(el('h3', 'archify-model-group', group.group));
// Each folder is its own collapsible header; diagrams next to main.archify are listed directly.
var holder = diagramsPanel;
if (group.group) {
holder = panel('folder:' + group.group, 'archify-model-folder', group.group, group.diagrams.length, true);
diagramsPanel.appendChild(holder);
}
var list = el('ul', 'archify-model-list');
group.diagrams.forEach(function (diagram) {
var item = el('li', 'archify-model-item' + (diagram.parseError ? ' is-broken' : ''));
@@ -224,11 +249,12 @@ function bridgeScript(options: PageOptions): string {
item.appendChild(button);
list.appendChild(item);
});
body.appendChild(list);
holder.appendChild(list);
});
// Registry elements as Archify info cards: the same cards a diagram shows below itself.
if (model.registry.length) {
body.appendChild(el('h2', 'archify-model-title', 'Registry · ' + model.registry.length));
var registryPanel = panel('registry', 'archify-model-registry', 'Registry', model.registry.length, false);
body.appendChild(registryPanel);
var cards = el('div', 'cards archify-model-cards');
model.registry.forEach(function (element) {
var card = el('div', 'card archify-model-card');
@@ -253,7 +279,7 @@ function bridgeScript(options: PageOptions): string {
card.appendChild(list);
cards.appendChild(card);
});
body.appendChild(cards);
registryPanel.appendChild(cards);
}
if (anchor && anchor.parentNode) anchor.parentNode.insertBefore(body, anchor.nextSibling); else document.body.appendChild(body);
}
@@ -287,8 +313,23 @@ const MODEL_STYLE = `
.archify-model-name { margin: 0.25rem 0 0.5rem; font-size: 1.5rem; font-weight: 700; letter-spacing: -0.02em; }
.archify-model-description { margin: 0; max-width: 72ch; font-size: 0.9375rem; line-height: 1.55; color: var(--m-muted); white-space: pre-line; }
.archify-model-problem { margin: 0.75rem 0 0; padding: 0.5rem 0.75rem; border-radius: 0.5rem; border: 1px solid var(--vscode-inputValidation-errorBorder, #be1100); color: var(--m-text); font-size: 0.8125rem; }
.archify-model-title { margin: 1.5rem 0 0.75rem; font-size: 0.875rem; font-weight: 600; letter-spacing: 0.02em; }
.archify-model-group { margin: 1rem 0 0.25rem; font-size: 0.6875rem; font-weight: 700; letter-spacing: 0.1em; text-transform: uppercase; color: var(--m-muted); font-family: ui-monospace, Menlo, monospace; }
/* Collapsible panels: Diagrams and Registry, and one per folder inside Diagrams. */
.archify-model-panel { margin: 1rem 0 0; border: 1px solid var(--m-border); border-radius: 0.75rem; background: var(--m-panel); }
.archify-model-panel[open] { padding-bottom: 0.85rem; }
.archify-model-summary { display: flex; align-items: center; gap: 0.6rem; padding: 0.75rem 0.9rem; cursor: pointer; list-style: none; user-select: none; font-size: 0.875rem; font-weight: 600; letter-spacing: 0.02em; color: var(--m-text); }
.archify-model-summary::-webkit-details-marker { display: none; }
.archify-model-summary:hover { color: var(--m-accent); }
.archify-model-summary:focus-visible { outline: 2px solid var(--m-accent); outline-offset: -2px; border-radius: 0.75rem; }
.archify-model-chevron { flex: none; width: 0.5rem; height: 0.5rem; border-right: 2px solid currentColor; border-bottom: 2px solid currentColor; transform: rotate(-45deg); transition: transform 0.15s; opacity: 0.7; margin-left: 0.1rem; }
.archify-model-panel[open] > .archify-model-summary > .archify-model-chevron { transform: rotate(45deg); }
.archify-model-count { margin-left: auto; font-size: 0.75rem; font-weight: 500; color: var(--m-muted); font-family: ui-monospace, Menlo, monospace; padding: 0.05rem 0.5rem; border: 1px solid var(--m-border); border-radius: 999px; }
.archify-model-panel > .archify-model-list, .archify-model-panel > .archify-model-cards, .archify-model-panel > .archify-model-empty { margin: 0 0.9rem; }
.archify-model-folder { margin: 0.35rem 0.9rem 0; background: transparent; border-radius: 0.6rem; }
.archify-model-folder[open] { padding-bottom: 0.7rem; }
.archify-model-folder > .archify-model-summary { padding: 0.55rem 0.75rem; font-size: 0.6875rem; font-weight: 700; letter-spacing: 0.1em; text-transform: uppercase; color: var(--m-muted); font-family: ui-monospace, Menlo, monospace; }
.archify-model-folder > .archify-model-summary:hover { color: var(--m-accent); }
.archify-model-folder > .archify-model-list { margin: 0 0.75rem; }
.archify-model-diagrams > .archify-model-list { margin-top: 0.25rem; }
.archify-model-list { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: 0.5rem; }
.archify-model-open { display: flex; flex-direction: column; gap: 0.2rem; width: 100%; padding: 0.7rem 0.85rem; text-align: left; cursor: pointer; font: inherit; color: var(--m-text); background: var(--m-panel); border: 1px solid var(--m-border); border-radius: 0.6rem; transition: border-color 0.15s, transform 0.15s; }
.archify-model-open:hover { border-color: var(--m-accent); transform: translateY(-1px); }
@@ -299,7 +340,7 @@ const MODEL_STYLE = `
.archify-model-empty { color: var(--m-muted); }
/* Registry cards: Archify's own .card look, with fallbacks for the page without a diagram. */
.archify-model { --m-frontend: var(--frontend-stroke, #22d3ee); --m-backend: var(--backend-stroke, #34d399); --m-database: var(--database-stroke, #a78bfa); --m-cloud: var(--cloud-stroke, #fbbf24); --m-security: var(--security-stroke, #fb7185); --m-messagebus: var(--messagebus-stroke, #fb923c); --m-external: var(--external-stroke, #94a3b8); }
.archify-model-cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(15rem, 100%), 1fr)); gap: 1rem; margin-top: 0.75rem; }
.archify-model-cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(15rem, 100%), 1fr)); gap: 1rem; margin-top: 0.25rem; }
.archify-model-card { background: var(--m-panel); border: 1px solid var(--m-border); border-radius: 0.75rem; padding: 1.125rem 1.25rem; }
.archify-model-card .card-header { display: flex; align-items: center; gap: 0.6rem; margin-bottom: 0.6rem; }
.archify-model-card .card-dot { flex: none; width: 10px; height: 10px; border-radius: 50%; }