@chrisdudek/yg 5.4.1 → 5.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin.js +153 -41
- package/dist/templates/portal/app.css +278 -0
- package/dist/templates/portal/shell.css +257 -0
- package/dist/templates/portal/shell.html +39 -0
- package/dist/templates/portal/tokens.css +269 -0
- package/dist/templates/portal/views-audit.css +108 -0
- package/dist/templates/portal/views-flows.css +258 -0
- package/dist/templates/portal/views-panel.css +249 -0
- package/dist/templates/portal/views-relations.css +218 -0
- package/dist/templates/portal/views-rulebook.css +279 -0
- package/dist/templates/portal/views-start.css +208 -0
- package/dist/templates/portal/views.css +340 -0
- package/package.json +4 -2
package/dist/bin.js
CHANGED
|
@@ -16157,6 +16157,7 @@ async function runFill(graph, opts) {
|
|
|
16157
16157
|
}
|
|
16158
16158
|
const detPairs = unverifiedPairs.filter((p2) => p2.kind === "deterministic");
|
|
16159
16159
|
const llmPairs = onlyDeterministic ? [] : unverifiedPairs.filter((p2) => p2.kind === "llm");
|
|
16160
|
+
const skippedLlmPairs = onlyDeterministic ? unverifiedPairs.filter((p2) => p2.kind === "llm").length : 0;
|
|
16160
16161
|
const aspectById = /* @__PURE__ */ new Map();
|
|
16161
16162
|
for (const a of graph.aspects) aspectById.set(a.id, a);
|
|
16162
16163
|
const deterministicAspectIds = new Set(
|
|
@@ -16174,6 +16175,12 @@ async function runFill(graph, opts) {
|
|
|
16174
16175
|
`Filling ${unverifiedPairs.length} unverified pairs across ${nodeSet.size} nodes \u2014 ${detPairs.length} deterministic (no cost), ${reviewerCallBudget} reviewer calls (consensus included)
|
|
16175
16176
|
`
|
|
16176
16177
|
);
|
|
16178
|
+
if (skippedLlmPairs > 0) {
|
|
16179
|
+
write(
|
|
16180
|
+
` Deterministic-only mode \u2014 ${skippedLlmPairs} LLM pair${skippedLlmPairs === 1 ? "" : "s"} will NOT be reviewed this run; run \`yg check --approve\` to review ${skippedLlmPairs === 1 ? "it" : "them"}.
|
|
16181
|
+
`
|
|
16182
|
+
);
|
|
16183
|
+
}
|
|
16177
16184
|
if (dryRun) {
|
|
16178
16185
|
const byNode = /* @__PURE__ */ new Map();
|
|
16179
16186
|
for (const p2 of unverifiedPairs) {
|
|
@@ -16214,7 +16221,7 @@ async function runFill(graph, opts) {
|
|
|
16214
16221
|
return writeChain;
|
|
16215
16222
|
};
|
|
16216
16223
|
const setEntry = async (aspectId, unitKey, entry) => {
|
|
16217
|
-
(lock.verdicts[aspectId] ??= {})[unitKey] = entry;
|
|
16224
|
+
(lock.verdicts[aspectId] ??= {})[toPosixPath(unitKey)] = entry;
|
|
16218
16225
|
await persistLock();
|
|
16219
16226
|
};
|
|
16220
16227
|
const blockedNodes = /* @__PURE__ */ new Set();
|
|
@@ -16375,7 +16382,14 @@ async function runFill(graph, opts) {
|
|
|
16375
16382
|
}
|
|
16376
16383
|
await garbageCollectAndRewrite(graph, lock, persistLock);
|
|
16377
16384
|
if (reviewerCallsMade === 0 && infraFailures === 0 && runtimeErrors === 0 && companionRuntimeErrors === 0) {
|
|
16378
|
-
|
|
16385
|
+
if (skippedLlmPairs > 0) {
|
|
16386
|
+
write(
|
|
16387
|
+
`0 reviewer calls made \u2014 deterministic-only mode; ${skippedLlmPairs} LLM pair${skippedLlmPairs === 1 ? "" : "s"} left unverified. Run \`yg check --approve\` to review ${skippedLlmPairs === 1 ? "it" : "them"}.
|
|
16388
|
+
`
|
|
16389
|
+
);
|
|
16390
|
+
} else {
|
|
16391
|
+
write("0 reviewer calls made \u2014 all expected pairs hold valid verdicts\n");
|
|
16392
|
+
}
|
|
16379
16393
|
}
|
|
16380
16394
|
if (infraFailures > 0) {
|
|
16381
16395
|
const providers = [...new Set(infraReport.map((r) => r.provider).filter(Boolean))].join(", ");
|
|
@@ -16709,6 +16723,10 @@ function resolveTopValue(raw) {
|
|
|
16709
16723
|
if (Number.isNaN(n) || n < 1) return null;
|
|
16710
16724
|
return n;
|
|
16711
16725
|
}
|
|
16726
|
+
function nextPointer(next) {
|
|
16727
|
+
const firstLine = next.split("\n")[0];
|
|
16728
|
+
return firstLine.trimEnd().endsWith(":") ? next : firstLine;
|
|
16729
|
+
}
|
|
16712
16730
|
function residualAfterNext(result) {
|
|
16713
16731
|
if (!result.suggestedNext?.startsWith("yg check --approve")) return "";
|
|
16714
16732
|
const errors = result.issues.filter((i) => i.severity === "error");
|
|
@@ -16757,7 +16775,7 @@ function formatOutput(result, view = { kind: "full" }, autoFilled = false, emoji
|
|
|
16757
16775
|
}
|
|
16758
16776
|
const firstFiltered = [...filteredErrors, ...filteredWarnings][0];
|
|
16759
16777
|
if (firstFiltered?.messageData.next) {
|
|
16760
|
-
const nextCmd = firstFiltered.messageData.next
|
|
16778
|
+
const nextCmd = nextPointer(firstFiltered.messageData.next);
|
|
16761
16779
|
sections.push("");
|
|
16762
16780
|
sections.push(`Next (this group): ${nextCmd}`);
|
|
16763
16781
|
sections.push("");
|
|
@@ -16787,7 +16805,7 @@ function formatOutput(result, view = { kind: "full" }, autoFilled = false, emoji
|
|
|
16787
16805
|
}
|
|
16788
16806
|
}
|
|
16789
16807
|
if (result.suggestedNext) {
|
|
16790
|
-
const nextCmd = result.suggestedNext
|
|
16808
|
+
const nextCmd = nextPointer(result.suggestedNext);
|
|
16791
16809
|
const residual = view.kind === "full" || view.kind === "details" ? residualAfterNext(result) : "";
|
|
16792
16810
|
sections.push("");
|
|
16793
16811
|
sections.push(`Next: ${nextCmd}${residual}`);
|
|
@@ -21146,8 +21164,11 @@ cached and FINAL for unchanged inputs. Interrupting is safe \u2014 finished pair
|
|
|
21146
21164
|
persist, the next run resumes.
|
|
21147
21165
|
|
|
21148
21166
|
When nothing was unverified, the summary says \`0 reviewer calls made \u2014 all
|
|
21149
|
-
expected pairs hold valid verdicts\`.
|
|
21150
|
-
|
|
21167
|
+
expected pairs hold valid verdicts\`. Under \`--only-deterministic\` the header and
|
|
21168
|
+
summary instead name the LLM pairs left unverified \u2014 they are skipped by design,
|
|
21169
|
+
not reviewed \u2014 and point at a full \`yg check --approve\` to review them, so a
|
|
21170
|
+
deterministic-only run never reads as if it verified everything. Use \`yg impact\`
|
|
21171
|
+
to predict cost before editing.
|
|
21151
21172
|
|
|
21152
21173
|
\`--dry-run\` (with \`--approve\`) is a free cost preview: it runs the same
|
|
21153
21174
|
structural gate, pair classification, and budget computation, prints the
|
|
@@ -23236,16 +23257,6 @@ async function computePortalFreshness(graph, lock) {
|
|
|
23236
23257
|
}
|
|
23237
23258
|
|
|
23238
23259
|
// src/portal/derive-node-state.ts
|
|
23239
|
-
var PAIR_RANK = {
|
|
23240
|
-
refused: 4,
|
|
23241
|
-
unverified: 3,
|
|
23242
|
-
warning: 2,
|
|
23243
|
-
verified: 1,
|
|
23244
|
-
"n/a": 0
|
|
23245
|
-
};
|
|
23246
|
-
function worstPairState(states) {
|
|
23247
|
-
return states.reduce((worst, s) => PAIR_RANK[s] > PAIR_RANK[worst] ? s : worst, "verified");
|
|
23248
|
-
}
|
|
23249
23260
|
var STATE_RANK = {
|
|
23250
23261
|
refused: 4,
|
|
23251
23262
|
unverified: 3,
|
|
@@ -23297,6 +23308,18 @@ function computeNotApplicable(node, graph, effectiveIds) {
|
|
|
23297
23308
|
return out;
|
|
23298
23309
|
}
|
|
23299
23310
|
|
|
23311
|
+
// src/portal/derive-pair-state.ts
|
|
23312
|
+
var PAIR_RANK = {
|
|
23313
|
+
refused: 4,
|
|
23314
|
+
unverified: 3,
|
|
23315
|
+
warning: 2,
|
|
23316
|
+
verified: 1,
|
|
23317
|
+
"n/a": 0
|
|
23318
|
+
};
|
|
23319
|
+
function worstPairState(states) {
|
|
23320
|
+
return states.reduce((worst, s) => PAIR_RANK[s] > PAIR_RANK[worst] ? s : worst, "verified");
|
|
23321
|
+
}
|
|
23322
|
+
|
|
23300
23323
|
// src/portal/derive-nodes.ts
|
|
23301
23324
|
function displayPairState(rawKind, status) {
|
|
23302
23325
|
if (rawKind === "verified") return "verified";
|
|
@@ -23614,28 +23637,6 @@ function buildTypes(graph) {
|
|
|
23614
23637
|
}
|
|
23615
23638
|
|
|
23616
23639
|
// src/portal/derive-rest.ts
|
|
23617
|
-
function buildBoundary(input) {
|
|
23618
|
-
if (input === null) {
|
|
23619
|
-
return { phantom: [], declaredOnly: [], forbiddenType: [], unknown: true };
|
|
23620
|
-
}
|
|
23621
|
-
return {
|
|
23622
|
-
phantom: dedupeEdges(input.phantom),
|
|
23623
|
-
declaredOnly: dedupeEdges(input.declaredOnly),
|
|
23624
|
-
forbiddenType: dedupeEdges(input.forbiddenType),
|
|
23625
|
-
unknown: false
|
|
23626
|
-
};
|
|
23627
|
-
}
|
|
23628
|
-
function dedupeEdges(edges) {
|
|
23629
|
-
const seen = /* @__PURE__ */ new Set();
|
|
23630
|
-
const out = [];
|
|
23631
|
-
for (const e of edges) {
|
|
23632
|
-
const key = `${e.source}\0${e.target}`;
|
|
23633
|
-
if (seen.has(key)) continue;
|
|
23634
|
-
seen.add(key);
|
|
23635
|
-
out.push(e);
|
|
23636
|
-
}
|
|
23637
|
-
return out.sort((a, b) => a.source.localeCompare(b.source, "en") || a.target.localeCompare(b.target, "en"));
|
|
23638
|
-
}
|
|
23639
23640
|
function buildSuppressions(markers) {
|
|
23640
23641
|
return markers.map((m) => ({
|
|
23641
23642
|
aspectId: m.aspectId,
|
|
@@ -23677,6 +23678,30 @@ function buildWorklist(check) {
|
|
|
23677
23678
|
}));
|
|
23678
23679
|
}
|
|
23679
23680
|
|
|
23681
|
+
// src/portal/derive-boundary.ts
|
|
23682
|
+
function buildBoundary(input) {
|
|
23683
|
+
if (input === null) {
|
|
23684
|
+
return { phantom: [], declaredOnly: [], forbiddenType: [], unknown: true };
|
|
23685
|
+
}
|
|
23686
|
+
return {
|
|
23687
|
+
phantom: dedupeEdges(input.phantom),
|
|
23688
|
+
declaredOnly: dedupeEdges(input.declaredOnly),
|
|
23689
|
+
forbiddenType: dedupeEdges(input.forbiddenType),
|
|
23690
|
+
unknown: false
|
|
23691
|
+
};
|
|
23692
|
+
}
|
|
23693
|
+
function dedupeEdges(edges) {
|
|
23694
|
+
const seen = /* @__PURE__ */ new Set();
|
|
23695
|
+
const out = [];
|
|
23696
|
+
for (const e of edges) {
|
|
23697
|
+
const key = `${e.source}\0${e.target}`;
|
|
23698
|
+
if (seen.has(key)) continue;
|
|
23699
|
+
seen.add(key);
|
|
23700
|
+
out.push(e);
|
|
23701
|
+
}
|
|
23702
|
+
return out.sort((a, b) => a.source.localeCompare(b.source, "en") || a.target.localeCompare(b.target, "en"));
|
|
23703
|
+
}
|
|
23704
|
+
|
|
23680
23705
|
// src/portal/extract.ts
|
|
23681
23706
|
async function extractPortalData(projectRoot, opts) {
|
|
23682
23707
|
const graph = await loadPortalGraph(projectRoot);
|
|
@@ -23930,6 +23955,78 @@ async function readPortalAsset(relPath) {
|
|
|
23930
23955
|
// src/portal/server/server.ts
|
|
23931
23956
|
import { createServer } from "http";
|
|
23932
23957
|
|
|
23958
|
+
// src/portal/server/boot-pages.ts
|
|
23959
|
+
var BOOT_PAGE_CSS = `
|
|
23960
|
+
:root{color-scheme:light dark;--yg-bg:#fcfcfd;--yg-surface:#f9f9fb;--yg-fg:#1c2024;--yg-muted:#60646c;--yg-accent:#0090ff;--yg-border:#cdced6}
|
|
23961
|
+
@media (prefers-color-scheme:dark){:root{--yg-bg:#111113;--yg-surface:#18191b;--yg-fg:#edeef0;--yg-muted:#b0b4ba;--yg-accent:#3b9eff;--yg-border:#43484e}}
|
|
23962
|
+
html,body{height:100%}
|
|
23963
|
+
body{margin:0;background:var(--yg-bg);color:var(--yg-fg);font:15px/1.5 -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;display:grid;place-items:center;padding:24px;box-sizing:border-box}
|
|
23964
|
+
.yg-box{display:flex;flex-direction:column;align-items:center;gap:14px;text-align:center;max-width:56ch}
|
|
23965
|
+
.yg-title{font-weight:600;font-size:16px}
|
|
23966
|
+
.yg-sub{color:var(--yg-muted);font-size:13.5px;max-width:46ch}
|
|
23967
|
+
.yg-sub code,.yg-detail{font-family:ui-monospace,SFMono-Regular,Menlo,monospace}
|
|
23968
|
+
.yg-sub code{font-size:12px;background:var(--yg-surface);border:1px solid var(--yg-border);border-radius:4px;padding:1px 5px}`;
|
|
23969
|
+
function renderLoadingShell() {
|
|
23970
|
+
return `<!doctype html>
|
|
23971
|
+
<html lang="en">
|
|
23972
|
+
<head>
|
|
23973
|
+
<meta charset="utf-8" />
|
|
23974
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
23975
|
+
<title>Yggdrasil Portal</title>
|
|
23976
|
+
<style>${BOOT_PAGE_CSS}
|
|
23977
|
+
.yg-spinner{width:34px;height:34px;border-radius:50%;border:3px solid var(--yg-border);border-top-color:var(--yg-accent);animation:yg-spin .8s linear infinite}
|
|
23978
|
+
@keyframes yg-spin{to{transform:rotate(360deg)}}
|
|
23979
|
+
@media (prefers-reduced-motion:reduce){.yg-spinner{animation-duration:2.4s}}
|
|
23980
|
+
</style>
|
|
23981
|
+
</head>
|
|
23982
|
+
<body>
|
|
23983
|
+
<div class="yg-box" id="yg-boot">
|
|
23984
|
+
<div class="yg-spinner" role="status" aria-label="Loading"></div>
|
|
23985
|
+
<div class="yg-title">Reading your architecture\u2026</div>
|
|
23986
|
+
<div class="yg-sub">Checking the graph and rendering the portal. This can take a moment on a large project.</div>
|
|
23987
|
+
</div>
|
|
23988
|
+
<script>
|
|
23989
|
+
(function(){
|
|
23990
|
+
fetch('/render' + location.search, { headers: { accept: 'text/html' } })
|
|
23991
|
+
.then(function(r){ return r.text(); })
|
|
23992
|
+
.then(function(html){ document.open(); document.write(html); document.close(); })
|
|
23993
|
+
.catch(function(){
|
|
23994
|
+
var b = document.getElementById('yg-boot');
|
|
23995
|
+
if (b) { b.innerHTML =
|
|
23996
|
+
'<div class="yg-title">Couldn\\u2019t reach the portal</div>' +
|
|
23997
|
+
'<div class="yg-sub">The local portal process may have stopped. Restart it in your terminal, then reload this page.</div>'; }
|
|
23998
|
+
});
|
|
23999
|
+
})();
|
|
24000
|
+
</script>
|
|
24001
|
+
</body>
|
|
24002
|
+
</html>
|
|
24003
|
+
`;
|
|
24004
|
+
}
|
|
24005
|
+
function renderErrorPage(message) {
|
|
24006
|
+
return `<!doctype html>
|
|
24007
|
+
<html lang="en">
|
|
24008
|
+
<head>
|
|
24009
|
+
<meta charset="utf-8" />
|
|
24010
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
24011
|
+
<title>Yggdrasil Portal \u2014 couldn\u2019t load</title>
|
|
24012
|
+
<style>${BOOT_PAGE_CSS}
|
|
24013
|
+
.yg-detail{margin-top:4px;max-width:74ch;text-align:left;white-space:pre-wrap;word-break:break-word;font-size:12px;line-height:1.5;color:var(--yg-muted);background:var(--yg-surface);border:1px solid var(--yg-border);border-radius:8px;padding:12px 14px}
|
|
24014
|
+
</style>
|
|
24015
|
+
</head>
|
|
24016
|
+
<body>
|
|
24017
|
+
<div class="yg-box">
|
|
24018
|
+
<div class="yg-title">The portal couldn\u2019t load your architecture</div>
|
|
24019
|
+
<div class="yg-sub">Something went wrong while reading the graph and building the page. Make sure this is a project with a <code>.yggdrasil/</code> graph, then reload. If it keeps happening, run <code>yg check</code> in your terminal to see the underlying problem.</div>
|
|
24020
|
+
<div class="yg-detail">${escapeHtmlText(message)}</div>
|
|
24021
|
+
</div>
|
|
24022
|
+
</body>
|
|
24023
|
+
</html>
|
|
24024
|
+
`;
|
|
24025
|
+
}
|
|
24026
|
+
function escapeHtmlText(s) {
|
|
24027
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
24028
|
+
}
|
|
24029
|
+
|
|
23933
24030
|
// src/portal/server/page.ts
|
|
23934
24031
|
async function freshPortalData(projectRoot, writeEnabled) {
|
|
23935
24032
|
return extractPortalData(projectRoot, { writeEnabled });
|
|
@@ -23937,6 +24034,12 @@ async function freshPortalData(projectRoot, writeEnabled) {
|
|
|
23937
24034
|
async function renderLivePage(data) {
|
|
23938
24035
|
return renderPortalPage(data);
|
|
23939
24036
|
}
|
|
24037
|
+
function loadingShell() {
|
|
24038
|
+
return renderLoadingShell();
|
|
24039
|
+
}
|
|
24040
|
+
function errorPage(message) {
|
|
24041
|
+
return renderErrorPage(message);
|
|
24042
|
+
}
|
|
23940
24043
|
async function readStaticAsset(relPath) {
|
|
23941
24044
|
return readPortalAsset(relPath);
|
|
23942
24045
|
}
|
|
@@ -24028,9 +24131,18 @@ async function handleRequest(req, res, config) {
|
|
|
24028
24131
|
const pathname = url.pathname;
|
|
24029
24132
|
try {
|
|
24030
24133
|
if (method === "GET" && pathname === "/") {
|
|
24031
|
-
|
|
24032
|
-
|
|
24033
|
-
|
|
24134
|
+
sendText(res, 200, "text/html; charset=utf-8", loadingShell());
|
|
24135
|
+
return;
|
|
24136
|
+
}
|
|
24137
|
+
if (method === "GET" && pathname === "/render") {
|
|
24138
|
+
try {
|
|
24139
|
+
const data = await freshPortalData(config.projectRoot, config.writeEnabled);
|
|
24140
|
+
const html = await renderLivePage(data);
|
|
24141
|
+
sendText(res, 200, "text/html; charset=utf-8", html);
|
|
24142
|
+
} catch (err) {
|
|
24143
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
24144
|
+
sendText(res, 500, "text/html; charset=utf-8", errorPage(message));
|
|
24145
|
+
}
|
|
24034
24146
|
return;
|
|
24035
24147
|
}
|
|
24036
24148
|
if (method === "GET" && pathname === "/data") {
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Component styles — the count header, the tree, the node panel, the glossary tooltip,
|
|
3
|
+
* and the command-palette overlay.
|
|
4
|
+
*
|
|
5
|
+
* The reusable surface pieces the views compose. Tokens come from tokens.css; the shell
|
|
6
|
+
* frame from shell.css. Calm, dense, document-like; the only saturated color is a state
|
|
7
|
+
* (resolved through the state classes in tokens.css). This file stays focused on the
|
|
8
|
+
* in-stage components and the ⌘K overlay.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/* ── Count header + chips ───────────────────────────────────────────────── */
|
|
12
|
+
.stage-header {
|
|
13
|
+
border-bottom: 1px solid var(--border);
|
|
14
|
+
padding-bottom: 16px;
|
|
15
|
+
margin-bottom: 18px;
|
|
16
|
+
}
|
|
17
|
+
.stage-title {
|
|
18
|
+
font-size: 24px;
|
|
19
|
+
line-height: 32px;
|
|
20
|
+
font-weight: 600;
|
|
21
|
+
margin: 0 0 4px;
|
|
22
|
+
letter-spacing: -0.01em;
|
|
23
|
+
}
|
|
24
|
+
.stage-sub {
|
|
25
|
+
color: var(--text-secondary);
|
|
26
|
+
font-size: 12.5px;
|
|
27
|
+
margin: 0 0 16px;
|
|
28
|
+
}
|
|
29
|
+
.count-bar {
|
|
30
|
+
display: flex;
|
|
31
|
+
flex-wrap: wrap;
|
|
32
|
+
gap: 8px;
|
|
33
|
+
}
|
|
34
|
+
.count-chip {
|
|
35
|
+
display: inline-flex;
|
|
36
|
+
align-items: center;
|
|
37
|
+
gap: 6px;
|
|
38
|
+
padding: 4px 10px;
|
|
39
|
+
border-radius: 7px;
|
|
40
|
+
border: 1px solid var(--border-subtle);
|
|
41
|
+
background: var(--surface-1);
|
|
42
|
+
font-size: 13px;
|
|
43
|
+
}
|
|
44
|
+
.chip-count {
|
|
45
|
+
font-weight: 600;
|
|
46
|
+
}
|
|
47
|
+
.chip-label {
|
|
48
|
+
color: var(--text-secondary);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.stage-intro {
|
|
52
|
+
margin-bottom: 14px;
|
|
53
|
+
}
|
|
54
|
+
.stage-notfound {
|
|
55
|
+
margin: 10px 0 0;
|
|
56
|
+
padding: 8px 12px;
|
|
57
|
+
border: 1px solid var(--border);
|
|
58
|
+
border-left: 3px solid var(--unverified);
|
|
59
|
+
border-radius: 8px;
|
|
60
|
+
background: var(--surface-1);
|
|
61
|
+
color: var(--text-secondary);
|
|
62
|
+
font-size: 13px;
|
|
63
|
+
}
|
|
64
|
+
.stage-vtitle {
|
|
65
|
+
font-size: 16px;
|
|
66
|
+
margin: 0 0 2px;
|
|
67
|
+
}
|
|
68
|
+
.stage-vblurb {
|
|
69
|
+
margin: 0;
|
|
70
|
+
color: var(--text-secondary);
|
|
71
|
+
font-size: 13px;
|
|
72
|
+
}
|
|
73
|
+
.stage-note {
|
|
74
|
+
color: var(--text-muted);
|
|
75
|
+
font-size: 12.5px;
|
|
76
|
+
border: 1px dashed var(--border);
|
|
77
|
+
border-radius: 9px;
|
|
78
|
+
padding: 10px 12px;
|
|
79
|
+
margin: 4px 0 18px;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/* ── Tree (virtualized) ─────────────────────────────────────────────────── */
|
|
83
|
+
.tree-filter {
|
|
84
|
+
margin: 4px 0 8px;
|
|
85
|
+
}
|
|
86
|
+
.tree-filter-input {
|
|
87
|
+
width: 100%;
|
|
88
|
+
padding: 7px 11px;
|
|
89
|
+
border: 1px solid var(--border);
|
|
90
|
+
border-radius: 8px;
|
|
91
|
+
background: var(--bg);
|
|
92
|
+
color: var(--text-primary);
|
|
93
|
+
font: inherit;
|
|
94
|
+
font-size: 13px;
|
|
95
|
+
}
|
|
96
|
+
/* "You are here" marker for a row revealed by a palette pick / deep-link. */
|
|
97
|
+
.tree-row-revealed {
|
|
98
|
+
background: var(--surface-3);
|
|
99
|
+
box-shadow: inset 3px 0 0 var(--accent);
|
|
100
|
+
}
|
|
101
|
+
.tree-mount {
|
|
102
|
+
margin: 4px 0 18px;
|
|
103
|
+
}
|
|
104
|
+
.tree-scroller {
|
|
105
|
+
position: relative;
|
|
106
|
+
height: 60vh;
|
|
107
|
+
min-height: 280px;
|
|
108
|
+
overflow: auto;
|
|
109
|
+
border: 1px solid var(--border);
|
|
110
|
+
border-radius: 10px;
|
|
111
|
+
background: var(--surface-1);
|
|
112
|
+
}
|
|
113
|
+
.tree-spacer {
|
|
114
|
+
position: relative;
|
|
115
|
+
width: 100%;
|
|
116
|
+
}
|
|
117
|
+
.tree-window {
|
|
118
|
+
position: absolute;
|
|
119
|
+
top: 0;
|
|
120
|
+
left: 0;
|
|
121
|
+
right: 0;
|
|
122
|
+
will-change: transform;
|
|
123
|
+
}
|
|
124
|
+
.tree-row {
|
|
125
|
+
display: flex;
|
|
126
|
+
align-items: center;
|
|
127
|
+
gap: 9px;
|
|
128
|
+
height: 28px;
|
|
129
|
+
padding-right: 12px;
|
|
130
|
+
border-bottom: 1px solid var(--surface-2);
|
|
131
|
+
font-size: 13px;
|
|
132
|
+
cursor: pointer;
|
|
133
|
+
}
|
|
134
|
+
.tree-row:hover {
|
|
135
|
+
background: var(--surface-3);
|
|
136
|
+
}
|
|
137
|
+
.tree-name {
|
|
138
|
+
font-weight: 500;
|
|
139
|
+
}
|
|
140
|
+
.tree-type {
|
|
141
|
+
margin-left: auto;
|
|
142
|
+
color: var(--text-muted);
|
|
143
|
+
font-size: 11px;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/* ── Node panel ─────────────────────────────────────────────────────────── */
|
|
147
|
+
.panel-title {
|
|
148
|
+
font-size: 13px;
|
|
149
|
+
margin: 0 0 10px;
|
|
150
|
+
font-family: 'JetBrains Mono', ui-monospace, monospace;
|
|
151
|
+
word-break: break-all;
|
|
152
|
+
}
|
|
153
|
+
.panel-head {
|
|
154
|
+
display: flex;
|
|
155
|
+
align-items: center;
|
|
156
|
+
gap: 9px;
|
|
157
|
+
font-size: 13px;
|
|
158
|
+
margin-bottom: 8px;
|
|
159
|
+
}
|
|
160
|
+
.panel-sub {
|
|
161
|
+
color: var(--text-secondary);
|
|
162
|
+
font-size: 12.5px;
|
|
163
|
+
margin: 0;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/* ── Glossary term tooltip ──────────────────────────────────────────────── */
|
|
167
|
+
.term {
|
|
168
|
+
border-bottom: 1px dotted var(--border-strong);
|
|
169
|
+
cursor: help;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/* ── Command palette overlay ────────────────────────────────────────────── */
|
|
173
|
+
.palette-backdrop {
|
|
174
|
+
position: fixed;
|
|
175
|
+
inset: 0;
|
|
176
|
+
display: flex;
|
|
177
|
+
align-items: flex-start;
|
|
178
|
+
justify-content: center;
|
|
179
|
+
padding-top: 60px;
|
|
180
|
+
background: rgba(0, 0, 0, 0.32);
|
|
181
|
+
z-index: 50;
|
|
182
|
+
}
|
|
183
|
+
.palette-box {
|
|
184
|
+
width: 640px;
|
|
185
|
+
max-width: 92vw;
|
|
186
|
+
background: var(--surface-1);
|
|
187
|
+
border: 1px solid var(--border);
|
|
188
|
+
border-radius: 14px;
|
|
189
|
+
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.04), 0 16px 40px -10px rgba(0, 0, 0, 0.45);
|
|
190
|
+
overflow: hidden;
|
|
191
|
+
}
|
|
192
|
+
.palette-input {
|
|
193
|
+
display: flex;
|
|
194
|
+
align-items: center;
|
|
195
|
+
gap: 11px;
|
|
196
|
+
padding: 13px 16px;
|
|
197
|
+
border-bottom: 1px solid var(--border-subtle);
|
|
198
|
+
}
|
|
199
|
+
.palette-search-ic {
|
|
200
|
+
color: var(--text-muted);
|
|
201
|
+
}
|
|
202
|
+
.palette-field {
|
|
203
|
+
flex: 1;
|
|
204
|
+
border: 0;
|
|
205
|
+
background: none;
|
|
206
|
+
color: var(--text-primary);
|
|
207
|
+
font: inherit;
|
|
208
|
+
font-size: 16px;
|
|
209
|
+
}
|
|
210
|
+
.palette-field:focus {
|
|
211
|
+
outline: none;
|
|
212
|
+
}
|
|
213
|
+
.palette-results {
|
|
214
|
+
max-height: 380px;
|
|
215
|
+
overflow: auto;
|
|
216
|
+
padding: 6px;
|
|
217
|
+
}
|
|
218
|
+
.palette-empty {
|
|
219
|
+
padding: 16px;
|
|
220
|
+
color: var(--text-muted);
|
|
221
|
+
font-size: 13px;
|
|
222
|
+
}
|
|
223
|
+
.palette-row {
|
|
224
|
+
display: flex;
|
|
225
|
+
align-items: center;
|
|
226
|
+
gap: 11px;
|
|
227
|
+
padding: 8px 12px;
|
|
228
|
+
border-radius: 8px;
|
|
229
|
+
font-size: 13.5px;
|
|
230
|
+
cursor: pointer;
|
|
231
|
+
}
|
|
232
|
+
.palette-row.on {
|
|
233
|
+
background: var(--surface-3);
|
|
234
|
+
}
|
|
235
|
+
.palette-ic {
|
|
236
|
+
width: 18px;
|
|
237
|
+
text-align: center;
|
|
238
|
+
color: var(--text-muted);
|
|
239
|
+
}
|
|
240
|
+
.palette-nm {
|
|
241
|
+
font-weight: 500;
|
|
242
|
+
}
|
|
243
|
+
.palette-sub {
|
|
244
|
+
color: var(--text-muted);
|
|
245
|
+
font-size: 11.5px;
|
|
246
|
+
}
|
|
247
|
+
.palette-kind {
|
|
248
|
+
margin-left: auto;
|
|
249
|
+
font-size: 10px;
|
|
250
|
+
color: var(--text-muted);
|
|
251
|
+
border: 1px solid var(--border-subtle);
|
|
252
|
+
border-radius: 5px;
|
|
253
|
+
padding: 0 6px;
|
|
254
|
+
}
|
|
255
|
+
.palette-foot {
|
|
256
|
+
display: flex;
|
|
257
|
+
gap: 16px;
|
|
258
|
+
padding: 9px 14px;
|
|
259
|
+
border-top: 1px solid var(--border-subtle);
|
|
260
|
+
font-size: 11px;
|
|
261
|
+
color: var(--text-muted);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
.portal-error {
|
|
265
|
+
color: var(--refused);
|
|
266
|
+
padding: 24px;
|
|
267
|
+
font-size: 14px;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
@media (prefers-reduced-motion: reduce) {
|
|
271
|
+
*,
|
|
272
|
+
*::before,
|
|
273
|
+
*::after {
|
|
274
|
+
transition: none !important;
|
|
275
|
+
animation: none !important;
|
|
276
|
+
scroll-behavior: auto !important;
|
|
277
|
+
}
|
|
278
|
+
}
|