Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 67 additions & 0 deletions apps/web/src/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -2557,6 +2557,73 @@ tr.insights-row-clickable:hover {
padding: 0.55rem 0.7rem;
}

/* "Running now" strip — real in-flight runs only (see RunningNowStrip),
scrollable so a busy tenant doesn't push the layout wide. */
.insights-running-now {
display: flex;
flex-direction: column;
gap: 0.5rem;
}

.insights-running-now-head {
display: flex;
align-items: baseline;
gap: 0.5rem;
}

.insights-running-now-head h3 {
margin: 0;
font-size: 0.78rem;
font-weight: 700;
letter-spacing: 0.04em;
text-transform: uppercase;
color: var(--muted-foreground);
}

.insights-running-now-count {
font-size: 0.75rem;
color: var(--muted-foreground);
}

.insights-running-now-strip {
display: flex;
gap: 0.5rem;
margin: 0;
padding: 0.1rem 0.05rem 0.35rem;
list-style: none;
overflow-x: auto;
}

.insights-flight {
display: flex;
flex-shrink: 0;
align-items: center;
gap: 0.5rem;
padding: 0.4rem 0.65rem;
border: 1px solid color-mix(in srgb, var(--foreground) 10%, transparent);
background: var(--card, var(--background));
font-size: 0.8rem;
cursor: pointer;
}

.insights-flight:hover {
background-color: color-mix(in srgb, var(--primary) 8%, transparent);
}

.insights-flight-name {
max-width: 14rem;
overflow: hidden;
font-weight: 600;
text-overflow: ellipsis;
white-space: nowrap;
}

.insights-flight-elapsed {
font-size: 0.75rem;
font-variant-numeric: tabular-nums;
color: var(--muted-foreground);
}

.insights-section {
padding-top: 0.25rem;
}
Expand Down
52 changes: 48 additions & 4 deletions apps/web/src/pages/insights-page-render.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,11 @@ import { EMPTY_OVERALL_USAGE } from "@corbits/insights/client";
import { InsightsPage, useInsightsWindow } from "./insights-page";
import { BenchContext } from "../bench-context";
import type { BenchState } from "../bench-context";
import type { InsightsRun } from "../insights-api";
import { NavigationProvider } from "../navigation";

type RunsStub = { data: readonly InsightsRun[]; nextCursor: string | null };

let container: HTMLDivElement | null = null;
let root: Root | null = null;

Expand All @@ -42,7 +45,13 @@ const benchState: BenchState = {
onBenchCreated: () => {},
};

function InsightsPageAtPath({ path }: { readonly path: string }) {
function InsightsPageAtPath({
path,
runs = { data: [], nextCursor: null },
}: {
readonly path: string;
readonly runs?: RunsStub;
}) {
const range = useInsightsWindow();
return (
<NavigationProvider navigate={() => {}}>
Expand All @@ -52,7 +61,7 @@ function InsightsPageAtPath({ path }: { readonly path: string }) {
summary={readyEmpty(EMPTY_OVERALL_USAGE)}
activity={readyEmpty([])}
byTool={readyEmpty([])}
runs={readyEmpty({ data: [], nextCursor: null })}
runs={readyEmpty(runs)}
routines={readyEmpty([])}
workbenches={readyEmpty({ items: [] })}
latency={{ kind: "loading" }}
Expand All @@ -66,12 +75,17 @@ function InsightsPageAtPath({ path }: { readonly path: string }) {
);
}

function render(path: string) {
function render(path: string, runs?: RunsStub) {
container = document.createElement("div");
document.body.appendChild(container);
root = createRoot(container);
act(() => {
root?.render(<InsightsPageAtPath path={path} />);
root?.render(
<InsightsPageAtPath
path={path}
{...(runs === undefined ? {} : { runs })}
/>,
);
});
return container;
}
Expand All @@ -91,3 +105,33 @@ describe("InsightsPage with a malformed URL escape", () => {
expect(el.textContent).toContain("All workbenches");
});
});

describe("InsightsPage 'Running now' strip", () => {
test("no in-flight runs: the strip renders nothing, not an empty-state fixture", () => {
const el = render("/insights", { data: [], nextCursor: null });
expect(el.textContent).not.toContain("Running now");
});

test("a genuinely running run surfaces in the strip by name", () => {
const el = render("/insights", {
data: [
{
id: "run_1",
tenantId: "tnt_bench_a",
definitionId: "wfd_a",
definitionName: "Weekly digest",
address: "addr",
status: "running",
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
routineId: null,
routineName: null,
},
],
nextCursor: null,
});
expect(el.textContent).toContain("Running now");
expect(el.textContent).toContain("1 in progress");
expect(el.textContent).toContain("Weekly digest");
});
});
97 changes: 95 additions & 2 deletions apps/web/src/pages/insights-page.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,15 @@
// own `/insights/workbench/:workbenchId` route (see `InsightsWorkbenchPage`),
// so this landing scope never takes a workbench override anymore.
import { describe, expect, test } from "bun:test";
import type { DayActivity } from "@corbits/insights/client";

import { resolveInsightsScope } from "./insights-page";
import type { InsightsScope } from "../insights-api";
import {
costPerDay,
elapsedLabel,
resolveInsightsScope,
runsPerDay,
} from "./insights-page";
import type { InsightsRun, InsightsScope } from "../insights-api";

const workspaceMemberScope: InsightsScope = {
tenantId: "tnt_bench_a",
Expand Down Expand Up @@ -85,3 +91,90 @@ describe("resolveInsightsScope", () => {
expect(result.effectiveTenantId).toBeNull();
});
});

function day(
partial: Partial<DayActivity> & Pick<DayActivity, "day">,
): DayActivity {
return { turns: 0, tokens: 0, byModel: [], ...partial };
}

function run(
partial: Partial<InsightsRun> & Pick<InsightsRun, "id" | "createdAt">,
): InsightsRun {
return {
tenantId: "t1",
definitionId: "wfd_a",
definitionName: "Research brief",
address: "addr",
status: "running",
updatedAt: partial.createdAt,
routineId: null,
routineName: null,
...partial,
};
}

describe("runsPerDay", () => {
test("buckets each run's date onto the matching day, zero elsewhere", () => {
const days = [
day({ day: "2026-01-01" }),
day({ day: "2026-01-02" }),
day({ day: "2026-01-03" }),
];
const runs = [
run({ id: "a", createdAt: "2026-01-01T09:00:00.000Z" }),
run({ id: "b", createdAt: "2026-01-01T18:00:00.000Z" }),
run({ id: "c", createdAt: "2026-01-03T00:00:01.000Z" }),
];
expect(runsPerDay(runs, days)).toEqual([2, 0, 1]);
});

test("a run outside the window contributes to no bucket", () => {
const days = [day({ day: "2026-01-01" })];
const runs = [run({ id: "a", createdAt: "2025-12-25T00:00:00.000Z" })];
expect(runsPerDay(runs, days)).toEqual([0]);
});

test("no days: returns an empty series, not a fabricated one", () => {
expect(
runsPerDay([run({ id: "a", createdAt: "2026-01-01T00:00:00.000Z" })], []),
).toEqual([]);
});
});

describe("costPerDay", () => {
test("sums known per-model costs for each day", () => {
const days = [
day({
day: "2026-01-01",
byModel: [
{ model: "opus-5", tokens: 100, costUsd: 1.5 },
{ model: "sonnet-5", tokens: 50, costUsd: 0.25 },
],
}),
day({ day: "2026-01-02", byModel: [] }),
];
expect(costPerDay(days)).toEqual([1.75, 0]);
});

test("a null model rate contributes 0, not NaN — caller decides whether to show it", () => {
const days = [
day({
day: "2026-01-01",
byModel: [{ model: "new-model", tokens: 10, costUsd: null }],
}),
];
expect(costPerDay(days)).toEqual([0]);
});
});

describe("elapsedLabel", () => {
test("formats wall-clock time since createdAt", () => {
const now = Date.parse("2026-01-01T00:02:12.000Z");
expect(elapsedLabel("2026-01-01T00:00:00.000Z", now)).toBe("2.2m");
});

test("an invalid timestamp reads as unknown, not a fabricated duration", () => {
expect(elapsedLabel("not-a-date", Date.now())).toBe("—");
});
});
Loading
Loading