mirror of
https://github.com/block/buzz.git
synced 2026-08-18 06:50:31 +02:00
feat(desktop): display cache-read/write/fresh-input breakdown in agent-usage focused view
The plumbing already returned cacheReadTokens/cacheWriteTokens/freshInputTokens at every scope of AgentUsageSeries, but the focused view rendered only the four top-level stats and never surfaced them. Add an Input-breakdown subsection to the totals card (Cache read / Cache write / Fresh input) gated on any known cache value, and a compact per-model breakdown line. Fields follow the existing unknown-not-zero semantics: absent subsets are omitted, a known-but-incomplete value shows its lower bound with a Partial marker, and a scope with no cache data renders no subsection at all. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
This commit is contained in:
@@ -13,8 +13,10 @@ import {
|
||||
formatCoverageDate,
|
||||
formatEstimatedCostUsd,
|
||||
formatLocalDate,
|
||||
formatModelCacheBreakdown,
|
||||
formatTokenCountCompact,
|
||||
formatTokenCountExact,
|
||||
hasKnownCacheData,
|
||||
isPartialField,
|
||||
isUnknownField,
|
||||
MAX_RANGE_DAYS,
|
||||
@@ -443,6 +445,85 @@ test("isPartialField and isUnknownField classify usage fields correctly", () =>
|
||||
assert.equal(isUnknownField(usageField({ value: "0" })), false);
|
||||
});
|
||||
|
||||
// ── formatModelCacheBreakdown / hasKnownCacheData ────────────────────────────
|
||||
|
||||
test("hasKnownCacheData is true iff any cache subset carries a known value", () => {
|
||||
assert.equal(hasKnownCacheData(reportedUsage()), false, "all absent → false");
|
||||
for (const field of [
|
||||
"cacheReadTokens",
|
||||
"cacheWriteTokens",
|
||||
"freshInputTokens",
|
||||
]) {
|
||||
assert.equal(
|
||||
hasKnownCacheData(reportedUsage({ [field]: usageField({ value: "0" }) })),
|
||||
true,
|
||||
`a known ${field} (even zero) → true`,
|
||||
);
|
||||
}
|
||||
// A field that is incomplete but has no value is still unknown, not known.
|
||||
assert.equal(
|
||||
hasKnownCacheData(
|
||||
reportedUsage({
|
||||
cacheReadTokens: usageField({ value: null, incomplete: true }),
|
||||
}),
|
||||
),
|
||||
false,
|
||||
"incomplete with null value is unknown, not known",
|
||||
);
|
||||
});
|
||||
|
||||
test("formatModelCacheBreakdown omits absent subsets, never renders them as zero, and marks a known lower bound Partial", () => {
|
||||
const build = (overrides) =>
|
||||
modelUsage("m", null, { usage: reportedUsage(overrides) });
|
||||
|
||||
// No cache data at all → null, so the caller omits the line entirely.
|
||||
assert.equal(formatModelCacheBreakdown(build({})), null);
|
||||
|
||||
// Each known subset appears compact-formatted; absent ones are omitted
|
||||
// rather than shown as "0".
|
||||
assert.equal(
|
||||
formatModelCacheBreakdown(
|
||||
build({
|
||||
cacheReadTokens: usageField({ value: "1500" }),
|
||||
cacheWriteTokens: usageField({ value: "300" }),
|
||||
freshInputTokens: usageField({ value: "1200000" }),
|
||||
}),
|
||||
),
|
||||
"Cache read 1.5K · Cache write 300 · Fresh 1.2M",
|
||||
);
|
||||
|
||||
// Only cache-read known — the other two are unknown and omitted, not zero.
|
||||
assert.equal(
|
||||
formatModelCacheBreakdown(
|
||||
build({ cacheReadTokens: usageField({ value: "800" }) }),
|
||||
),
|
||||
"Cache read 800",
|
||||
);
|
||||
|
||||
// An incomplete known field appends a single trailing Partial marker.
|
||||
assert.equal(
|
||||
formatModelCacheBreakdown(
|
||||
build({
|
||||
cacheReadTokens: usageField({ value: "800", incomplete: true }),
|
||||
cacheWriteTokens: usageField({ value: "200" }),
|
||||
}),
|
||||
),
|
||||
"Cache read 800 · Cache write 200 · Partial",
|
||||
);
|
||||
|
||||
// An incomplete field with NO value carries no known lower bound: it is
|
||||
// omitted and does not trigger Partial on its own.
|
||||
assert.equal(
|
||||
formatModelCacheBreakdown(
|
||||
build({
|
||||
cacheReadTokens: usageField({ value: "800" }),
|
||||
cacheWriteTokens: usageField({ value: null, incomplete: true }),
|
||||
}),
|
||||
),
|
||||
"Cache read 800",
|
||||
);
|
||||
});
|
||||
|
||||
// ── sumKnownBucketTotals ──────────────────────────────────────────────────────
|
||||
|
||||
function bucket(overrides = {}) {
|
||||
|
||||
@@ -534,6 +534,57 @@ export function isUnknownField(field: UsageField | CostField): boolean {
|
||||
return field.value === null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compact per-model input breakdown for the focused view: the cache-read,
|
||||
* cache-write, and fresh-input subsets of input, each shown only when known
|
||||
* (`value !== null`) and never as zero. Returns `null` when no subset is
|
||||
* known at all, so the caller omits the line entirely rather than printing
|
||||
* three unknowns. A trailing ` · Partial` marks that at least one shown field
|
||||
* is a known-but-incomplete lower bound — matching the row's Partial badge and
|
||||
* {@link deriveUsageIngressTrailing}'s text convention. Absent (`null`) fields
|
||||
* are simply omitted, never marked Partial (they carry no known lower bound).
|
||||
*/
|
||||
export function formatModelCacheBreakdown(
|
||||
model: AgentUsageModel,
|
||||
): string | null {
|
||||
const { cacheReadTokens, cacheWriteTokens, freshInputTokens } = model.usage;
|
||||
const parts: string[] = [];
|
||||
const push = (label: string, field: UsageField) => {
|
||||
const parsed = parseTokenCount(field.value);
|
||||
if (parsed !== null) {
|
||||
parts.push(`${label} ${formatTokenCountCompact(parsed)}`);
|
||||
}
|
||||
};
|
||||
push("Cache read", cacheReadTokens);
|
||||
push("Cache write", cacheWriteTokens);
|
||||
push("Fresh", freshInputTokens);
|
||||
if (parts.length === 0) return null;
|
||||
const partial =
|
||||
isPartialField(cacheReadTokens) ||
|
||||
isPartialField(cacheWriteTokens) ||
|
||||
isPartialField(freshInputTokens);
|
||||
return partial ? `${parts.join(" · ")} · Partial` : parts.join(" · ");
|
||||
}
|
||||
|
||||
/**
|
||||
* True when a usage scope has any known cache-read, cache-write, or
|
||||
* fresh-input value — the gate for showing the focused view's input-breakdown
|
||||
* subsection. When every cache subset is unknown (old-harness data that never
|
||||
* reported cache tokens), the subsection is omitted rather than rendering a
|
||||
* row of "—", which keeps absence honest without visual noise.
|
||||
*/
|
||||
export function hasKnownCacheData(usage: {
|
||||
cacheReadTokens: UsageField;
|
||||
cacheWriteTokens: UsageField;
|
||||
freshInputTokens: UsageField;
|
||||
}): boolean {
|
||||
return (
|
||||
!isUnknownField(usage.cacheReadTokens) ||
|
||||
!isUnknownField(usage.cacheWriteTokens) ||
|
||||
!isUnknownField(usage.freshInputTokens)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Truthful trailing summary for the profile Info-tab Usage ingress row
|
||||
* (plan:328): the viewer's own agent's 7-day known total, `Partial` when
|
||||
|
||||
@@ -18,8 +18,10 @@ import {
|
||||
describeRange,
|
||||
formatCoverageDate,
|
||||
formatEstimatedCostUsd,
|
||||
formatModelCacheBreakdown,
|
||||
formatTokenCountCompact,
|
||||
formatTokenCountExact,
|
||||
hasKnownCacheData,
|
||||
isPartialField,
|
||||
isUnknownField,
|
||||
parseTokenCount,
|
||||
@@ -218,6 +220,8 @@ function AgentUsageFocusedTotals({
|
||||
|
||||
// Display total for the Total tokens stat.
|
||||
const displayTotal = deriveDisplayTotal(agent.usage);
|
||||
const { cacheReadTokens, cacheWriteTokens, freshInputTokens } = agent.usage;
|
||||
const showCacheBreakdown = hasKnownCacheData(agent.usage);
|
||||
|
||||
return (
|
||||
<Card className="space-y-4 p-6" data-testid="agent-usage-focused-totals">
|
||||
@@ -236,6 +240,34 @@ function AgentUsageFocusedTotals({
|
||||
/>
|
||||
</div>
|
||||
|
||||
{showCacheBreakdown ? (
|
||||
<div
|
||||
className="space-y-2 border-t border-border pt-4"
|
||||
data-testid="agent-usage-focused-cache"
|
||||
>
|
||||
<h3 className="text-sm font-medium text-foreground">
|
||||
Input breakdown
|
||||
</h3>
|
||||
<div className="grid grid-cols-2 gap-4 sm:grid-cols-3">
|
||||
<TokenStat
|
||||
field={cacheReadTokens}
|
||||
label="Cache read"
|
||||
testId="agent-usage-focused-cache-read-value"
|
||||
/>
|
||||
<TokenStat
|
||||
field={cacheWriteTokens}
|
||||
label="Cache write"
|
||||
testId="agent-usage-focused-cache-write-value"
|
||||
/>
|
||||
<TokenStat
|
||||
field={freshInputTokens}
|
||||
label="Fresh input"
|
||||
testId="agent-usage-focused-fresh-input-value"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{agent.buckets.length > 0 ? (
|
||||
<div
|
||||
className="space-y-2 border-t border-border pt-4"
|
||||
@@ -253,35 +285,10 @@ function AgentUsageFocusedTotals({
|
||||
>
|
||||
<h3 className="text-sm font-medium text-foreground">By model</h3>
|
||||
{models.map((model) => (
|
||||
<div
|
||||
className="flex items-center justify-between gap-3 text-sm"
|
||||
<FocusedModelRow
|
||||
key={`${model.harness ?? ""}:${model.model ?? "unknown"}`}
|
||||
>
|
||||
<span className="min-w-0 truncate text-muted-foreground">
|
||||
{model.model ?? "Unknown model"}
|
||||
{model.harness !== null ? (
|
||||
<span
|
||||
className="ml-1.5 text-xs text-muted-foreground/70"
|
||||
data-testid="agent-usage-model-harness-label"
|
||||
>
|
||||
{model.harness}
|
||||
</span>
|
||||
) : null}
|
||||
</span>
|
||||
<span className="shrink-0 font-medium text-foreground">
|
||||
{isUnknownField(model.usage.totalTokens)
|
||||
? formatModelIndependentFields(model)
|
||||
: formatTokenCountExact(
|
||||
parseTokenCount(model.usage.totalTokens.value) ?? 0n,
|
||||
)}
|
||||
{isPartialField(model.usage.totalTokens) ||
|
||||
isModelIoPartial(model) ? (
|
||||
<Badge className="ml-2" variant="outline">
|
||||
Partial
|
||||
</Badge>
|
||||
) : null}
|
||||
</span>
|
||||
</div>
|
||||
model={model}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
) : null}
|
||||
@@ -318,9 +325,11 @@ function AgentUsageFocusedTotals({
|
||||
function TokenStat({
|
||||
field,
|
||||
label,
|
||||
testId,
|
||||
}: {
|
||||
field: { value: string | null; incomplete: boolean };
|
||||
label: string;
|
||||
testId?: string;
|
||||
}) {
|
||||
const parsed = parseTokenCount(field.value);
|
||||
return (
|
||||
@@ -328,6 +337,7 @@ function TokenStat({
|
||||
display={parsed !== null ? formatTokenCountExact(parsed) : null}
|
||||
isPartial={isPartialField(field)}
|
||||
label={label}
|
||||
testId={testId}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -400,6 +410,55 @@ function formatCoverageRange(coverage: AgentUsageSeries["coverage"]): string {
|
||||
return `${formatCoverageDate(firstReportedAt)} – ${formatCoverageDate(lastReportedAt)}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* One "By model" row: the model/harness label with its display total and
|
||||
* Partial badge, plus a muted cache/fresh-input breakdown line beneath it
|
||||
* when any cache subset is known. The breakdown is computed once and omitted
|
||||
* entirely when no subset was reported, so old-harness models read no
|
||||
* differently than before.
|
||||
*/
|
||||
function FocusedModelRow({ model }: { model: AgentUsageModel }) {
|
||||
const cacheBreakdown = formatModelCacheBreakdown(model);
|
||||
return (
|
||||
<div className="space-y-0.5">
|
||||
<div className="flex items-center justify-between gap-3 text-sm">
|
||||
<span className="min-w-0 truncate text-muted-foreground">
|
||||
{model.model ?? "Unknown model"}
|
||||
{model.harness !== null ? (
|
||||
<span
|
||||
className="ml-1.5 text-xs text-muted-foreground/70"
|
||||
data-testid="agent-usage-model-harness-label"
|
||||
>
|
||||
{model.harness}
|
||||
</span>
|
||||
) : null}
|
||||
</span>
|
||||
<span className="shrink-0 font-medium text-foreground">
|
||||
{isUnknownField(model.usage.totalTokens)
|
||||
? formatModelIndependentFields(model)
|
||||
: formatTokenCountExact(
|
||||
parseTokenCount(model.usage.totalTokens.value) ?? 0n,
|
||||
)}
|
||||
{isPartialField(model.usage.totalTokens) ||
|
||||
isModelIoPartial(model) ? (
|
||||
<Badge className="ml-2" variant="outline">
|
||||
Partial
|
||||
</Badge>
|
||||
) : null}
|
||||
</span>
|
||||
</div>
|
||||
{cacheBreakdown !== null ? (
|
||||
<p
|
||||
className="text-xs text-muted-foreground/70"
|
||||
data-testid="agent-usage-model-cache-breakdown"
|
||||
>
|
||||
{cacheBreakdown}
|
||||
</p>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render known model I/O fields when the model total is unknown — never
|
||||
* collapses to "No usage reported" when input or output is actually known
|
||||
|
||||
@@ -24,6 +24,9 @@ function reportedUsage(
|
||||
outputTokens: string | null;
|
||||
totalTokens: string | null;
|
||||
estimatedCostUsd: number | null;
|
||||
cacheReadTokens: string | null;
|
||||
cacheWriteTokens: string | null;
|
||||
freshInputTokens: string | null;
|
||||
}> = {},
|
||||
) {
|
||||
return {
|
||||
@@ -31,9 +34,9 @@ function reportedUsage(
|
||||
inputTokens: usageField(overrides.inputTokens ?? null),
|
||||
outputTokens: usageField(overrides.outputTokens ?? null),
|
||||
totalTokens: usageField(overrides.totalTokens ?? null),
|
||||
cacheReadTokens: usageField(null),
|
||||
cacheWriteTokens: usageField(null),
|
||||
freshInputTokens: usageField(null),
|
||||
cacheReadTokens: usageField(overrides.cacheReadTokens ?? null),
|
||||
cacheWriteTokens: usageField(overrides.cacheWriteTokens ?? null),
|
||||
freshInputTokens: usageField(overrides.freshInputTokens ?? null),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -296,6 +299,9 @@ test.describe("agent usage screenshots", () => {
|
||||
inputTokens: "2100",
|
||||
outputTokens: "700",
|
||||
estimatedCostUsd: 0.35,
|
||||
cacheReadTokens: "1400",
|
||||
cacheWriteTokens: "300",
|
||||
freshInputTokens: "400",
|
||||
}),
|
||||
},
|
||||
{
|
||||
@@ -308,6 +314,9 @@ test.describe("agent usage screenshots", () => {
|
||||
inputTokens: "600",
|
||||
outputTokens: "200",
|
||||
estimatedCostUsd: 0.04,
|
||||
cacheReadTokens: "350",
|
||||
cacheWriteTokens: "50",
|
||||
freshInputTokens: "200",
|
||||
}),
|
||||
},
|
||||
],
|
||||
@@ -316,6 +325,9 @@ test.describe("agent usage screenshots", () => {
|
||||
inputTokens: "2700",
|
||||
outputTokens: "900",
|
||||
totalTokens: "3600",
|
||||
cacheReadTokens: "1750",
|
||||
cacheWriteTokens: "350",
|
||||
freshInputTokens: "600",
|
||||
}),
|
||||
}),
|
||||
],
|
||||
|
||||
@@ -55,6 +55,9 @@ function reportedUsage(
|
||||
outputTokens: string | null;
|
||||
totalTokens: string | null;
|
||||
estimatedCostUsd: number | null;
|
||||
cacheReadTokens: string | null;
|
||||
cacheWriteTokens: string | null;
|
||||
freshInputTokens: string | null;
|
||||
}> = {},
|
||||
) {
|
||||
return {
|
||||
@@ -62,9 +65,9 @@ function reportedUsage(
|
||||
inputTokens: usageField(overrides.inputTokens ?? null),
|
||||
outputTokens: usageField(overrides.outputTokens ?? null),
|
||||
totalTokens: usageField(overrides.totalTokens ?? null),
|
||||
cacheReadTokens: usageField(null),
|
||||
cacheWriteTokens: usageField(null),
|
||||
freshInputTokens: usageField(null),
|
||||
cacheReadTokens: usageField(overrides.cacheReadTokens ?? null),
|
||||
cacheWriteTokens: usageField(overrides.cacheWriteTokens ?? null),
|
||||
freshInputTokens: usageField(overrides.freshInputTokens ?? null),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -994,6 +997,159 @@ test("focused view renders the unknown-intervals and invalid-reports caveats und
|
||||
});
|
||||
});
|
||||
|
||||
test("the focused view shows the cache/fresh-input breakdown with exact totals and a per-model line when cache data is present", async ({
|
||||
page,
|
||||
}) => {
|
||||
await installMockBridge(page);
|
||||
await openAgentsView(page);
|
||||
|
||||
const agentPubkey = await addGenericAgent(page, "general", "Cache Bot");
|
||||
await seedSeries(
|
||||
page,
|
||||
mockUsageSeries({
|
||||
agents: [
|
||||
mockAgentUsage(agentPubkey, {
|
||||
models: [
|
||||
{
|
||||
harness: "claude-code",
|
||||
hasUnknownUsage: false,
|
||||
model: "claude-opus",
|
||||
reportCount: 1,
|
||||
usage: reportedUsage({
|
||||
totalTokens: "12000",
|
||||
inputTokens: "10000",
|
||||
outputTokens: "2000",
|
||||
cacheReadTokens: "6000",
|
||||
cacheWriteTokens: "1500",
|
||||
freshInputTokens: "2500",
|
||||
}),
|
||||
},
|
||||
],
|
||||
usage: reportedUsage({
|
||||
inputTokens: "10000",
|
||||
outputTokens: "2000",
|
||||
totalTokens: "12000",
|
||||
cacheReadTokens: "6000",
|
||||
cacheWriteTokens: "1500",
|
||||
freshInputTokens: "2500",
|
||||
}),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
);
|
||||
|
||||
await page.getByTestId(`agent-usage-row-${agentPubkey}`).click();
|
||||
await expect(page.getByTestId("agent-usage-focused-view")).toBeVisible();
|
||||
|
||||
// Input-breakdown subsection: three exact stats, none rendered as zero.
|
||||
const cache = page.getByTestId("agent-usage-focused-cache");
|
||||
await expect(cache).toBeVisible();
|
||||
await expect(cache).toContainText("Cache read");
|
||||
await expect(
|
||||
page.getByTestId("agent-usage-focused-cache-read-value"),
|
||||
).toHaveText("6,000");
|
||||
await expect(
|
||||
page.getByTestId("agent-usage-focused-cache-write-value"),
|
||||
).toHaveText("1,500");
|
||||
await expect(
|
||||
page.getByTestId("agent-usage-focused-fresh-input-value"),
|
||||
).toHaveText("2,500");
|
||||
// No Partial badge when every shown cache field is a complete value.
|
||||
await expect(cache.getByText("Partial", { exact: true })).toHaveCount(0);
|
||||
|
||||
// Per-model breakdown line carries the same subsets, compact-formatted.
|
||||
const modelCache = page.getByTestId("agent-usage-model-cache-breakdown");
|
||||
await expect(modelCache).toContainText("Cache read 6K");
|
||||
await expect(modelCache).toContainText("Cache write 1.5K");
|
||||
await expect(modelCache).toContainText("Fresh 2.5K");
|
||||
});
|
||||
|
||||
test("the focused view marks a known-but-incomplete cache field Partial, omits absent subsets, and hides the breakdown entirely when no cache data exists", async ({
|
||||
page,
|
||||
}) => {
|
||||
await installMockBridge(page);
|
||||
await openAgentsView(page);
|
||||
|
||||
const agentPubkey = await addGenericAgent(
|
||||
page,
|
||||
"general",
|
||||
"Partial Cache Bot",
|
||||
);
|
||||
|
||||
await test.step("mixed cache completeness → Partial on the lower bound, absent fresh-input shows em-dash", async () => {
|
||||
await seedSeries(
|
||||
page,
|
||||
mockUsageSeries({
|
||||
agents: [
|
||||
mockAgentUsage(agentPubkey, {
|
||||
usage: {
|
||||
estimatedCostUsd: costField(null),
|
||||
inputTokens: usageField("10000"),
|
||||
outputTokens: usageField("2000"),
|
||||
totalTokens: usageField("12000"),
|
||||
// Known but incomplete (Will's mixed-window shape) → Partial.
|
||||
cacheReadTokens: usageField("6000", true),
|
||||
// Absent → must render "—", never 0.
|
||||
cacheWriteTokens: usageField(null),
|
||||
// Fail-closed: fresh input cannot be derived → unknown → "—".
|
||||
freshInputTokens: usageField(null, true),
|
||||
},
|
||||
}),
|
||||
],
|
||||
}),
|
||||
);
|
||||
|
||||
await page.getByTestId(`agent-usage-row-${agentPubkey}`).click();
|
||||
const cache = page.getByTestId("agent-usage-focused-cache");
|
||||
await expect(cache).toBeVisible();
|
||||
// Cache read: known lower bound → value plus Partial badge.
|
||||
await expect(
|
||||
page.getByTestId("agent-usage-focused-cache-read-value"),
|
||||
).toHaveText("6,000");
|
||||
await expect(cache.getByText("Partial", { exact: true })).toBeVisible();
|
||||
// Cache write + fresh input are unknown → em-dash, never zero.
|
||||
await expect(
|
||||
page.getByTestId("agent-usage-focused-cache-write-value"),
|
||||
).toHaveText("—");
|
||||
await expect(
|
||||
page.getByTestId("agent-usage-focused-fresh-input-value"),
|
||||
).toHaveText("—");
|
||||
});
|
||||
|
||||
await test.step("no cache data at all → the breakdown subsection is absent", async () => {
|
||||
await seedSeries(
|
||||
page,
|
||||
mockUsageSeries({
|
||||
agents: [
|
||||
mockAgentUsage(agentPubkey, {
|
||||
// Old-harness shape: i/o known, every cache subset absent.
|
||||
usage: reportedUsage({
|
||||
inputTokens: "10000",
|
||||
outputTokens: "2000",
|
||||
totalTokens: "12000",
|
||||
}),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
);
|
||||
await page.evaluate(() =>
|
||||
(
|
||||
window as Window & {
|
||||
__BUZZ_E2E_QUERY_CLIENT__?: {
|
||||
invalidateQueries: () => Promise<void>;
|
||||
};
|
||||
}
|
||||
).__BUZZ_E2E_QUERY_CLIENT__?.invalidateQueries(),
|
||||
);
|
||||
await page.getByTestId(`agent-usage-row-${agentPubkey}`).click();
|
||||
await expect(page.getByTestId("agent-usage-focused-totals")).toBeVisible();
|
||||
await expect(page.getByTestId("agent-usage-focused-cache")).toHaveCount(0);
|
||||
await expect(
|
||||
page.getByTestId("agent-usage-model-cache-breakdown"),
|
||||
).toHaveCount(0);
|
||||
});
|
||||
});
|
||||
|
||||
test("overview and focused view distinguish an invalid-only window from ordinary empty windows", async ({
|
||||
page,
|
||||
}) => {
|
||||
|
||||
Reference in New Issue
Block a user