fix(desktop): correct context capacity overflow display / 修复上下文容量条超限显示 (#8307)

* fix(desktop): align context capacity meter with usage

Problem:
The context window meter mixed token composition into a capacity gauge, so the visible fill could disagree with the displayed usage. Over-limit values were also clamped to 100% without an explicit status.

Root cause:
The meter reused token breakdown segments and a single clamped percentage for both physical layout and user-facing text.

Fix:
Render a single capacity fill, preserve the raw percentage for labels and status, cap only the physical fill, keep the compaction marker, and apply the same contract to the context ring popover.

Verification:
- 68 focused context UI assertions
- pnpm typecheck
- pnpm check:css
- pnpm build
- Playwright Chromium DOM, hover, computed-style, accessibility, and console checks

* fix(desktop): preserve just-over-limit context status

Problem:
Context usage that exceeded the window by less than half a percentage point rounded to 100%, so the panel and ring hid the overflow.

Root cause:
The over-limit status was derived from the rounded integer percentage instead of the exact token counts.

Fix:
Keep the existing integer percentage contract, but force the raw value above 100 whenever used tokens exceed the window. Add focused helper, panel, and ring regressions for 1001/1000.

Verification:
- 69 focused context UI assertions
- pnpm build
- In-app Chromium DOM, hover, computed-width, accessibility, and console checks

* fix(desktop): align status bar context overflow

Problem:
The status bar still clamped over-limit context usage to 100%, so it disagreed with the context panel and ring.

Root cause:
The status bar kept a local percentage calculation instead of using the shared raw/display context-window contract.

Fix:
Move contextWindowPercentages into a shared frontend library and use its raw percentage for the status bar label while preserving capped graphical fills.

Verification:
- 114 focused context and status-bar assertions
- pnpm build
- Playwright Chromium DOM, visibility, and console check

* fix(desktop): satisfy context meter lint budget

Problem:
The shared context percentage helper pushed StatusBar.tsx one line beyond its baselined file-size budget, leaving PR #8307's lint job red.

Root cause:
formatRate used a two-branch statement where a behavior-equivalent conditional expression is sufficient.

Fix:
Keep the shared context percentage contract and simplify formatRate without changing its zero or negative denominator behavior.

Verification:
- go run ./tools/repolint
- 54 context panel assertions
- 6 capacity meter assertions
- 9 context ring assertions
- 45 status bar assertions
- pnpm test:typecheck
- pnpm build
This commit is contained in:
SivanCola authored and GitHub committed 2026-08-11 16:07:16 +08:00
1 parent 717497e25d
commit bb58eec24e
12 files changed
+208 -60

No files matched your search

@@ -1,6 +1,7 @@
// Run: tsx src/__tests__/context-panel-breakdown.test.ts
import { cacheHitTone, contextBreakdown, contextCostDisplay, contextSessionCache, contextSourceRows, contextUsageRefreshKey, contextWindowStatus, formatCacheHitRate, formatMetricTokens, formatSharePercent, liveTurnUsageBreakdown } from "../components/ContextPanel";
import { contextWindowPercentages } from "../lib/contextWindow";
import { currencySymbol, formatMoney, formatMoneyLocalized } from "../lib/money";
import type { WireUsage } from "../lib/types";
@@ -168,10 +169,21 @@ eq(
console.log("\ncontext window status");
eq(
contextWindowPercentages(1_400_000, 1_000_000),
{ raw: 140, display: 100 },
"over-limit context preserves the raw percentage while capping the meter fill",
);
eq(
contextWindowPercentages(1_001, 1_000),
{ raw: 101, display: 100 },
"just-over-limit context remains visibly over 100 percent after integer formatting",
);
eq(contextWindowStatus(33, 80), { tone: "good", key: "context.windowStatusHealthy" }, "low usage stays healthy");
eq(contextWindowStatus(72, 80), { tone: "notice", key: "context.windowStatusWatch" }, "usage near compact threshold warns early");
eq(contextWindowStatus(80, 80), { tone: "warn", key: "context.windowStatusPastCompact" }, "compact threshold reached takes warning tone");
eq(contextWindowStatus(91, 80), { tone: "warn", key: "context.windowStatusNearLimit" }, "near hard limit overrides compact status");
eq(contextWindowStatus(140, 80), { tone: "warn", key: "context.windowStatusOverLimit" }, "over-limit context has a distinct status");
console.log("\ncontext panel cost");
@@ -0,0 +1,116 @@
// Run: tsx src/__tests__/context-panel-capacity.test.tsx
import { JSDOM } from "jsdom";
import React from "react";
import { act } from "react";
import { createRoot } from "react-dom/client";
import { ContextPanel } from "../components/ContextPanel";
import { LocaleProvider } from "../lib/i18n";
import type { ContextPanelInfo } from "../lib/types";
let passed = 0;
let failed = 0;
function ok(value: boolean, label: string) {
if (value) {
process.stdout.write(` PASS ${label}\n`);
passed += 1;
} else {
process.stdout.write(` FAIL ${label}\n`);
failed += 1;
}
}
function eq(actual: unknown, expected: unknown, label: string) {
if (actual === expected) ok(true, label);
else ok(false, `${label}: expected ${JSON.stringify(expected)}, got ${JSON.stringify(actual)}`);
}
function wait(ms = 0): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, ms));
}
class TestResizeObserver {
observe() {}
unobserve() {}
disconnect() {}
}
function installDom() {
const dom = new JSDOM("<!doctype html><html><body><div id=\"root\"></div></body></html>", {
pretendToBeVisual: true,
url: "http://localhost/",
});
(globalThis as typeof globalThis & { IS_REACT_ACT_ENVIRONMENT: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
globalThis.window = dom.window as unknown as Window & typeof globalThis;
globalThis.document = dom.window.document;
globalThis.Node = dom.window.Node;
globalThis.HTMLElement = dom.window.HTMLElement;
globalThis.Event = dom.window.Event;
globalThis.ResizeObserver = TestResizeObserver;
return dom;
}
function emptyPanelInfo(): ContextPanelInfo {
return {
usedTokens: 0,
windowTokens: 0,
promptTokens: 0,
completionTokens: 0,
totalTokens: 0,
reasoningTokens: 0,
cacheHitTokens: 0,
cacheMissTokens: 0,
sessionCacheHitTokens: 0,
sessionCacheMissTokens: 0,
sessionCompletionTokens: 0,
requestCount: 0,
elapsedMs: 0,
sessionCost: 0,
readFiles: [],
changedFiles: [],
};
}
console.log("\ncontext panel capacity");
const dom = installDom();
(window as unknown as { go: { main: { App: { ContextPanel: () => Promise<ContextPanelInfo> } } } }).go = {
main: { App: { ContextPanel: async () => emptyPanelInfo() } },
};
const rootEl = document.getElementById("root");
if (!rootEl) throw new Error("missing root");
const root = createRoot(rootEl);
await act(async () => {
root.render(
<LocaleProvider>
<ContextPanel
tabId="tab-capacity"
context={{ used: 1_001, window: 1_000, sessionTokens: 1_001, compactRatio: 0.8 }}
/>
</LocaleProvider>,
);
await wait();
});
const capacity = document.querySelector(".context-panel__capacity-card");
const meter = capacity?.querySelector(".context-panel__capacity-meter");
const fill = meter?.querySelector(".context-panel__progress-fill") as HTMLElement | null;
const usedPin = meter?.querySelector(".context-panel__capacity-pin--used");
const compactMarker = meter?.querySelector(".context-panel__compact-marker") as HTMLElement | null;
eq(capacity?.querySelector(".context-panel__capacity-status")?.textContent, "Over context limit", "over-limit status is explicit");
eq(usedPin?.textContent, "101%", "just-over-limit percentage pin remains visibly over 100 percent");
eq(fill?.style.width, "100%", "capacity fill is capped at the physical track width");
eq(meter?.querySelectorAll(".context-panel__progress-segment").length, 0, "capacity meter does not mix token composition into its fill");
eq(compactMarker?.style.left, "80%", "compression threshold marker stays at the configured ratio");
ok(meter?.getAttribute("aria-label")?.includes("101% used") === true, "accessible summary reports the over-limit ratio");
await act(async () => {
root.unmount();
});
dom.window.close();
console.log(`\n${passed} passed, ${failed} failed`);
if (failed > 0) process.exit(1);
@@ -171,6 +171,30 @@ console.log("\ncontext window ring");
dom.window.close();
}
{
const dom = installDom();
installContextPanelMock(async () => contextPanelInfo(0));
const { root } = await renderRing({ context: { used: 1_001, window: 1_000, compactRatio: 0.8 } });
const button = document.querySelector(".context-ring") as HTMLButtonElement | null;
if (!button) throw new Error("missing over-limit context ring button");
await act(async () => {
button.dispatchEvent(new MouseEvent("mouseover", { bubbles: true, relatedTarget: null }));
await wait(220);
});
const popover = document.querySelector(".context-ring-popover");
const fill = popover?.querySelector(".context-ring-popover__fill") as HTMLElement | null;
eq(popover?.querySelector(".context-ring-popover__pct")?.textContent, "101%", "ring popover keeps a just-over-limit ratio visibly above 100 percent");
eq(fill?.style.width, "100%", "ring popover fill is capped at the physical track width");
eq(popover?.querySelectorAll(".context-ring-popover__seg").length, 0, "ring popover does not mix token composition into its capacity fill");
await act(async () => {
root.unmount();
});
dom.window.close();
}
{
const dom = installDom();
const calls: string[] = [];
@@ -47,6 +47,15 @@ console.log("\nstatus bar workspace");
);
}
{
const html = renderStatusBar({
items: ["context"],
context: { used: 1_001, window: 1_000, sessionTokens: 1_001, compactRatio: 0.8 },
});
ok(html.includes(">101%</b>"), "context status preserves a just-over-limit percentage");
ok(!html.includes(">100%</b>"), "context status does not clamp an over-limit percentage to 100 percent");
}
{
const remoteHosts = [
{ id: "demo", label: "demo", host: "192.0.2.10", port: 22, user: "dev", identityFile: "", proxyJump: "", defaultWorkspace: "~/app", serveInstall: "auto", useSSHConfig: false },
@@ -3,6 +3,7 @@
import { useCallback, useEffect, useRef, useState, type ReactNode } from "react";
import { asArray } from "../lib/array";
import { app } from "../lib/bridge";
import { contextWindowPercentages } from "../lib/contextWindow";
import { useI18n, type Locale, type Translator } from "../lib/i18n";
import { formatMoneyLocalized } from "../lib/money";
import { formatTokens, formatOptionalTokens } from "../lib/format";
@@ -241,7 +242,9 @@ export function contextBreakdown(
};
}
export function contextWindowStatus(usagePct: number, compactPct: number): ContextWindowStatus {
export function contextWindowStatus(rawUsagePct: number, compactPct: number): ContextWindowStatus {
if (rawUsagePct > 100) return { tone: "warn", key: "context.windowStatusOverLimit" };
const usagePct = Math.min(100, Math.max(0, rawUsagePct));
if (usagePct >= 90) return { tone: "warn", key: "context.windowStatusNearLimit" };
if (compactPct > 0 && usagePct >= compactPct) return { tone: "warn", key: "context.windowStatusPastCompact" };
if (compactPct > 0 && usagePct >= Math.max(0, compactPct - 10)) return { tone: "notice", key: "context.windowStatusWatch" };
@@ -419,7 +422,9 @@ export function ContextPanel({
const readFiles = asArray(info?.readFiles);
const changedFiles = asArray(info?.changedFiles);
const usagePct = windowTokens > 0 ? Math.min(100, Math.round((usedTokens / windowTokens) * 100)) : 0;
const usagePercentages = contextWindowPercentages(usedTokens, windowTokens);
const rawUsagePct = usagePercentages.raw;
const usagePct = usagePercentages.display;
const compactRatio = context?.compactRatio && context.compactRatio > 0 ? context.compactRatio : 0.85;
const compactPct = Math.round(compactRatio * 100);
const reportedTriggerTokens = context?.maintenance?.triggerTokens ?? 0;
@@ -439,7 +444,7 @@ export function ContextPanel({
const elapsed = info?.elapsedMs && info.elapsedMs > 0 ? info.elapsedMs : derivedElapsed;
const derivedRequestCount = Math.max(readFiles.length + changedFiles.length, 0);
const requestCount = info?.requestCount && info.requestCount > 0 ? info.requestCount : derivedRequestCount;
const windowStatus = contextWindowStatus(usagePct, compactPct);
const windowStatus = contextWindowStatus(rawUsagePct, compactPct);
const balanceLabel = balance?.available && balance.display ? balance.display : "-";
const turnEstimated = usage?.estimated === true || info?.estimated === true;
const sessionEstimated = info?.sessionEstimated === true || context?.estimated === true;
@@ -453,7 +458,7 @@ export function ContextPanel({
const compactMarkerPct = Math.max(0, Math.min(100, compactPct));
const usageMarkerPct = Math.max(6, Math.min(94, usagePct));
const compactLabelPct = Math.max(6, Math.min(94, compactMarkerPct));
const usageSummary = t("context.windowUsageSummary", { used: usedLabel, window: windowLabel, pct: usagePct });
const usageSummary = t("context.windowUsageSummary", { used: usedLabel, window: windowLabel, pct: rawUsagePct });
const compactSummary = t("context.windowCompactRemaining", { used: usedLabel, window: windowLabel, tokens: compactRemainingLabel, pct: compactPct });
const activeAnalysisView: UsageAnalysisView = showSourceUsageRows ? analysisView : "type";
const tokenTypeRows = [
@@ -533,14 +538,11 @@ export function ContextPanel({
</div>
<div className="context-panel__usage-progress context-panel__capacity-meter" aria-label={`${t(windowStatus.key)}. ${usageSummary}. ${compactSummary}`}>
<div className="context-panel__capacity-scale" aria-hidden="true">
<span className="context-panel__capacity-pin context-panel__capacity-pin--used" style={{ left: `${usageMarkerPct}%` }}>{usagePct}%</span>
<span className="context-panel__capacity-pin context-panel__capacity-pin--used" style={{ left: `${usageMarkerPct}%` }}>{rawUsagePct}%</span>
<span className="context-panel__capacity-pin context-panel__capacity-pin--compact" style={{ left: `${compactLabelPct}%` }}>{compactPct}%</span>
</div>
<div className="context-panel__progress-track" aria-hidden="true">
<span className="context-panel__progress-segment context-panel__progress-segment--prompt" style={{ width: `${breakdown.promptPct}%` }} />
<span className="context-panel__progress-segment context-panel__progress-segment--completion" style={{ width: `${Math.max(0, breakdown.completionPct - breakdown.promptPct)}%` }} />
<span className="context-panel__progress-segment context-panel__progress-segment--reasoning" style={{ width: `${Math.max(0, breakdown.reasoningPct - breakdown.completionPct)}%` }} />
<span className="context-panel__progress-segment context-panel__progress-segment--other" style={{ width: `${Math.max(0, breakdown.otherPct - breakdown.reasoningPct)}%` }} />
<span className="context-panel__progress-fill" style={{ width: `${usagePct}%` }} />
<span className="context-panel__compact-marker" style={{ left: `${compactMarkerPct}%` }} />
</div>
</div>
@@ -1,11 +1,11 @@
import { useCallback, useEffect, useRef, useState } from "react";
import { app } from "../lib/bridge";
import { contextWindowPercentages } from "../lib/contextWindow";
import { useI18n } from "../lib/i18n";
import { formatMoneyLocalized } from "../lib/money";
import type { BalanceInfo, ContextInfo, ContextPanelInfo } from "../lib/types";
import { AnchoredPopover } from "./AnchoredPopover";
import {
contextBreakdown,
contextWindowStatus,
formatCacheHitRate,
} from "./ContextPanel";
@@ -53,10 +53,12 @@ export function ContextWindowRing({ enabled = true, context, tabId, turnCost, cu
const used = context?.used ?? 0;
const windowTokens = context?.window ?? 0;
const usagePct = windowTokens > 0 ? Math.min(100, Math.round((used / windowTokens) * 100)) : 0;
const usagePercentages = contextWindowPercentages(used, windowTokens);
const rawUsagePct = usagePercentages.raw;
const usagePct = usagePercentages.display;
const compactRatio = context?.compactRatio && context.compactRatio > 0 ? context.compactRatio : 0.85;
const compactPct = Math.round(compactRatio * 100);
const status = contextWindowStatus(usagePct, compactPct);
const status = contextWindowStatus(rawUsagePct, compactPct);
const loadInfo = useCallback(() => {
if (!enabled || !tabId) return;
@@ -107,10 +109,6 @@ export function ContextWindowRing({ enabled = true, context, tabId, turnCost, cu
if (!enabled) return null;
const promptTokens = info?.promptTokens ?? 0;
const completionTokens = info?.completionTokens ?? 0;
const reasoningTokens = info?.reasoningTokens ?? 0;
const breakdown = contextBreakdown(used, windowTokens, promptTokens, completionTokens, reasoningTokens);
const turnCacheHit = cacheHitTokens ?? info?.cacheHitTokens ?? 0;
const turnCacheMiss = cacheMissTokens ?? info?.cacheMissTokens ?? 0;
const turnCacheRate = formatCacheHitRate(turnCacheHit, turnCacheMiss);
@@ -131,7 +129,7 @@ export function ContextWindowRing({ enabled = true, context, tabId, turnCost, cu
className={`context-ring${open ? " context-ring--open" : ""} context-ring--${status.tone}`}
onMouseEnter={onEnter}
onMouseLeave={onLeave}
aria-label={t("context.windowUsageSummary", { used: String(used), window: String(windowTokens), pct: usagePct })}
aria-label={t("context.windowUsageSummary", { used: String(used), window: String(windowTokens), pct: rawUsagePct })}
>
<svg width={RING} height={RING} viewBox={`0 0 ${RING} ${RING}`} className="context-ring__svg">
<circle className="context-ring__track" cx={RING / 2} cy={RING / 2} r={RING_R} fill="none" strokeWidth={3} />
@@ -159,16 +157,11 @@ export function ContextWindowRing({ enabled = true, context, tabId, turnCost, cu
<span className="context-ring-popover__title">
{fmtCompact(used)} / {fmtCompact(windowTokens)}
</span>
<span className="context-ring-popover__pct">{usagePct}%</span>
<span className="context-ring-popover__pct">{rawUsagePct}%</span>
</div>
<div className="context-ring-popover__gauge">
<div className="context-ring-popover__bar">
<span className="context-ring-popover__seg context-ring-popover__seg--prompt" style={{ width: `${breakdown.promptPct}%` }} />
<span className="context-ring-popover__seg context-ring-popover__seg--completion" style={{ width: `${Math.max(0, breakdown.completionPct - breakdown.promptPct)}%` }} />
{breakdown.reasoningTokens > 0 && (
<span className="context-ring-popover__seg context-ring-popover__seg--reasoning" style={{ width: `${Math.max(0, breakdown.reasoningPct - breakdown.completionPct)}%` }} />
)}
<span className="context-ring-popover__seg context-ring-popover__seg--other" style={{ width: `${Math.max(0, breakdown.otherPct - breakdown.reasoningPct)}%` }} />
<span className="context-ring-popover__fill" style={{ width: `${usagePct}%` }} />
<span className="context-ring-popover__mark context-ring-popover__mark--compact" style={{ left: `${compactPct}%` }} />
<span className="context-ring-popover__mark context-ring-popover__mark--attention" style={{ left: `30%` }} />
</div>
@@ -3,6 +3,7 @@ import { Activity, CircleDollarSign, CircleGauge, Database, FileOutput, Folder,
import { AnchoredPopover } from "./AnchoredPopover";
import { RemoteConnectionErrorDialog } from "./RemoteConnectionErrorDialog";
import { Tooltip } from "./Tooltip";
import { contextWindowPercentages } from "../lib/contextWindow";
import { useI18n, type Translator } from "../lib/i18n";
import { formatMoneyLocalized } from "../lib/money";
import { normalizeStatusBarItems, type StatusBarItemId } from "../lib/statusBarItems";
@@ -14,8 +15,7 @@ import { useRemoteStore } from "../store/remote";
type StatusBarLabelStyle = "icon" | "text";
function formatRate(hit: number, denom: number): string | null {
if (denom <= 0) return null;
return ((hit / denom) * 100).toFixed(2);
return denom > 0 ? ((hit / denom) * 100).toFixed(2) : null;
}
// nowRate is the SINGLE-TURN prompt cache-hit % (latest turn) — the higher,
@@ -230,7 +230,7 @@ export function StatusBar({
extensionStatuses?: ExtensionStatusEntry[];
}) {
const { locale, t } = useI18n();
const pct = context.window ? Math.min(100, Math.round((context.used / context.window) * 100)) : null;
const pct = context.window > 0 ? contextWindowPercentages(context.used, context.window).raw : null;
const compactPct = context.compactRatio ? Math.round(context.compactRatio * 100) : null;
const compactNear = pct !== null && compactPct !== null && pct >= Math.max(0, compactPct - 10);
const compactReached = pct !== null && compactPct !== null && pct >= compactPct;
+13
View File
@@ -0,0 +1,13 @@
export interface ContextWindowPercentages {
raw: number;
display: number;
}
export function contextWindowPercentages(usedTokens: number, windowTokens: number): ContextWindowPercentages {
if (!Number.isFinite(usedTokens) || !Number.isFinite(windowTokens) || usedTokens <= 0 || windowTokens <= 0) {
return { raw: 0, display: 0 };
}
const rounded = Math.max(0, Math.round((usedTokens / windowTokens) * 100));
const raw = usedTokens > windowTokens ? Math.max(101, rounded) : rounded;
return { raw, display: Math.min(100, raw) };
}
+1
View File
@@ -3088,6 +3088,7 @@ export const en = {
"context.windowStatusWatch": "Compression soon",
"context.windowStatusPastCompact": "Compression threshold reached",
"context.windowStatusNearLimit": "Near context limit",
"context.windowStatusOverLimit": "Over context limit",
"context.windowUsageSummary": "{pct}% used",
"context.windowCompactRemaining": "{used} / {window} · {tokens} until compression",
"context.windowCompactDistance": "To compact",
+1
View File
@@ -3174,6 +3174,7 @@ export const zhTW: Record<DictKey, string> = {
"context.windowStatusWatch": "即將壓縮",
"context.windowStatusPastCompact": "已到壓縮閾值",
"context.windowStatusNearLimit": "接近視窗上限",
"context.windowStatusOverLimit": "已超過視窗上限",
"context.windowUsageSummary": "{pct}% 已用",
"context.windowCompactRemaining": "{used} / {window} · 距壓縮還剩 {tokens}",
"context.windowCompactDistance": "距壓縮",
+1
View File
@@ -3091,6 +3091,7 @@ export const zh: Record<DictKey, string> = {
"context.windowStatusWatch": "即将压缩",
"context.windowStatusPastCompact": "已到压缩阈值",
"context.windowStatusNearLimit": "接近窗口上限",
"context.windowStatusOverLimit": "已超过窗口上限",
"context.windowUsageSummary": "{pct}% 已用",
"context.windowCompactRemaining": "{used} / {window} · 距压缩还剩 {tokens}",
"context.windowCompactDistance": "距压缩",
+9 -33
View File
@@ -25303,17 +25303,6 @@ body > .mermaid-diagram--fullscreen {
background: color-mix(in srgb, var(--fg-faint) 10%, transparent);
}
.context-panel__progress-segment {
display: block;
height: 100%;
min-width: 0;
}
.context-panel__progress-segment--prompt { background: #13a7a5; }
.context-panel__progress-segment--completion { background: #2f6df6; }
.context-panel__progress-segment--reasoning { background: #f97316; }
.context-panel__progress-segment--other { background: var(--border); }
.context-panel__compact-marker {
position: absolute;
top: 0;
@@ -25328,7 +25317,11 @@ body > .mermaid-diagram--fullscreen {
}
.context-panel__progress-fill {
display: none;
display: block;
height: 100%;
min-width: 0;
border-radius: inherit;
background: linear-gradient(90deg, color-mix(in srgb, var(--accent) 72%, #fff), var(--accent));
}
.context-panel__session-section,
@@ -31870,22 +31863,13 @@ body > .mermaid-diagram--fullscreen {
overflow: hidden;
background: color-mix(in srgb, var(--fg-faint) 10%, transparent);
}
.context-ring-popover__seg {
.context-ring-popover__fill {
height: 100%;
min-width: 0;
border-radius: inherit;
background: linear-gradient(90deg, color-mix(in srgb, var(--accent) 72%, #fff), var(--accent));
transition: width 0.3s var(--ease-out);
}
.context-ring-popover__seg--prompt {
background: color-mix(in srgb, var(--accent) 68%, var(--fg));
}
.context-ring-popover__seg--completion {
background: color-mix(in srgb, var(--accent) 48%, var(--fg));
}
.context-ring-popover__seg--reasoning {
background: color-mix(in srgb, var(--warn) 56%, var(--fg));
}
.context-ring-popover__seg--other {
background: color-mix(in srgb, var(--fg-faint) 22%, var(--fg));
}
/* 进度条标记线 */
.context-ring-popover__mark {
position: absolute;
@@ -34184,14 +34168,6 @@ body > .mermaid-diagram--fullscreen {
box-shadow: 0 0 0 1px color-mix(in srgb, var(--creation-panel-card) 88%, transparent);
}
.app--creation .context-panel__progress-fill {
display: block;
height: 100%;
min-width: 2px;
border-radius: inherit;
background: linear-gradient(90deg, color-mix(in srgb, var(--accent) 72%, #fff), var(--accent));
}
.app--creation .context-panel__session-metrics {
margin: 0;
padding: 0;