@materialwebunofficial/antigravity-themes 0.0.0-stage → 1.0.0
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/LICENSE +201 -0
- package/NOTICE +30 -0
- package/README.md +261 -2
- package/bin/cli.mjs +252 -0
- package/lib/autostart.mjs +70 -0
- package/lib/bundle.mjs +73 -0
- package/lib/injector.mjs +176 -0
- package/lib/paths.mjs +28 -0
- package/package.json +40 -4
- package/src/code-tools.js +131 -0
- package/src/context-card.js +536 -0
- package/src/loading-indicator.js +74 -0
- package/src/progress-indicators.js +98 -0
- package/src/theme-host.js +709 -0
- package/src/turn-tokens.js +115 -0
- package/themes/index.json +21 -0
- package/themes/matte.css +390 -0
- package/themes/md3e-icons.css +244 -0
- package/themes/md3e-tokens.css +834 -0
- package/themes/md3e.css +1206 -0
- package/vendor/md3e/LICENSE +176 -0
- package/vendor/md3e/NOTICE +686 -0
- package/vendor/md3e/Roboto-OFL.txt +93 -0
- package/vendor/md3e/SOURCE.txt +10 -0
- package/vendor/md3e/components.js +4682 -0
- package/vendor/md3e/roboto-variable.woff2 +0 -0
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
// Code tools: two toggles in every chat code block's header, beside
|
|
2
|
+
// "Copy code": Wrap lines (the app's default; off scrolls long lines
|
|
3
|
+
// sideways) and Line numbers. One choice for all code blocks, kept in the
|
|
4
|
+
// agy-code cookie like the theme, and applied as attributes on <html>.
|
|
5
|
+
//
|
|
6
|
+
// The buttons are the app's own header buttons (its classes, a Material
|
|
7
|
+
// Symbols glyph at 14px; Outlined like the app's icons, md3e.css swaps in
|
|
8
|
+
// Rounded), so each theme styles them as it styles Copy code. Line numbers
|
|
9
|
+
// are a CSS counter on the app's .code-line rows (::before): nothing is
|
|
10
|
+
// added to the rows React renders, and the numbers stay out of selections
|
|
11
|
+
// and of Copy code. Themes color them through --agy-lineno.
|
|
12
|
+
function agyCodeTools() {
|
|
13
|
+
const COOKIE = 'agy-code';
|
|
14
|
+
const COPY = 'button[aria-label="Copy code"], button[aria-label="Copied"]';
|
|
15
|
+
const BUTTON_CLASSES = 'appearance-none bg-transparent border-0 p-0 cursor-pointer text-secondary-foreground hover:text-foreground transition-colors';
|
|
16
|
+
const TOOLS = [
|
|
17
|
+
{ key: 'wrap', label: 'Wrap lines', on: (p) => !p.nowrap, // wrap_text
|
|
18
|
+
icon: 'M570 -192 432 -330 570 -468 621 -417 569 -366H672Q702 -366 723 -387Q744 -408 744 -438Q744 -468 723 -489Q702 -510 672 -510H192V-582H672Q732 -582 774 -540Q816 -498 816 -438Q816 -378 774 -336Q732 -294 672 -294H570L621 -243ZM192 -294V-366H384V-294ZM192 -726V-798H768V-726Z' },
|
|
19
|
+
{ key: 'lines', label: 'Line numbers', on: (p) => p.lines, // format_list_numbered
|
|
20
|
+
icon: 'M144 -144V-192H240V-216H192V-264H240V-288H144V-336H264Q274 -336 281 -329Q288 -322 288 -312V-264Q288 -254 281 -247Q274 -240 264 -240Q274 -240 281 -233Q288 -226 288 -216V-168Q288 -158 281 -151Q274 -144 264 -144ZM144 -384V-480Q144 -490 151 -497Q158 -504 168 -504H240V-528H144V-576H264Q274 -576 281 -569Q288 -562 288 -552V-480Q288 -470 281 -463Q274 -456 264 -456H192V-432H288V-384ZM192 -624V-768H144V-816H240V-624ZM360 -240V-312H816V-240ZM360 -444V-516H816V-444ZM360 -648V-720H816V-648Z' },
|
|
21
|
+
];
|
|
22
|
+
|
|
23
|
+
const style = document.createElement('style');
|
|
24
|
+
style.id = 'agy-code-css';
|
|
25
|
+
style.textContent = `
|
|
26
|
+
.agy-code-tool { display: inline-flex; align-items: center; justify-content: center; }
|
|
27
|
+
.agy-code-tool[aria-pressed="true"] { color: var(--foreground); }
|
|
28
|
+
:root[data-agy-code-lines] .agy-code { counter-reset: agy-line; }
|
|
29
|
+
:root[data-agy-code-lines] .agy-code .code-line { counter-increment: agy-line; }
|
|
30
|
+
:root[data-agy-code-lines] .agy-code .code-line::before {
|
|
31
|
+
content: counter(agy-line);
|
|
32
|
+
flex: none; min-width: 2ch; margin-right: 16px; text-align: right;
|
|
33
|
+
color: var(--agy-lineno, var(--editor-line-number-foreground, rgb(128 128 128 / 0.5)));
|
|
34
|
+
font-variant-numeric: tabular-nums; user-select: none;
|
|
35
|
+
}
|
|
36
|
+
:root[data-agy-code-nowrap] .agy-code { white-space: pre; word-break: normal; }
|
|
37
|
+
:root[data-agy-code-nowrap] .agy-code > :last-child { overflow-x: auto; }
|
|
38
|
+
:root[data-agy-code-nowrap] .agy-code .line-content { white-space: pre !important; word-break: normal; } /* inline pre-wrap */`;
|
|
39
|
+
(document.head || document.documentElement).appendChild(style);
|
|
40
|
+
|
|
41
|
+
// ---- The choice ---------------------------------------------------------------
|
|
42
|
+
const readPref = () => {
|
|
43
|
+
const flags = ((document.cookie.match(/(?:^|;\s*)agy-code=([^;]*)/) || [])[1] || '').split('.');
|
|
44
|
+
return { nowrap: flags.includes('nowrap'), lines: flags.includes('lines') };
|
|
45
|
+
};
|
|
46
|
+
let pref = readPref();
|
|
47
|
+
function apply() {
|
|
48
|
+
const root = document.documentElement;
|
|
49
|
+
root.toggleAttribute('data-agy-code-nowrap', pref.nowrap);
|
|
50
|
+
root.toggleAttribute('data-agy-code-lines', pref.lines);
|
|
51
|
+
for (const b of document.querySelectorAll('.agy-code-tool')) {
|
|
52
|
+
const tool = TOOLS.find((t) => t.key === b.dataset.tool);
|
|
53
|
+
b.setAttribute('aria-pressed', String(tool.on(pref)));
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
function toggle(key) {
|
|
57
|
+
if (key === 'wrap') pref.nowrap = !pref.nowrap;
|
|
58
|
+
else pref.lines = !pref.lines;
|
|
59
|
+
const value = [pref.nowrap && 'nowrap', pref.lines && 'lines'].filter(Boolean).join('.');
|
|
60
|
+
document.cookie = `${COOKIE}=${value}; Max-Age=31536000; Path=/; SameSite=Strict; Secure`;
|
|
61
|
+
apply();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// ---- The buttons --------------------------------------------------------------
|
|
65
|
+
// Header markup: block > [header > [language, buttons…], body]
|
|
66
|
+
function decorate(copy) {
|
|
67
|
+
const group = copy.parentElement, header = group?.parentElement, block = header?.parentElement;
|
|
68
|
+
if (!block || !header.nextElementSibling || group.querySelector(':scope > .agy-code-tool')) return;
|
|
69
|
+
block.classList.add('agy-code');
|
|
70
|
+
for (const tool of TOOLS) {
|
|
71
|
+
const b = document.createElement('button');
|
|
72
|
+
b.type = 'button';
|
|
73
|
+
b.className = `${BUTTON_CLASSES} agy-code-tool`;
|
|
74
|
+
b.dataset.tool = tool.key;
|
|
75
|
+
b.setAttribute('aria-label', tool.label);
|
|
76
|
+
b.setAttribute('aria-pressed', String(tool.on(pref)));
|
|
77
|
+
b.innerHTML = `<svg viewBox="0 -960 960 960" fill="currentColor" class="h-3.5 w-3.5" aria-hidden="true"><path d="${tool.icon}"></path></svg>`;
|
|
78
|
+
b.addEventListener('click', (e) => {
|
|
79
|
+
e.stopPropagation();
|
|
80
|
+
toggle(tool.key);
|
|
81
|
+
});
|
|
82
|
+
group.insertBefore(b, copy);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Code blocks mount with their message: look inside what the transcript
|
|
87
|
+
// adds, and rescan it every 2s for a replaced viewport
|
|
88
|
+
let viewport = null, observer = null;
|
|
89
|
+
function onMutations(records) {
|
|
90
|
+
try {
|
|
91
|
+
for (const r of records) {
|
|
92
|
+
for (const n of r.addedNodes) {
|
|
93
|
+
if (n.nodeType !== 1 || n.classList.contains('agy-code-tool')) continue;
|
|
94
|
+
if (n.matches(COPY)) decorate(n);
|
|
95
|
+
else n.querySelectorAll(COPY).forEach(decorate);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
} catch {
|
|
99
|
+
// app internals changed: leave the code blocks as they are
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
function scan() {
|
|
103
|
+
if (document.hidden) return;
|
|
104
|
+
const vp = document.querySelector('[data-testid="autoscroll-viewport"]');
|
|
105
|
+
if (vp !== viewport) {
|
|
106
|
+
observer?.disconnect();
|
|
107
|
+
viewport = vp;
|
|
108
|
+
if (vp) {
|
|
109
|
+
observer = new MutationObserver(onMutations);
|
|
110
|
+
observer.observe(vp, { childList: true, subtree: true });
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
viewport?.querySelectorAll(COPY).forEach(decorate);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
apply();
|
|
117
|
+
const timer = setInterval(scan, 2000);
|
|
118
|
+
scan();
|
|
119
|
+
|
|
120
|
+
return {
|
|
121
|
+
dispose() {
|
|
122
|
+
clearInterval(timer);
|
|
123
|
+
observer?.disconnect();
|
|
124
|
+
document.querySelectorAll('.agy-code-tool').forEach((b) => b.remove());
|
|
125
|
+
document.querySelectorAll('.agy-code').forEach((b) => b.classList.remove('agy-code'));
|
|
126
|
+
document.documentElement.removeAttribute('data-agy-code-nowrap');
|
|
127
|
+
document.documentElement.removeAttribute('data-agy-code-lines');
|
|
128
|
+
style.remove();
|
|
129
|
+
},
|
|
130
|
+
};
|
|
131
|
+
}
|
|
@@ -0,0 +1,536 @@
|
|
|
1
|
+
// Context card: a "Context" section at the top of the Overview panel —
|
|
2
|
+
// how full the conversation's context window is and what takes it up.
|
|
3
|
+
// Two looks, chosen in CSS from the page's data-agy-theme (:host-context), so
|
|
4
|
+
// a theme switch restyles it at once: Matte (the panel's own quiet rows, flat
|
|
5
|
+
// 4px bar) for every theme but MD3E, and M3 Expressive for MD3E. Segment and
|
|
6
|
+
// dot colors are per-theme custom properties (--k-*), never set from JS.
|
|
7
|
+
//
|
|
8
|
+
// Read-only, from the app's own state:
|
|
9
|
+
// used the latest model call's prompt: input + cache-read + cache-write
|
|
10
|
+
// tokens (CortexStepMetadata.modelUsage) in the conversation view's
|
|
11
|
+
// trajectorySlice (React props above the transcript viewport)
|
|
12
|
+
// groups Rules / Skills / MCP tools / ... from getTokenBase() on the app's
|
|
13
|
+
// agent service, the call its Customizations screen makes
|
|
14
|
+
// window by model family: the app's model configs carry no size; the bar,
|
|
15
|
+
// the chip and the warnings measure against it
|
|
16
|
+
// total the conversation's own running count: every model call the card
|
|
17
|
+
// sees is added once (by step index) and kept per conversation in
|
|
18
|
+
// the agy-usage cookie (last 20; cookies are the one store that
|
|
19
|
+
// survives the app's port changing per launch). Reading the whole
|
|
20
|
+
// history instead costs ~12 KB a step; a conversation first
|
|
21
|
+
// seen part-way says which step its count starts from.
|
|
22
|
+
// Warnings at 80% and 95% of the window: one quiet line under the summary.
|
|
23
|
+
function agyContextCard() {
|
|
24
|
+
const WINDOWS = [[/^gemini/i, 1048576], [/^claude/i, 200000], [/gpt-oss/i, 131072]];
|
|
25
|
+
const LEVELS = [[0.95, 'critical', 'The context window is almost full.'],
|
|
26
|
+
[0.8, 'warn', 'The context window is filling up.']];
|
|
27
|
+
const GROUP_NAMES = { 1: 'System prompt', 2: 'Tools', 3: 'Rules', 4: 'MCP tools', 5: 'Skills', 6: 'Subagents', 7: 'Messages', 8: 'Workflows', 9: 'Plugins', 10: 'Flows' };
|
|
28
|
+
const GROUP_KINDS = { 3: 'rules', 4: 'mcp', 5: 'skills' };
|
|
29
|
+
const color = (kind) => `var(--k-${kind})`;
|
|
30
|
+
|
|
31
|
+
const TICK_MS = 2000;
|
|
32
|
+
const BASE_MS = 120000;
|
|
33
|
+
const CHEVRON = '<svg viewBox="0 -960 960 960" aria-hidden="true"><path d="M517.85-480l-184-184L376-706.15L602.15-480L376-253.85L333.85-296l184-184Z"/></svg>';
|
|
34
|
+
|
|
35
|
+
const num = (v) => Number(v ?? 0) || 0;
|
|
36
|
+
const fmt = (n) => (n >= 1e6 ? `${+(n / 1e6).toFixed(1)}M` : n >= 1000 ? `${+(n / 1000).toFixed(1)}k` : String(n));
|
|
37
|
+
const pct = (n, d) => {
|
|
38
|
+
const p = (n / d) * 100;
|
|
39
|
+
return p > 0 && p < 0.1 ? '<0.1%' : `${p.toFixed(p < 10 ? 1 : 0)}%`;
|
|
40
|
+
};
|
|
41
|
+
const fiberOf = (el) => el && el[Object.keys(el).find((k) => k.startsWith('__reactFiber$'))];
|
|
42
|
+
|
|
43
|
+
// ---- Data -------------------------------------------------------------------
|
|
44
|
+
function readSlice() {
|
|
45
|
+
let f = fiberOf(document.querySelector('[data-testid="autoscroll-viewport"]'));
|
|
46
|
+
for (let i = 0; f && i < 40; i++, f = f.return) {
|
|
47
|
+
if (f.memoizedProps?.trajectorySlice) return f.memoizedProps.trajectorySlice;
|
|
48
|
+
}
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
function readUsage(slice) {
|
|
52
|
+
const steps = slice?.stepsInSlice ?? [];
|
|
53
|
+
for (let i = steps.length - 1; i >= 0; i--) {
|
|
54
|
+
const u = steps[i]?.metadata?.modelUsage;
|
|
55
|
+
if (u) return num(u.inputTokens) + num(u.cacheReadTokens) + num(u.cacheWriteTokens);
|
|
56
|
+
}
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
// ---- Conversation total -----------------------------------------------------
|
|
60
|
+
// Cookie: "id:in.out.cache.from.last|…", numbers in base 36, newest first
|
|
61
|
+
const TOTALS_COOKIE = 'agy-usage';
|
|
62
|
+
const TOTALS_MAX = 20;
|
|
63
|
+
function readTotals() {
|
|
64
|
+
const raw = (document.cookie.match(/(?:^|;\s*)agy-usage=([^;]*)/) || [])[1];
|
|
65
|
+
if (!raw) return [];
|
|
66
|
+
return decodeURIComponent(raw).split('|').map((entry) => {
|
|
67
|
+
const [id, nums = ''] = entry.split(':');
|
|
68
|
+
const [input, output, cache, from, last] = nums.split('.').map((n) => parseInt(n, 36));
|
|
69
|
+
return { id, input, output, cache, from, last };
|
|
70
|
+
}).filter((r) => r.id && [r.input, r.output, r.cache, r.from, r.last].every(Number.isFinite));
|
|
71
|
+
}
|
|
72
|
+
function writeTotals(list) {
|
|
73
|
+
const value = list.map((r) => `${r.id}:${[r.input, r.output, r.cache, r.from, r.last].map((n) => n.toString(36)).join('.')}`).join('|');
|
|
74
|
+
document.cookie = `${TOTALS_COOKIE}=${encodeURIComponent(value)}; Max-Age=31536000; Path=/; SameSite=Strict; Secure`;
|
|
75
|
+
}
|
|
76
|
+
let totals = readTotals();
|
|
77
|
+
function countTotal(slice) {
|
|
78
|
+
const id = slice?.conversationId;
|
|
79
|
+
if (!id) return null;
|
|
80
|
+
const start = num(slice.stepsSlice?.startIndex);
|
|
81
|
+
const length = num(slice.totalStepsLength);
|
|
82
|
+
let rec = totals.find((r) => r.id === id);
|
|
83
|
+
let changed = false;
|
|
84
|
+
if (!rec) {
|
|
85
|
+
rec = { id, input: 0, output: 0, cache: 0, from: start, last: start - 1 };
|
|
86
|
+
changed = true;
|
|
87
|
+
}
|
|
88
|
+
if (length && rec.last >= length) { // undone ("Undo to this point"): later steps get reused indices
|
|
89
|
+
rec.last = length - 1;
|
|
90
|
+
changed = true;
|
|
91
|
+
}
|
|
92
|
+
(slice.stepsInSlice ?? []).forEach((s, i) => {
|
|
93
|
+
const u = s?.metadata?.modelUsage;
|
|
94
|
+
if (!u || start + i <= rec.last) return;
|
|
95
|
+
rec.input += num(u.inputTokens) + num(u.cacheWriteTokens);
|
|
96
|
+
rec.output += num(u.outputTokens);
|
|
97
|
+
rec.cache += num(u.cacheReadTokens);
|
|
98
|
+
rec.last = start + i;
|
|
99
|
+
changed = true;
|
|
100
|
+
});
|
|
101
|
+
if (changed) {
|
|
102
|
+
totals = [rec, ...totals.filter((r) => r !== rec)].slice(0, TOTALS_MAX);
|
|
103
|
+
writeTotals(totals);
|
|
104
|
+
}
|
|
105
|
+
return rec;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function readWindow(slice) {
|
|
109
|
+
const name = slice?.generatorMetadatasInSlice?.at(-1)?.plannerConfig?.modelName
|
|
110
|
+
|| document.querySelector('[data-testid="model-selector-trigger"]')?.textContent || '';
|
|
111
|
+
return WINDOWS.find(([re]) => re.test(name.trim()))?.[1] ?? null;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
let service = null;
|
|
115
|
+
function agentService() {
|
|
116
|
+
if (service) return service;
|
|
117
|
+
const root = document.getElementById('root');
|
|
118
|
+
const key = root && Object.keys(root).find((k) => k.startsWith('__reactContainer$'));
|
|
119
|
+
const stack = [root?.[key]?.stateNode?.current ?? root?.[key]];
|
|
120
|
+
const seen = new Set();
|
|
121
|
+
while (stack.length && seen.size < 30000) {
|
|
122
|
+
const f = stack.pop();
|
|
123
|
+
if (!f || seen.has(f)) continue;
|
|
124
|
+
seen.add(f);
|
|
125
|
+
const v = f.memoizedProps?.value;
|
|
126
|
+
if (v && typeof v.getTokenBase === 'function') return (service = v);
|
|
127
|
+
if (f.sibling) stack.push(f.sibling);
|
|
128
|
+
if (f.child) stack.push(f.child);
|
|
129
|
+
}
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
let base = null, baseFor = null, baseAt = 0, baseLoading = false;
|
|
134
|
+
function refreshBase(slice) {
|
|
135
|
+
const convo = slice?.conversationId ?? null;
|
|
136
|
+
if (baseLoading || (convo === baseFor && Date.now() - baseAt < BASE_MS)) return;
|
|
137
|
+
const svc = agentService();
|
|
138
|
+
if (!svc) return;
|
|
139
|
+
baseLoading = true;
|
|
140
|
+
const uris = slice?.metadata?.workspaceUris;
|
|
141
|
+
svc.getTokenBase(Array.isArray(uris) ? { workspaceUris: uris } : {})
|
|
142
|
+
.then((r) => {
|
|
143
|
+
base = (r.customizationTokenBase?.groups ?? [])
|
|
144
|
+
.map((g) => ({
|
|
145
|
+
type: g.type,
|
|
146
|
+
name: GROUP_NAMES[g.type] || g.name,
|
|
147
|
+
tokens: num(g.numTokens),
|
|
148
|
+
children: (g.children ?? []).map((c) => ({ name: c.name, tokens: num(c.numTokens) })).sort((a, b) => b.tokens - a.tokens),
|
|
149
|
+
}))
|
|
150
|
+
.filter((g) => g.tokens > 0)
|
|
151
|
+
.sort((a, b) => b.tokens - a.tokens);
|
|
152
|
+
baseFor = convo;
|
|
153
|
+
baseAt = Date.now();
|
|
154
|
+
render();
|
|
155
|
+
})
|
|
156
|
+
.catch(() => {})
|
|
157
|
+
.finally(() => { baseLoading = false; });
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// ---- View -------------------------------------------------------------------
|
|
161
|
+
let hostEl = null, shadow = null, state = null, signature = '';
|
|
162
|
+
let open = true;
|
|
163
|
+
const openGroups = new Set();
|
|
164
|
+
|
|
165
|
+
function mount() {
|
|
166
|
+
const list = document.querySelector('[data-testid="overview-panel"] > .overflow-y-auto');
|
|
167
|
+
if (!list) return false;
|
|
168
|
+
if (hostEl?.parentElement === list) return true;
|
|
169
|
+
if (!hostEl) {
|
|
170
|
+
hostEl = document.createElement('div');
|
|
171
|
+
hostEl.id = 'agy-context-card';
|
|
172
|
+
shadow = hostEl.attachShadow({ mode: 'open' });
|
|
173
|
+
shadow.innerHTML = `
|
|
174
|
+
<style>
|
|
175
|
+
:host { display: block; padding: 4px; }
|
|
176
|
+
*, *::before, *::after { box-sizing: border-box; }
|
|
177
|
+
button { font: inherit; color: inherit; background: none; border: 0; padding: 0; cursor: default; text-align: start; }
|
|
178
|
+
svg { width: 16px; height: 16px; fill: currentColor; flex: none; }
|
|
179
|
+
|
|
180
|
+
/* =========================================================================
|
|
181
|
+
SHARED LAYOUT & ANIMATION
|
|
182
|
+
========================================================================= */
|
|
183
|
+
.body, .kids { display: grid; grid-template-rows: 1fr; transition: grid-template-rows 300ms cubic-bezier(0.2, 0, 0, 1); }
|
|
184
|
+
.body[hidden], .kids[hidden] { display: grid; grid-template-rows: 0fr; }
|
|
185
|
+
.clip { min-height: 0; overflow: hidden; }
|
|
186
|
+
[hidden] > .clip { visibility: hidden; transition: visibility 0s 300ms; }
|
|
187
|
+
ul { list-style: none; margin: 0; padding: 2px 0 0; }
|
|
188
|
+
.status[hidden], .total[hidden] { display: none; }
|
|
189
|
+
.total-value { font-variant-numeric: tabular-nums; }
|
|
190
|
+
.name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
191
|
+
.n, .p { text-align: end; }
|
|
192
|
+
.head:focus-visible, .row:focus-visible {
|
|
193
|
+
outline: 2px solid var(--md-sys-color-secondary, var(--ring, currentColor)); outline-offset: -2px;
|
|
194
|
+
}
|
|
195
|
+
@media (prefers-reduced-motion: reduce) { * { transition: none !important; } }
|
|
196
|
+
|
|
197
|
+
/* =========================================================================
|
|
198
|
+
MATTE THEME STYLING (matches stock Overview items & Background Commands)
|
|
199
|
+
========================================================================= */
|
|
200
|
+
:host-context(:root:not([data-agy-theme="md3e"])) {
|
|
201
|
+
color: var(--foreground, #dcdcde);
|
|
202
|
+
font-family: var(--font-sans, "Segoe UI Variable Text", "Segoe UI", system-ui, sans-serif);
|
|
203
|
+
/* Matte's own tokens (matte.css, light and dark), with its dark
|
|
204
|
+
values as fallbacks for any other non-MD3E theme */
|
|
205
|
+
--k-conversation: var(--m-text, #dcdcde);
|
|
206
|
+
--k-rules: var(--m-blue, #7aa2f7);
|
|
207
|
+
--k-skills: var(--m-amber, #e5a84b);
|
|
208
|
+
--k-mcp: var(--m-teal, #73daca);
|
|
209
|
+
--k-other: var(--m-text-3, #86868c);
|
|
210
|
+
--k-free: var(--m-line-3, rgba(255, 255, 255, 0.18)); /* legend dot; the bar's own track shows the free part */
|
|
211
|
+
--k-hover: var(--m-hover, rgba(255, 255, 255, 0.045));
|
|
212
|
+
--k-track: var(--m-line, rgba(255, 255, 255, 0.075));
|
|
213
|
+
}
|
|
214
|
+
:host-context(:root:not([data-agy-theme="md3e"])) .head:focus-visible svg { opacity: 0.7; }
|
|
215
|
+
:host-context(:root:not([data-agy-theme="md3e"])) .head {
|
|
216
|
+
display: flex; align-items: center; gap: 6px; width: 100%; height: 24px; min-height: 24px; padding: 0 6px 0 8px;
|
|
217
|
+
border-radius: 8px; font-size: 12px; font-weight: 500; cursor: pointer;
|
|
218
|
+
transition: background 150ms ease;
|
|
219
|
+
}
|
|
220
|
+
:host-context(:root:not([data-agy-theme="md3e"])) .head:hover {
|
|
221
|
+
background: var(--k-hover);
|
|
222
|
+
}
|
|
223
|
+
:host-context(:root:not([data-agy-theme="md3e"])) .title {
|
|
224
|
+
color: var(--foreground, #dcdcde);
|
|
225
|
+
opacity: 0.5;
|
|
226
|
+
transition: opacity 150ms ease;
|
|
227
|
+
}
|
|
228
|
+
:host-context(:root:not([data-agy-theme="md3e"])) .head:hover .title { opacity: 1; }
|
|
229
|
+
:host-context(:root:not([data-agy-theme="md3e"])) .chip {
|
|
230
|
+
padding: 1px 6px; border-radius: 9999px;
|
|
231
|
+
background: var(--border, rgba(255, 255, 255, 0.08));
|
|
232
|
+
color: var(--foreground, #dcdcde);
|
|
233
|
+
font-size: 10px; font-weight: 500; font-family: var(--font-mono, monospace); line-height: 14px;
|
|
234
|
+
opacity: 0.5;
|
|
235
|
+
transition: opacity 150ms ease;
|
|
236
|
+
}
|
|
237
|
+
:host-context(:root:not([data-agy-theme="md3e"])) .head:hover .chip { opacity: 1; }
|
|
238
|
+
:host-context(:root:not([data-agy-theme="md3e"])) .head svg {
|
|
239
|
+
margin-left: auto; width: 14px; height: 14px;
|
|
240
|
+
color: var(--muted-foreground, #86868c);
|
|
241
|
+
opacity: 0;
|
|
242
|
+
rotate: 90deg;
|
|
243
|
+
transition: opacity 150ms ease, rotate 150ms ease;
|
|
244
|
+
}
|
|
245
|
+
:host-context(:root:not([data-agy-theme="md3e"])) .head:hover svg { opacity: 0.7; }
|
|
246
|
+
:host-context(:root:not([data-agy-theme="md3e"])) .head[aria-expanded="false"] svg { rotate: 0deg; }
|
|
247
|
+
:host-context(:root:not([data-agy-theme="md3e"])) .summary {
|
|
248
|
+
padding: 2px 8px 6px; font-size: 11px; font-variant-numeric: tabular-nums;
|
|
249
|
+
color: var(--muted-foreground, #86868c);
|
|
250
|
+
}
|
|
251
|
+
:host-context(:root:not([data-agy-theme="md3e"])) .total {
|
|
252
|
+
padding: 0 8px 6px; font-size: 11px; line-height: 14px; color: var(--muted-foreground, #86868c);
|
|
253
|
+
}
|
|
254
|
+
:host-context(:root:not([data-agy-theme="md3e"])) .total-value { color: var(--secondary-foreground, #a3a3a8); }
|
|
255
|
+
:host-context(:root:not([data-agy-theme="md3e"])) .status {
|
|
256
|
+
padding: 0 8px 6px; font-size: 11px; line-height: 14px; font-variant-numeric: tabular-nums;
|
|
257
|
+
color: var(--muted-foreground, #86868c);
|
|
258
|
+
}
|
|
259
|
+
/* Warnings stay monochrome: the chip comes forward, then inverts */
|
|
260
|
+
:host-context(:root:not([data-agy-theme="md3e"])) :is([data-level="warn"], [data-level="critical"]) .chip { opacity: 1; }
|
|
261
|
+
:host-context(:root:not([data-agy-theme="md3e"])) [data-level="critical"] .chip {
|
|
262
|
+
background: var(--foreground, #dcdcde); color: var(--background, #111113);
|
|
263
|
+
}
|
|
264
|
+
:host-context(:root:not([data-agy-theme="md3e"])) [data-level="critical"] .status { color: var(--foreground, #dcdcde); }
|
|
265
|
+
:host-context(:root:not([data-agy-theme="md3e"])) .bar {
|
|
266
|
+
position: relative; display: flex; gap: 1px; height: 4px; margin: 0 8px 6px;
|
|
267
|
+
border-radius: 2px; background: var(--k-track); overflow: hidden;
|
|
268
|
+
}
|
|
269
|
+
:host-context(:root:not([data-agy-theme="md3e"])) .seg {
|
|
270
|
+
flex: var(--v) 1 0; min-width: 2px; border-radius: 0; background: var(--c);
|
|
271
|
+
transition: flex-grow 300ms ease;
|
|
272
|
+
}
|
|
273
|
+
:host-context(:root:not([data-agy-theme="md3e"])) .seg.free { background: transparent; }
|
|
274
|
+
:host-context(:root:not([data-agy-theme="md3e"])) .row {
|
|
275
|
+
display: grid; grid-template-columns: 6px 1fr auto 40px 14px; align-items: center; gap: 8px;
|
|
276
|
+
width: 100%; min-height: 24px; padding: 2px 8px; border-radius: 6px;
|
|
277
|
+
font-size: 12px; font-variant-numeric: tabular-nums;
|
|
278
|
+
color: var(--secondary-foreground, #dcdcde);
|
|
279
|
+
cursor: default;
|
|
280
|
+
transition: background 120ms ease;
|
|
281
|
+
}
|
|
282
|
+
:host-context(:root:not([data-agy-theme="md3e"])) .row[aria-expanded] { cursor: pointer; }
|
|
283
|
+
:host-context(:root:not([data-agy-theme="md3e"])) .row[aria-expanded]:hover { background: var(--k-hover); }
|
|
284
|
+
:host-context(:root:not([data-agy-theme="md3e"])) .dot { width: 6px; height: 6px; border-radius: 50%; background: var(--c); }
|
|
285
|
+
:host-context(:root:not([data-agy-theme="md3e"])) .n, :host-context(:root:not([data-agy-theme="md3e"])) .p {
|
|
286
|
+
font-size: 11px; font-family: var(--font-mono, monospace); color: var(--muted-foreground, #86868c);
|
|
287
|
+
}
|
|
288
|
+
:host-context(:root:not([data-agy-theme="md3e"])) .row svg {
|
|
289
|
+
width: 12px; height: 12px; color: var(--muted-foreground, #86868c); opacity: 0.4;
|
|
290
|
+
transition: rotate 200ms ease, opacity 150ms ease;
|
|
291
|
+
rotate: 0deg;
|
|
292
|
+
}
|
|
293
|
+
:host-context(:root:not([data-agy-theme="md3e"])) .row[aria-expanded="true"] svg { rotate: 90deg; opacity: 0.8; }
|
|
294
|
+
:host-context(:root:not([data-agy-theme="md3e"])) .kids .row { padding-left: 18px; min-height: 22px; opacity: 0.85; }
|
|
295
|
+
:host-context(:root:not([data-agy-theme="md3e"])) .more { padding: 2px 8px 4px 28px; font-size: 11px; color: var(--muted-foreground, #86868c); }
|
|
296
|
+
|
|
297
|
+
/* =========================================================================
|
|
298
|
+
MD3E THEME STYLING (Expressive tonal container, pill shapes, spring motion)
|
|
299
|
+
========================================================================= */
|
|
300
|
+
:host-context([data-agy-theme="md3e"]) {
|
|
301
|
+
color: var(--md-sys-color-on-surface, var(--foreground));
|
|
302
|
+
font-family: var(--font-sans, "Roboto Variable", Roboto, sans-serif);
|
|
303
|
+
/* tonal roles that stay apart in every seed (primary and
|
|
304
|
+
primary-container are near twins in the expressive schemes) */
|
|
305
|
+
--k-conversation: var(--md-sys-color-primary);
|
|
306
|
+
--k-rules: var(--md-sys-color-secondary);
|
|
307
|
+
--k-skills: var(--md-sys-color-tertiary);
|
|
308
|
+
--k-mcp: var(--md-sys-color-outline);
|
|
309
|
+
--k-other: var(--md-sys-color-outline-variant);
|
|
310
|
+
--k-free: var(--md-sys-color-secondary-container); /* the M3 progress track */
|
|
311
|
+
}
|
|
312
|
+
/* the Rounded symbol, as md3e-icons.css does for the app's own icons */
|
|
313
|
+
:host-context([data-agy-theme="md3e"]) svg path {
|
|
314
|
+
d: path("M522 -480 358 -644Q347 -655 347 -670Q347 -684 358 -695Q369 -706 384 -706Q398 -706 409 -695L599 -505Q604 -500 606 -494Q609 -487 609 -480Q609 -473 606 -466Q604 -460 599 -455L409 -265Q398 -254 384 -254Q370 -255 359 -266Q348 -277 348 -292Q348 -306 359 -317Z");
|
|
315
|
+
}
|
|
316
|
+
:host-context([data-agy-theme="md3e"]) .head {
|
|
317
|
+
display: flex; align-items: center; gap: 6px; width: 100%; min-height: 28px; padding: 4px 8px;
|
|
318
|
+
border-radius: var(--md-sys-shape-corner-full, 14px);
|
|
319
|
+
font: var(--md-sys-typescale-label-medium, 500 12px/16px Roboto, sans-serif);
|
|
320
|
+
cursor: pointer;
|
|
321
|
+
transition: background-color 15ms linear;
|
|
322
|
+
}
|
|
323
|
+
:host-context([data-agy-theme="md3e"]) .head:hover, :host-context([data-agy-theme="md3e"]) .row[aria-expanded]:hover {
|
|
324
|
+
background: color-mix(in srgb, currentColor 8%, transparent);
|
|
325
|
+
}
|
|
326
|
+
:host-context([data-agy-theme="md3e"]) .title {
|
|
327
|
+
color: var(--md-sys-color-on-surface-variant, var(--muted-foreground));
|
|
328
|
+
}
|
|
329
|
+
:host-context([data-agy-theme="md3e"]) .chip {
|
|
330
|
+
padding: 1px 8px; border-radius: var(--md-sys-shape-corner-full, 999px);
|
|
331
|
+
background: var(--md-sys-color-secondary-container, var(--border));
|
|
332
|
+
color: var(--md-sys-color-on-secondary-container, inherit);
|
|
333
|
+
font-size: 11px; line-height: 16px; font-variant-numeric: tabular-nums;
|
|
334
|
+
transition: background-color var(--md-color-fast, 150ms), color var(--md-color-fast, 150ms), border-radius var(--md-fast, 300ms);
|
|
335
|
+
}
|
|
336
|
+
/* Warnings: tertiary at 80%, error at 95%; the chip also changes
|
|
337
|
+
shape (full -> 6px) to say the state changed */
|
|
338
|
+
:host-context([data-agy-theme="md3e"]) [data-level="warn"] .chip {
|
|
339
|
+
background: var(--md-sys-color-tertiary-container); color: var(--md-sys-color-on-tertiary-container);
|
|
340
|
+
}
|
|
341
|
+
:host-context([data-agy-theme="md3e"]) [data-level="critical"] .chip {
|
|
342
|
+
border-radius: 6px;
|
|
343
|
+
background: var(--md-sys-color-error-container); color: var(--md-sys-color-on-error-container);
|
|
344
|
+
}
|
|
345
|
+
:host-context([data-agy-theme="md3e"]) .status {
|
|
346
|
+
padding: 0 8px 8px; font: var(--md-sys-typescale-body-small, 400 12px/16px Roboto, sans-serif);
|
|
347
|
+
color: var(--md-sys-color-tertiary);
|
|
348
|
+
}
|
|
349
|
+
:host-context([data-agy-theme="md3e"]) [data-level="critical"] .status { color: var(--md-sys-color-error); }
|
|
350
|
+
:host-context([data-agy-theme="md3e"]) [data-level="critical"] .bar::after { background: var(--md-sys-color-error); }
|
|
351
|
+
:host-context([data-agy-theme="md3e"]) .total { padding: 6px 8px 8px; }
|
|
352
|
+
:host-context([data-agy-theme="md3e"]) .total-label {
|
|
353
|
+
font: var(--md-sys-typescale-label-small, 500 11px/16px Roboto, sans-serif); color: var(--md-sys-color-on-surface-variant, var(--muted-foreground));
|
|
354
|
+
}
|
|
355
|
+
:host-context([data-agy-theme="md3e"]) .total-value {
|
|
356
|
+
font: var(--md-sys-typescale-body-small, 400 12px/16px Roboto, sans-serif); color: var(--md-sys-color-on-surface, var(--foreground));
|
|
357
|
+
}
|
|
358
|
+
:host-context([data-agy-theme="md3e"]) .head svg {
|
|
359
|
+
margin-left: auto; width: 16px; height: 16px;
|
|
360
|
+
color: var(--md-sys-color-on-surface-variant, var(--muted-foreground));
|
|
361
|
+
rotate: 90deg;
|
|
362
|
+
transition: rotate 300ms cubic-bezier(0.2, 0, 0, 1.2);
|
|
363
|
+
}
|
|
364
|
+
:host-context([data-agy-theme="md3e"]) .head[aria-expanded="false"] svg { rotate: 0deg; }
|
|
365
|
+
:host-context([data-agy-theme="md3e"]) .summary {
|
|
366
|
+
padding: 0 8px 8px; color: var(--md-sys-color-on-surface-variant, var(--muted-foreground));
|
|
367
|
+
font: var(--md-sys-typescale-body-small, 400 12px/16px Roboto, sans-serif); font-variant-numeric: tabular-nums;
|
|
368
|
+
}
|
|
369
|
+
:host-context([data-agy-theme="md3e"]) .bar {
|
|
370
|
+
position: relative; display: flex; gap: 3px; height: 6px; margin: 0 8px 4px;
|
|
371
|
+
}
|
|
372
|
+
:host-context([data-agy-theme="md3e"]) .seg {
|
|
373
|
+
flex: var(--v) 1 0; min-width: 3px; border-radius: 3px; background: var(--c);
|
|
374
|
+
transition: flex-grow var(--md-sys-motion-spatial-default-duration, 500ms) var(--md-sys-motion-spatial-default-easing, cubic-bezier(0.2, 0, 0, 1));
|
|
375
|
+
}
|
|
376
|
+
:host-context([data-agy-theme="md3e"]) .seg.free {
|
|
377
|
+
background: var(--k-free);
|
|
378
|
+
}
|
|
379
|
+
:host-context([data-agy-theme="md3e"]) .bar::after {
|
|
380
|
+
content: ""; position: absolute; right: 1px; top: 1px; width: 4px; height: 4px; border-radius: 50%;
|
|
381
|
+
background: var(--md-sys-color-primary, var(--primary));
|
|
382
|
+
}
|
|
383
|
+
:host-context([data-agy-theme="md3e"]) .row {
|
|
384
|
+
display: grid; grid-template-columns: 8px 1fr auto 44px 16px; align-items: center; gap: 8px;
|
|
385
|
+
width: 100%; min-height: 28px; padding: 2px 8px; border-radius: var(--md-sys-shape-corner-full, 14px);
|
|
386
|
+
font: var(--md-sys-typescale-body-small, 400 12px/16px Roboto, sans-serif); font-variant-numeric: tabular-nums;
|
|
387
|
+
transition: background-color 15ms linear;
|
|
388
|
+
}
|
|
389
|
+
:host-context([data-agy-theme="md3e"]) .dot { width: 8px; height: 8px; border-radius: 50%; background: var(--c); }
|
|
390
|
+
:host-context([data-agy-theme="md3e"]) .n, :host-context([data-agy-theme="md3e"]) .p {
|
|
391
|
+
text-align: end; color: var(--md-sys-color-on-surface-variant, var(--muted-foreground));
|
|
392
|
+
}
|
|
393
|
+
:host-context([data-agy-theme="md3e"]) .row svg {
|
|
394
|
+
width: 14px; height: 14px; color: var(--md-sys-color-on-surface-variant, var(--muted-foreground));
|
|
395
|
+
transition: rotate 300ms cubic-bezier(0.2, 0, 0, 1.2);
|
|
396
|
+
rotate: 0deg;
|
|
397
|
+
}
|
|
398
|
+
:host-context([data-agy-theme="md3e"]) .row[aria-expanded="true"] svg { rotate: 90deg; }
|
|
399
|
+
:host-context([data-agy-theme="md3e"]) .kids .row { grid-template-columns: 8px 1fr auto 44px 16px; min-height: 24px; opacity: 0.85; padding-left: 16px; }
|
|
400
|
+
:host-context([data-agy-theme="md3e"]) .more { padding: 2px 8px 4px 32px; font-size: 11px; color: var(--md-sys-color-on-surface-variant, var(--muted-foreground)); }
|
|
401
|
+
</style>
|
|
402
|
+
<section aria-label="Context window" data-level="">
|
|
403
|
+
<button class="head" aria-expanded="true"><span class="title">Context</span><span class="chip" hidden></span>${CHEVRON}</button>
|
|
404
|
+
<div class="summary"></div>
|
|
405
|
+
<div class="status" role="status" hidden></div>
|
|
406
|
+
<div class="bar" role="meter" aria-label="Context used" aria-valuemin="0" aria-valuemax="100"></div>
|
|
407
|
+
<div class="total" hidden><div class="total-label"></div><div class="total-value"></div></div>
|
|
408
|
+
<div class="body"><div class="clip"><ul class="legend"></ul></div></div>
|
|
409
|
+
</section>`;
|
|
410
|
+
state = {
|
|
411
|
+
section: shadow.querySelector('section'), head: shadow.querySelector('.head'), chip: shadow.querySelector('.chip'),
|
|
412
|
+
summary: shadow.querySelector('.summary'), status: shadow.querySelector('.status'), bar: shadow.querySelector('.bar'),
|
|
413
|
+
total: shadow.querySelector('.total'), totalLabel: shadow.querySelector('.total-label'), totalValue: shadow.querySelector('.total-value'),
|
|
414
|
+
body: shadow.querySelector('.body'), legend: shadow.querySelector('.legend'),
|
|
415
|
+
};
|
|
416
|
+
state.head.addEventListener('click', () => {
|
|
417
|
+
open = !open;
|
|
418
|
+
state.head.setAttribute('aria-expanded', String(open));
|
|
419
|
+
state.body.hidden = !open;
|
|
420
|
+
});
|
|
421
|
+
const toggleGroup = (e) => {
|
|
422
|
+
const row = e.target.closest('.row[aria-expanded]');
|
|
423
|
+
if (!row) return;
|
|
424
|
+
const name = row.dataset.group;
|
|
425
|
+
openGroups.has(name) ? openGroups.delete(name) : openGroups.add(name);
|
|
426
|
+
row.setAttribute('aria-expanded', String(openGroups.has(name)));
|
|
427
|
+
row.nextElementSibling.hidden = !openGroups.has(name);
|
|
428
|
+
};
|
|
429
|
+
state.legend.addEventListener('click', toggleGroup);
|
|
430
|
+
state.legend.addEventListener('keydown', (e) => {
|
|
431
|
+
if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); toggleGroup(e); }
|
|
432
|
+
});
|
|
433
|
+
}
|
|
434
|
+
list.prepend(hostEl);
|
|
435
|
+
signature = '';
|
|
436
|
+
return true;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
const esc = (s) => String(s).replace(/[&<>"]/g, (c) => ({ '&': '&', '<': '<', '>': '>', '"': '"' })[c]);
|
|
440
|
+
const row = (color, name, tokens, share, group) => `
|
|
441
|
+
<div class="row"${group ? ` aria-expanded="${openGroups.has(group)}" data-group="${esc(group)}" role="button" tabindex="0"` : ''} style="--c:${color}">
|
|
442
|
+
<span class="dot"></span><span class="name">${esc(name)}</span><span class="n">${fmt(tokens)}</span><span class="p">${share}</span>${group ? CHEVRON : '<span></span>'}
|
|
443
|
+
</div>`;
|
|
444
|
+
|
|
445
|
+
let last = null;
|
|
446
|
+
function render() {
|
|
447
|
+
if (!state || !last) return;
|
|
448
|
+
const { used, windowSize, total: conv } = last;
|
|
449
|
+
const groups = base ?? [];
|
|
450
|
+
const custom = groups.reduce((s, g) => s + g.tokens, 0);
|
|
451
|
+
const parts = [];
|
|
452
|
+
if (used != null) parts.push({ id: 'conversation', name: 'Conversation & system', tokens: Math.max(0, used - custom), color: color('conversation') });
|
|
453
|
+
for (const g of groups) {
|
|
454
|
+
parts.push({ id: `g${g.type}`, name: g.name, tokens: g.tokens, color: color(GROUP_KINDS[g.type] || 'other'), children: g.children });
|
|
455
|
+
}
|
|
456
|
+
const total = windowSize || Math.max(used ?? 0, custom) || 1;
|
|
457
|
+
const free = windowSize && used != null ? Math.max(0, windowSize - used) : null;
|
|
458
|
+
const share = windowSize && used != null ? used / windowSize : null;
|
|
459
|
+
const [, level = '', message = ''] = (share != null && LEVELS.find(([at]) => share >= at)) || [];
|
|
460
|
+
|
|
461
|
+
const convText = conv && (conv.input || conv.output)
|
|
462
|
+
? [`${fmt(conv.input)} in`, `${fmt(conv.output)} out`, conv.cache ? `${fmt(conv.cache)} cache` : ''].filter(Boolean).join(' · ')
|
|
463
|
+
: '';
|
|
464
|
+
const convLabel = conv?.from > 0 ? `This conversation, counted from step ${conv.from.toLocaleString('en-US')}` : 'This conversation';
|
|
465
|
+
|
|
466
|
+
const sig = JSON.stringify([used, windowSize, convText, convLabel, parts.map((p) => [p.id, p.tokens]), [...openGroups]]);
|
|
467
|
+
if (sig === signature) return;
|
|
468
|
+
signature = sig;
|
|
469
|
+
|
|
470
|
+
hostEl.hidden = used == null && !groups.length;
|
|
471
|
+
state.section.dataset.level = level;
|
|
472
|
+
state.chip.hidden = share == null;
|
|
473
|
+
state.chip.textContent = share != null ? pct(used, windowSize) : '';
|
|
474
|
+
state.summary.textContent = used == null
|
|
475
|
+
? 'No model response yet'
|
|
476
|
+
: windowSize ? `${fmt(used)} of ${fmt(windowSize)} tokens used` : `${fmt(used)} tokens used`;
|
|
477
|
+
state.status.hidden = !message;
|
|
478
|
+
state.status.textContent = message;
|
|
479
|
+
state.total.hidden = !convText;
|
|
480
|
+
state.totalLabel.textContent = convLabel;
|
|
481
|
+
state.totalValue.textContent = convText;
|
|
482
|
+
state.bar.setAttribute('aria-valuenow', share != null ? String(Math.min(100, Math.round(share * 100))) : '0');
|
|
483
|
+
|
|
484
|
+
// Segments keep their elements so the widths spring to new values
|
|
485
|
+
const segs = [...parts, ...(free != null ? [{ id: 'free', tokens: free }] : [])];
|
|
486
|
+
const ids = segs.map((s) => s.id).join();
|
|
487
|
+
if (state.bar.dataset.ids !== ids) {
|
|
488
|
+
state.bar.dataset.ids = ids;
|
|
489
|
+
state.bar.innerHTML = segs.map((s) => `<span class="seg${s.id === 'free' ? ' free' : ''}" data-id="${s.id}"></span>`).join('');
|
|
490
|
+
}
|
|
491
|
+
for (const s of segs) {
|
|
492
|
+
const el = state.bar.querySelector(`[data-id="${s.id}"]`);
|
|
493
|
+
if (el) {
|
|
494
|
+
el.style.setProperty('--v', String(Math.max(s.tokens / total, 0.004)));
|
|
495
|
+
if (s.color) el.style.setProperty('--c', s.color);
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
state.legend.innerHTML = [
|
|
500
|
+
...parts.map((p) => {
|
|
501
|
+
const share = pct(p.tokens, total);
|
|
502
|
+
if (!p.children?.length) return `<li>${row(p.color, p.name, p.tokens, share)}</li>`;
|
|
503
|
+
const kids = p.children.slice(0, 8).map((c) => row(p.color, c.name, c.tokens, pct(c.tokens, total))).join('');
|
|
504
|
+
const more = p.children.length > 8 ? `<div class="more">and ${p.children.length - 8} more</div>` : '';
|
|
505
|
+
return `<li>${row(p.color, p.name, p.tokens, share, p.name)}<div class="kids"${openGroups.has(p.name) ? '' : ' hidden'}><div class="clip">${kids}${more}</div></div></li>`;
|
|
506
|
+
}),
|
|
507
|
+
free != null ? `<li>${row(color('free'), 'Free space', free, pct(free, total))}</li>` : '',
|
|
508
|
+
].join('');
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
function tick() {
|
|
512
|
+
try {
|
|
513
|
+
// Count even while the card is hidden: the app keeps only the last ~240
|
|
514
|
+
// steps, so calls made meanwhile would otherwise fall out uncounted
|
|
515
|
+
const slice = readSlice();
|
|
516
|
+
const total = countTotal(slice);
|
|
517
|
+
if (!mount()) return;
|
|
518
|
+
if (document.hidden) return;
|
|
519
|
+
last = { used: readUsage(slice), windowSize: readWindow(slice), total };
|
|
520
|
+
refreshBase(slice);
|
|
521
|
+
render();
|
|
522
|
+
} catch {
|
|
523
|
+
// app internals changed: leave the card as it was
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
const timer = setInterval(tick, TICK_MS);
|
|
528
|
+
tick();
|
|
529
|
+
|
|
530
|
+
return {
|
|
531
|
+
dispose() {
|
|
532
|
+
clearInterval(timer);
|
|
533
|
+
hostEl?.remove();
|
|
534
|
+
},
|
|
535
|
+
};
|
|
536
|
+
}
|