Compare commits

..
Author SHA1 Message Date
root-at-skic 517697197e Sync button sits in the view switch's row on every page
Model, registry and decision pages: next to the switch, not floating above
it. Graph page: after Export PNG (its header is built by graph.js a moment
later, so the button waits for it) instead of covering Export. Sized to
the controls beside it.
2026-11-08 20:59:16 +02:00
root-at-skic c71296b01a 0.10.8: main.archify opens with its top-level panels collapsed
Shared, Diagrams, Registries and Decisions start closed on the model page;
folders inside keep their own default. Open or closed is still kept in
the webview state while the page is open.
2026-11-08 20:48:35 +02:00
root-at-skic 86a8adf03b Merge pull request '0.10.7: Model | Graph | Source styled like 2D | 3D' (#38) from fix/mode-switch-style into main 2026-11-08 10:37:07 -08:00
root-at-skic 6153c7f145 0.10.7: Model | Graph | Source styled like 2D | 3D
Archify gives every toolbar button a 2.75rem minimum height and a painted
::before; the switch's segments are buttons, so they grew to full height.
Segments now keep 1.75rem inside a 2.75rem switch, like 2D | 3D. The high
contrast rule no longer turns a shared model's Sync button blue.
2026-11-08 20:35:48 +02:00
root-at-skic 885977c4aa Merge pull request '0.10.6: Sync sign-in comes back through 127.0.0.1' (#37) from fix/sync-signin-loopback into main 2026-11-08 10:06:32 -08:00
root-at-skic 95c6e60199 0.10.6: Sync sign-in comes back through 127.0.0.1
Browsers open a vscode:// link only after a click on the page. With a live
identity provider session the sign-in ran without one, so the editor kept
waiting. On the desktop the browser now returns to a one-off listener on
127.0.0.1; remote and web editors keep the URI handler.
2026-11-08 20:05:11 +02:00
root-at-skic b54304fef8 Merge pull request '0.10.5: icon-only Sync button that keeps its size and colour' (#36) from fix/sync-button-icon into main 2026-11-08 07:59:32 -08:00
root-at-skic 8407c9a956 0.10.5: icon-only Sync button that keeps its size and colour
On a diagram page it goes last in Archify's toolbar, after Export, instead
of floating over it. Every rule is !important and flex: none, so the page's
button styles, narrow windows and high contrast themes no longer squeeze it
or strip its colour.
2026-11-08 17:45:17 +02:00
6 changed files with 107 additions and 25 deletions

No files matched your search

+18
View File
@@ -1,5 +1,23 @@
# Changelog # Changelog
## 0.10.8 — 2026-11-08
- **A model opens as its outline.** On `main.archify` the top-level panels (Shared, Diagrams, Registries, Decisions) start collapsed; open the one you need. What you open or close is kept while the page is open.
- **Sync button in line on every page.** On the model, registry and decision pages it sits next to Model | Graph | Source instead of floating above it, and on the Graph page it comes after Export PNG instead of covering it. It takes the height of the controls beside it.
## 0.10.7 — 2026-11-08
- **Model | Graph | Source looks like 2D | 3D.** The selected view is the same inset pill, the same height as the other toolbar controls.
- The Sync button of a shared model is green in high contrast themes too.
## 0.10.6 — 2026-11-08
- **Sync sign-in comes back by itself.** The browser now returns the sign-in to a one-off port on this machine (`127.0.0.1`) instead of a `vscode://` link. Browsers open such links only after a click, so when you were already signed in to the identity provider the editor kept waiting. Remote and web editors still use the link.
## 0.10.5 — 2026-11-08
- **Sync button fixed.** It is now an icon only (a cloud; green with a broadcast icon once the model is shared), the same size on every page and theme. On a diagram it sits in the toolbar after **Export** instead of over it; in narrow windows and high contrast themes it no longer shrinks or loses its colour.
## 0.10.4 — 2026-11-07 ## 0.10.4 — 2026-11-07
- **A real Sync button.** Every Archify page (diagram, model, graph, registry, decision, and in Source) shows a **Sync** button top right. It opens the Sync menu: sign in, share the model, join one, set the server address. On a model that is shared it turns green and reads **Shared**. - **A real Sync button.** Every Archify page (diagram, model, graph, registry, decision, and in Source) shows a **Sync** button top right. It opens the Sync menu: sign in, share the model, join one, set the server address. On a model that is shared it turns green and reads **Shared**.
+1 -1
View File
@@ -140,7 +140,7 @@ Optional. Without a sync server the extension works exactly as described above a
[Archify Sync](https://gitea.lego-cloud.eu/vscode-extensions/archify-vscode-ext-sync) is a small server you run yourself (one container and PostgreSQL; a Helm chart is included). With it, a model is edited by several people at once, like a shared document, and its files stay ordinary files on each person's disk, so Git, agents and every page of this extension keep working. [Archify Sync](https://gitea.lego-cloud.eu/vscode-extensions/archify-vscode-ext-sync) is a small server you run yourself (one container and PostgreSQL; a Helm chart is included). With it, a model is edited by several people at once, like a shared document, and its files stay ordinary files on each person's disk, so Git, agents and every page of this extension keep working.
- **Where.** Every Archify page (diagram, model, graph, registry, decision, also in Source) has a **Sync** button top right. It opens the Sync menu: sign in, share the model, join one, the server address, settings. On a shared model it turns green and reads **Shared**. The same menu is *Archify | Sync…* in the Command Palette. - **Where.** Every Archify page (diagram, model, graph, registry, decision, also in Source) has a Sync button (a cloud) top right; on a diagram it sits after **Export**. It opens the Sync menu: sign in, share the model, join one, the server address, settings. On a shared model it turns green with a broadcast icon. The same menu is *Archify | Sync…* in the Command Palette.
- **Server address.** Set `archify.sync.server` once (Settings › Archify › Sync, or *Archify | Set Sync Server Address…*), for example `https://archify.example.com`. Put it in the workspace settings and everyone in the project uses the same server. Left empty, the address is asked for once and remembered. - **Server address.** Set `archify.sync.server` once (Settings › Archify › Sync, or *Archify | Set Sync Server Address…*), for example `https://archify.example.com`. Put it in the workspace settings and everyone in the project uses the same server. Left empty, the address is asked for once and remembered.
- **Sign in.** *Archify | Sign in to Sync Server…* uses that address (or asks for it) and signs in through the browser; the session is kept in VS Code's secret storage and shows under **Accounts** as *Archify Sync*. A new server is set up first on its `/setup` page with the code from its log. - **Sign in.** *Archify | Sign in to Sync Server…* uses that address (or asks for it) and signs in through the browser; the session is kept in VS Code's secret storage and shows under **Accounts** as *Archify Sync*. A new server is set up first on its `/setup` page with the code from its log.
- **Share.** *Archify | Share Model…* (also on `main.archify` in the Explorer) puts the model on the server and writes a small `.archify-sync` file next to `main.archify`: the server and the model's id, no secrets. Commit it, and everyone who clones the repository and is a member opens the shared model. - **Share.** *Archify | Share Model…* (also on `main.archify` in the Explorer) puts the model on the server and writes a small `.archify-sync` file next to `main.archify`: the server and the model's id, no secrets. Commit it, and everyone who clones the repository and is a member opens the shared model.
+2 -2
View File
@@ -1,12 +1,12 @@
{ {
"name": "archify-vscode-ext", "name": "archify-vscode-ext",
"version": "0.10.4", "version": "0.10.8",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "archify-vscode-ext", "name": "archify-vscode-ext",
"version": "0.10.4", "version": "0.10.8",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@codemirror/autocomplete": "^6.20.4", "@codemirror/autocomplete": "^6.20.4",
+1 -1
View File
@@ -2,7 +2,7 @@
"name": "archify-vscode-ext", "name": "archify-vscode-ext",
"displayName": "Archify Diagram Viewer", "displayName": "Archify Diagram Viewer",
"description": "Archify diagrams in VS Code. Write diagrams as .archify files in YAML: they open as the interactive diagram, with live updates, validation, templates and completion. Also previews Archify's own JSON files (*.architecture.json, …) and opens rendered Archify HTML.", "description": "Archify diagrams in VS Code. Write diagrams as .archify files in YAML: they open as the interactive diagram, with live updates, validation, templates and completion. Also previews Archify's own JSON files (*.architecture.json, …) and opens rendered Archify HTML.",
"version": "0.10.4", "version": "0.10.8",
"publisher": "root-at-skic", "publisher": "root-at-skic",
"license": "MIT", "license": "MIT",
"sponsor": { "sponsor": {
+36 -3
View File
@@ -1,4 +1,5 @@
import * as crypto from 'node:crypto'; import * as crypto from 'node:crypto';
import * as http from 'node:http';
import * as vscode from 'vscode'; import * as vscode from 'vscode';
import { SyncApi, SyncApiError } from './api'; import { SyncApi, SyncApiError } from './api';
import { Account, TokenResponse } from './protocol'; import { Account, TokenResponse } from './protocol';
@@ -100,8 +101,13 @@ export class SyncAuth implements vscode.AuthenticationProvider, vscode.Disposabl
const verifier = crypto.randomBytes(32).toString('base64url'); const verifier = crypto.randomBytes(32).toString('base64url');
const challenge = crypto.createHash('sha256').update(verifier).digest('base64url'); const challenge = crypto.createHash('sha256').update(verifier).digest('base64url');
const state = crypto.randomBytes(16).toString('base64url'); const state = crypto.randomBytes(16).toString('base64url');
const callback = await vscode.env.asExternalUri(vscode.Uri.parse(`${vscode.env.uriScheme}://${this.context.extension.id}/auth`)); // On the desktop the browser comes back to a port on this machine: no
const redirectUri = callback.toString(true); // click needed, and it reaches this window. Remote and web editors use
// the extension's URI handler (vscode://…/auth) instead.
const loopback = vscode.env.remoteName === undefined && vscode.env.uiKind === vscode.UIKind.Desktop ? await this.listen().catch(() => undefined) : undefined;
const redirectUri = loopback
? `http://127.0.0.1:${loopback.port}/auth`
: (await vscode.env.asExternalUri(vscode.Uri.parse(`${vscode.env.uriScheme}://${this.context.extension.id}/auth`))).toString(true);
const query = new URLSearchParams({ const query = new URLSearchParams({
response_type: 'code', response_type: 'code',
redirect_uri: redirectUri, redirect_uri: redirectUri,
@@ -119,6 +125,7 @@ export class SyncAuth implements vscode.AuthenticationProvider, vscode.Disposabl
const done = () => { const done = () => {
clearTimeout(timer); clearTimeout(timer);
this.pending.delete(state); this.pending.delete(state);
loopback?.close();
}; };
this.pending.set(state, { this.pending.set(state, {
resolve: (c) => (done(), resolve(c)), resolve: (c) => (done(), resolve(c)),
@@ -137,9 +144,35 @@ export class SyncAuth implements vscode.AuthenticationProvider, vscode.Disposabl
return session; return session;
} }
/** A one-off HTTP listener on 127.0.0.1 for the browser's way back; closed when the sign-in ends. */
private listen(): Promise<{ port: number; close: () => void }> {
return new Promise((resolve, reject) => {
const server = http.createServer((req, res) => {
const url = new URL(req.url ?? '/', 'http://127.0.0.1');
const known = url.pathname === '/auth' && this.pending.has(url.searchParams.get('state') ?? '');
res.writeHead(known ? 200 : 404, { 'content-type': 'text/html; charset=utf-8', 'cache-control': 'no-store' });
res.end(
known
? '<!doctype html><meta charset="utf-8"><title>Archify Sync</title><body style="font:15px system-ui;margin:48px auto;max-width:420px"><h2>Signed in</h2><p>You can close this tab and go back to the editor.</p></body>'
: 'Not found',
);
if (known) this.completeSignIn(url.searchParams);
});
server.once('error', reject);
server.listen(0, '127.0.0.1', () => {
const address = server.address();
if (!address || typeof address === 'string') return reject(new Error('no port'));
resolve({ port: address.port, close: () => server.close() });
});
});
}
/** The browser came back: `…/auth?code=&state=`. */ /** The browser came back: `…/auth?code=&state=`. */
handleAuthUri(uri: vscode.Uri): void { handleAuthUri(uri: vscode.Uri): void {
const query = new URLSearchParams(uri.query); this.completeSignIn(new URLSearchParams(uri.query));
}
private completeSignIn(query: URLSearchParams): void {
const state = query.get('state') ?? ''; const state = query.get('state') ?? '';
const code = query.get('code'); const code = query.get('code');
const pending = this.pending.get(state); const pending = this.pending.get(state);
+49 -18
View File
@@ -208,6 +208,7 @@ function bridgeScript(options: PageOptions): string {
}, true); }, true);
// Collapsible panels of the model page; open/closed is kept in the webview state. // Collapsible panels of the model page; open/closed is kept in the webview state.
// The model page's top-level panels start closed: a model opens as its outline.
function panelStates() { return (vscode.getState() || {}).modelPanels || {}; } function panelStates() { return (vscode.getState() || {}).modelPanels || {}; }
function panel(key, className, title, count, defaultOpen) { function panel(key, className, title, count, defaultOpen) {
// count: a number or a short text such as "3 files". // count: a number or a short text such as "3 files".
@@ -624,7 +625,7 @@ function bridgeScript(options: PageOptions): string {
live.hidden = true; live.hidden = true;
body.appendChild(live); body.appendChild(live);
var count = model.groups.reduce(function (n, g) { return n + g.diagrams.length; }, 0); var count = model.groups.reduce(function (n, g) { return n + g.diagrams.length; }, 0);
var diagramsPanel = panel('diagrams', 'archify-model-diagrams', 'Diagrams', count, true); var diagramsPanel = panel('diagrams', 'archify-model-diagrams', 'Diagrams', count, false);
body.appendChild(diagramsPanel); 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.')); 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) { model.groups.forEach(function (group) {
@@ -658,7 +659,7 @@ function bridgeScript(options: PageOptions): string {
byFile[element.file].push(element); byFile[element.file].push(element);
}); });
files.sort(); files.sort();
var registryPanel = panel('registry', 'archify-model-registry', 'Registries', files.length + (files.length === 1 ? ' file' : ' files'), true); var registryPanel = panel('registry', 'archify-model-registry', 'Registries', files.length + (files.length === 1 ? ' file' : ' files'), false);
body.appendChild(registryPanel); body.appendChild(registryPanel);
files.forEach(function (file) { files.forEach(function (file) {
var elements = byFile[file].slice().sort(function (a, b) { return a.line - b.line; }); var elements = byFile[file].slice().sort(function (a, b) { return a.line - b.line; });
@@ -676,7 +677,7 @@ function bridgeScript(options: PageOptions): string {
} }
// Decisions: one list, live ones first, superseded and rejected dimmed and last. // Decisions: one list, live ones first, superseded and rejected dimmed and last.
if (model.decisions && model.decisions.length) { if (model.decisions && model.decisions.length) {
var decisionsPanel = panel('decisions', 'archify-model-decisions', 'Decisions', model.decisions.length, true); var decisionsPanel = panel('decisions', 'archify-model-decisions', 'Decisions', model.decisions.length, false);
body.appendChild(decisionsPanel); body.appendChild(decisionsPanel);
var decisionList = el('ul', 'archify-model-list archify-decision-list'); var decisionList = el('ul', 'archify-model-list archify-decision-list');
model.decisions.forEach(function (row) { decisionList.appendChild(decisionRowItem(row)); }); model.decisions.forEach(function (row) { decisionList.appendChild(decisionRowItem(row)); });
@@ -723,7 +724,7 @@ function bridgeScript(options: PageOptions): string {
if (!info) { holder.hidden = true; return; } if (!info) { holder.hidden = true; return; }
holder.hidden = false; holder.hidden = false;
var others = info.people.filter(function (p) { return !p.me; }); var others = info.people.filter(function (p) { return !p.me; });
var livePanel = panel('live', 'archify-model-live is-' + info.state, 'Shared \u00b7 ' + info.name, others.length ? others.length + 1 + ' here' : info.host, true); var livePanel = panel('live', 'archify-model-live is-' + info.state, 'Shared \u00b7 ' + info.name, others.length ? others.length + 1 + ' here' : info.host, false);
var status = el('div', 'archify-live-status'); var status = el('div', 'archify-live-status');
status.appendChild(el('span', 'archify-live-dot')); status.appendChild(el('span', 'archify-live-dot'));
status.appendChild(el('span', 'archify-live-state', (LIVE_STATE[info.state] || info.state) + (info.state === 'online' ? ' on ' + info.host : ''))); status.appendChild(el('span', 'archify-live-state', (LIVE_STATE[info.state] || info.state) + (info.state === 'online' ? ' on ' + info.host : '')));
@@ -888,7 +889,7 @@ function bridgeScript(options: PageOptions): string {
onReady(describeElements); onReady(describeElements);
onReady(renderModeSwitch); onReady(renderModeSwitch);
// Archify Sync: one labelled button, top right, on every page of an Archify // Archify Sync: one icon button, top right, on every page of an Archify
// file (diagram, model, graph, registry, decision). It opens the Sync menu. // file (diagram, model, graph, registry, decision). It opens the Sync menu.
function renderSyncButton() { function renderSyncButton() {
if (!(config.source || config.model || config.registry || config.decision || config.graph)) return; if (!(config.source || config.model || config.registry || config.decision || config.graph)) return;
@@ -901,35 +902,62 @@ function bridgeScript(options: PageOptions): string {
button.type = 'button'; button.type = 'button';
button.title = 'Archify Sync: sign in, share this model with others, or join one'; button.title = 'Archify Sync: sign in, share this model with others, or join one';
button.addEventListener('click', function (event) { event.stopPropagation(); post({ type: 'syncMenu' }); }); button.addEventListener('click', function (event) { event.stopPropagation(); post({ type: 'syncMenu' }); });
// Last in the row that holds the view switch: Archify's toolbar on a
// diagram (after Export), the Graph page's header actions (after Export,
// built a moment later by graph.js), next to the switch on the other
// pages. It floats only while there is no such row.
document.body.appendChild(button); document.body.appendChild(button);
function place() {
var modeSwitch = document.getElementById('archify-mode-switch');
var row = document.querySelector('.ag-actions') || (modeSwitch && modeSwitch.parentElement) || document.querySelector('body > .toolbar');
if (!row || row === document.body) return false;
button.classList.add('in-toolbar');
row.classList.add('archify-sync-row');
row.appendChild(button);
return true;
}
if (!place() && window.MutationObserver) {
var watch = new MutationObserver(function () { if (place()) watch.disconnect(); });
watch.observe(document.body, { childList: true, subtree: true });
}
showSyncState(false); showSyncState(false);
post({ type: 'syncReady' }); post({ type: 'syncReady' });
} }
// Not shared: a cloud, "Sync". Shared (the model has an .archify-sync file): broadcast waves, "Shared". // Not shared: a cloud. Shared (the model has an .archify-sync file): broadcast waves on green.
var SYNC_ICONS = { var SYNC_ICONS = {
off: '<svg viewBox="0 0 16 16" width="15" height="15" aria-hidden="true"><path fill="currentColor" d="M12.5 13H4.25a3.25 3.25 0 0 1-.6-6.44 4.5 4.5 0 0 1 8.7-.81A3.6 3.6 0 0 1 12.5 13Zm-8.25-1h8.25a2.6 2.6 0 0 0 0-5.2h-.5l-.12-.39a3.5 3.5 0 0 0-6.82.66l-.04.4-.4.05A2.25 2.25 0 0 0 4.25 12Z"/></svg>', off: '<svg viewBox="0 0 16 16" width="16" height="16" aria-hidden="true"><path fill="currentColor" d="M12.5 13H4.25a3.25 3.25 0 0 1-.6-6.44 4.5 4.5 0 0 1 8.7-.81A3.6 3.6 0 0 1 12.5 13Zm-8.25-1h8.25a2.6 2.6 0 0 0 0-5.2h-.5l-.12-.39a3.5 3.5 0 0 0-6.82.66l-.04.4-.4.05A2.25 2.25 0 0 0 4.25 12Z"/></svg>',
on: '<svg viewBox="0 0 16 16" width="15" height="15" aria-hidden="true"><circle cx="8" cy="8" r="1.75" fill="currentColor"/><path fill="none" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" d="M5.2 5.2a4 4 0 0 0 0 5.6M10.8 5.2a4 4 0 0 1 0 5.6M3.1 3.1a7 7 0 0 0 0 9.8M12.9 3.1a7 7 0 0 1 0 9.8"/></svg>', on: '<svg viewBox="0 0 16 16" width="16" height="16" aria-hidden="true"><circle cx="8" cy="8" r="1.75" fill="currentColor"/><path fill="none" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" d="M5.2 5.2a4 4 0 0 0 0 5.6M10.8 5.2a4 4 0 0 1 0 5.6M3.1 3.1a7 7 0 0 0 0 9.8M12.9 3.1a7 7 0 0 1 0 9.8"/></svg>',
}; };
function showSyncState(shared) { function showSyncState(shared) {
var button = document.getElementById('archify-sync-button'); var button = document.getElementById('archify-sync-button');
if (!button) return; if (!button) return;
button.classList.toggle('is-shared', !!shared); button.classList.toggle('is-shared', !!shared);
button.innerHTML = (shared ? SYNC_ICONS.on : SYNC_ICONS.off) + '<span>' + (shared ? 'Shared' : 'Sync') + '</span>'; button.innerHTML = shared ? SYNC_ICONS.on : SYNC_ICONS.off;
button.title = shared button.title = shared
? 'This model is shared with Archify Sync: invite people, publish a version, see versions' ? 'Archify Sync: this model is shared. Invite people, publish a version, see versions'
: 'Archify Sync: sign in, share this model with others, or join one'; : 'Archify Sync: sign in, share this model with others, or join one';
button.setAttribute('aria-label', shared ? 'Archify Sync (shared)' : 'Archify Sync');
} }
window.addEventListener('message', function (event) { window.addEventListener('message', function (event) {
var message = event.data || {}; var message = event.data || {};
if (message.type === 'syncState') showSyncState(message.shared); if (message.type === 'syncState') showSyncState(message.shared);
}); });
var SYNC_BUTTON_STYLE = '#archify-sync-button { position: fixed; top: 12px; right: 18px; z-index: 1001; display: inline-flex; align-items: center; gap: 6px;' // Every rule is !important: the page's own button styles (Archify's toolbar,
+ ' padding: 6px 12px; border-radius: 8px; cursor: pointer; font: 600 13px/1 var(--vscode-font-family, system-ui, sans-serif);' // VS Code's high contrast themes) must not resize or recolour it.
+ ' color: var(--vscode-button-foreground, #fff); background: var(--vscode-button-background, #0e639c); border: 1px solid var(--vscode-button-border, transparent); }' var SYNC_BUTTON_STYLE = '#archify-sync-button { all: unset; position: fixed !important; top: 12px !important; right: 28px !important; z-index: 1001 !important;'
+ ' #archify-sync-button:hover { background: var(--vscode-button-hoverBackground, #1177bb); }' + ' box-sizing: border-box !important; width: 30px !important; height: 30px !important; min-width: 0 !important; min-height: 0 !important; margin: 0 !important; padding: 0 !important;'
+ ' #archify-sync-button.is-shared { color: #fff; background: #1f8f5f; }' + ' display: flex !important; align-items: center !important; justify-content: center !important; border-radius: 8px !important; cursor: pointer !important;'
+ ' #archify-sync-button.is-shared:hover { background: #23a36c; }' + ' color: #fff !important; background: var(--vscode-button-background, #0e639c) !important; border: 1px solid var(--vscode-contrastBorder, transparent) !important;'
+ ' #archify-sync-button:focus-visible { outline: 2px solid var(--vscode-focusBorder, #007fd4); outline-offset: 2px; }'; + ' box-shadow: 0 1px 4px rgba(0,0,0,.35) !important; }'
+ ' .archify-model-switch-row.archify-sync-row { display: flex !important; align-items: center !important; gap: 0.5rem !important; }'
+ ' #archify-sync-button.in-toolbar { position: relative !important; top: auto !important; right: auto !important; align-self: center !important; flex: none !important; width: 36px !important; height: 36px !important; }'
+ ' .ag-actions #archify-sync-button.in-toolbar, .archify-model-switch-row #archify-sync-button.in-toolbar { align-self: stretch !important; height: auto !important; width: auto !important; aspect-ratio: 1 / 1 !important; }'
+ ' body.vscode-high-contrast #archify-sync-button:not(.is-shared) { background: #0e639c !important; }'
+ ' #archify-sync-button svg { display: block !important; width: 16px !important; height: 16px !important; flex: none !important; }'
+ ' #archify-sync-button:hover { background: var(--vscode-button-hoverBackground, #1177bb) !important; }'
+ ' #archify-sync-button.is-shared { background: #1f8f5f !important; }'
+ ' #archify-sync-button.is-shared:hover { background: #23a36c !important; }'
+ ' #archify-sync-button:focus-visible { outline: 2px solid var(--vscode-focusBorder, #007fd4) !important; outline-offset: 2px !important; }';
onReady(renderSyncButton); onReady(renderSyncButton);
if (config.source) { if (config.source) {
// The host answers with the problem count for the Source badge. // The host answers with the problem count for the Source badge.
@@ -1057,10 +1085,13 @@ const MODEL_STYLE = `
/* The Model | Graph switch: in Archify's toolbar it is the 2D | 3D switch's twin; on the model page it sits top right. */ /* The Model | Graph switch: in Archify's toolbar it is the 2D | 3D switch's twin; on the model page it sits top right. */
const SWITCH_STYLE = ` const SWITCH_STYLE = `
.archify-mode-switch { display: inline-flex; align-items: center; padding: 0 0.25rem; gap: 0; color: var(--text-muted, var(--vscode-descriptionForeground)); position: relative; } .archify-mode-switch { display: inline-flex; align-items: center; padding: 0 0.25rem; gap: 0; color: var(--text-muted, var(--vscode-descriptionForeground)); position: relative; }
.toolbar .archify-mode-switch { height: 2.75rem; box-sizing: border-box; }
.toolbar .archify-mode-switch::before { content: ""; position: absolute; inset: 0.25rem 0; border-radius: 0.6875rem; background: color-mix(in srgb, var(--panel) 78%, transparent); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--panel-border) 85%, transparent), 0 1px 2px rgba(15, 23, 42, 0.04); backdrop-filter: blur(12px); } .toolbar .archify-mode-switch::before { content: ""; position: absolute; inset: 0.25rem 0; border-radius: 0.6875rem; background: color-mix(in srgb, var(--panel) 78%, transparent); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--panel-border) 85%, transparent), 0 1px 2px rgba(15, 23, 42, 0.04); backdrop-filter: blur(12px); }
.toolbar .archify-mode-switch:hover::before { box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--text) 22%, transparent), 0 1px 2px rgba(15, 23, 42, 0.04); } .toolbar .archify-mode-switch:hover::before { box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--text) 22%, transparent), 0 1px 2px rgba(15, 23, 42, 0.04); }
/* Anchored on the id: Archify's .toolbar button rules (one of them with :not(#btn-theme)) would win otherwise. */ /* Anchored on the id: Archify's .toolbar button rules (one of them with :not(#btn-theme)) would win otherwise. */
#archify-mode-switch .archify-mode-seg { position: relative; display: inline-flex; align-items: center; height: 1.75rem; padding: 0 0.7rem; border: 0; border-radius: 0.5rem; background: transparent; color: inherit; font: inherit; font-weight: 550; font-size: 0.8125rem; cursor: pointer; transition: background 0.15s, color 0.15s; } #archify-mode-switch .archify-mode-seg { position: relative; display: inline-flex; align-items: center; height: 1.75rem; min-height: 0; margin: 0; padding: 0 0.7rem; border: 0; border-radius: 0.5rem; background: transparent; color: inherit; font: inherit; font-weight: 550; font-size: 0.8125rem; cursor: pointer; transition: background 0.15s, color 0.15s; }
/* Archify's toolbar buttons are 2.75rem tall with a painted ::before; the segments are not, like 2D | 3D. */
#archify-mode-switch .archify-mode-seg::before, #archify-mode-switch .archify-mode-seg::after { content: none; }
#archify-mode-switch .archify-mode-seg.on[aria-pressed="true"] { color: var(--bg, var(--vscode-editor-background)); background: var(--text, var(--vscode-foreground)); box-shadow: 0 1px 2px rgba(15, 23, 42, 0.12), 0 6px 16px -8px rgba(15, 23, 42, 0.4); } #archify-mode-switch .archify-mode-seg.on[aria-pressed="true"] { color: var(--bg, var(--vscode-editor-background)); background: var(--text, var(--vscode-foreground)); box-shadow: 0 1px 2px rgba(15, 23, 42, 0.12), 0 6px 16px -8px rgba(15, 23, 42, 0.4); }
#archify-mode-switch .archify-mode-seg:hover:not(.on) { color: var(--text, var(--vscode-foreground)); } #archify-mode-switch .archify-mode-seg:hover:not(.on) { color: var(--text, var(--vscode-foreground)); }
#archify-mode-switch .archify-mode-seg:focus-visible { outline: 2px solid var(--frontend-stroke, var(--vscode-focusBorder)); outline-offset: 1px; } #archify-mode-switch .archify-mode-seg:focus-visible { outline: 2px solid var(--frontend-stroke, var(--vscode-focusBorder)); outline-offset: 1px; }