0.9.2: Source view, schema checks without the YAML extension

Every Archify page can show its file's YAML in place. Diagrams get
Diagram | Graph | Source (Graph only inside a model), main.archify
Model | Graph | Source, registry and decision pages View | Source, and the
Graph page a Source segment. 2D | 3D moves left of that switch and hides
while Source shows.

The YAML opens in a CodeMirror editor inside the page (dist/source.js,
loaded on first use), themed with the page's colours. Edits go to the VS
Code document as WorkspaceEdits; outside changes come back to the page.
Undo and redo are the editor's own, since VS Code sends ⌘Z in a focused
webview to the webview; save and find stay VS Code's. Problems come from
the host only (what the Problems panel holds for the file) and are
counted on the Source segment; completion and hover come from the schema,
folded into one draft-07 document per diagram type.

The host now checks .archify files against the extension's schema with
ajv on the YAML as written, before archify validate, and checks
.archify-registry files against the registry schema for the first time.
Registry and decision pages become text editor providers. While Source
shows, new renders wait and appear when the picture comes back.

Show Source switches the page to Source; Open in Text Editor (the editor
title button) opens the text beside it, as Show Source did. The decision
schema is no longer given to the YAML extension. Fixed: YAML errors in
registry and decision files were reported on line 1; "Webview is
disposed" when a diagram closed mid-render; the model heading under the
toolbar in narrow editors.
This commit is contained in:
root-at-skic committed 2026-10-27 23:46:05 +02:00
1 parent 5bcd34b839
commit 27d4794ff8
32 files changed
+2486 -127

No files matched your search

+33 -12
View File
@@ -5,31 +5,41 @@ import { RegistryPanel, registryPanelFromIndex, registryPanelStandalone } from '
import { ModelService } from '../model/service';
import { readText } from '../util/files';
import { log } from '../util/log';
import { sourceScriptUri } from '../webview/viewer3d';
import { SourceSession } from './sourceSession';
import { handleWebviewMessage, WebviewMessage } from '../webview/messages';
import { makeNonce, registryPage } from '../webview/page';
import { DECISION_VIEW_TYPE, DIAGRAM_EDITOR_VIEW_TYPE, REGISTRY_VIEW_TYPE } from './viewTypes';
export { REGISTRY_VIEW_TYPE };
class RegistryDocument implements vscode.CustomDocument {
constructor(readonly uri: vscode.Uri) {}
dispose(): void {}
}
class RegistryView implements vscode.Disposable {
private seq = 0;
private timer: NodeJS.Timeout | undefined;
private last: string | undefined;
root: vscode.Uri | undefined;
private readonly disposables: vscode.Disposable[] = [];
/** The Source view; while it shows, a new page waits in `heldHtml`. */
readonly source: SourceSession;
private heldHtml: string | undefined;
constructor(
private readonly context: vscode.ExtensionContext,
private readonly models: ModelService,
readonly panel: vscode.WebviewPanel,
readonly uri: vscode.Uri,
) {
this.source = new SourceSession(context, panel, uri, 'registry', (shown) => {
if (!shown && this.heldHtml !== undefined) {
const html = this.heldHtml;
this.heldHtml = undefined;
this.panel.webview.html = html;
}
});
this.disposables.push(
this.source,
panel.webview.onDidReceiveMessage((message: WebviewMessage) => {
if (this.source.handle(message)) return;
if (message.type === 'openDiagram') return void this.openInModel(message.path, DIAGRAM_EDITOR_VIEW_TYPE);
if (message.type === 'openRegistry') return void this.openRegistryLine(message.file, message.line);
if (message.type === 'openDecision') return void this.openInModel(message.file, DECISION_VIEW_TYPE);
@@ -84,7 +94,12 @@ class RegistryView implements vscode.Disposable {
const fingerprint = JSON.stringify(registry);
if (fingerprint === this.last) return;
this.last = fingerprint;
this.panel.webview.html = registryPage({ cspSource: this.panel.webview.cspSource, nonce: makeNonce() }, registry);
const html = registryPage(
{ cspSource: this.panel.webview.cspSource, nonce: makeNonce(), source: { scriptUri: sourceScriptUri(this.panel.webview, this.context.extensionUri), left: 'View' } },
registry,
);
if (this.source.shown) this.heldHtml = html;
else this.panel.webview.html = html;
}
dispose(): void {
@@ -93,12 +108,15 @@ class RegistryView implements vscode.Disposable {
}
}
export class RegistryViewProvider implements vscode.CustomReadonlyEditorProvider<RegistryDocument>, vscode.Disposable {
export class RegistryViewProvider implements vscode.CustomTextEditorProvider, vscode.Disposable {
private readonly views = new Set<RegistryView>();
private active: RegistryView | undefined;
private readonly disposables: vscode.Disposable[];
constructor(private readonly models: ModelService) {
constructor(
private readonly context: vscode.ExtensionContext,
private readonly models: ModelService,
) {
this.disposables = [
vscode.window.registerCustomEditorProvider(REGISTRY_VIEW_TYPE, this, {
webviewOptions: { retainContextWhenHidden: true, enableFindWidget: true },
@@ -121,13 +139,16 @@ export class RegistryViewProvider implements vscode.CustomReadonlyEditorProvider
return this.active?.uri;
}
openCustomDocument(uri: vscode.Uri): RegistryDocument {
return new RegistryDocument(uri);
/** The view of the focused page, if any. */
get activeView(): { source: SourceSession } | undefined {
return this.active;
}
async resolveCustomEditor(document: RegistryDocument, panel: vscode.WebviewPanel): Promise<void> {
// A text editor provider: the page edits the file's own TextDocument (in
// its Source view), so dirty state, save and undo are VS Code's.
async resolveCustomTextEditor(document: vscode.TextDocument, panel: vscode.WebviewPanel): Promise<void> {
panel.webview.options = { enableScripts: true };
const view = new RegistryView(this.models, panel, document.uri);
const view = new RegistryView(this.context, this.models, panel, document.uri);
this.views.add(view);
const onViewState = () => {
if (panel.active) this.active = view;