From c3b9c9d88859c8b01d45c8546fa74f6daaefffc2 Mon Sep 17 00:00:00 2001 From: Adam <2363879+adamdotdevin@users.noreply.github.com> Date: Mon, 6 Jul 2026 13:23:53 -0500 Subject: [PATCH 01/33] feat(data): improved model usage section --- packages/stats/app/src/i18n.ts | 2 +- .../stats/app/src/routes/[lab]/[model].tsx | 296 +++++++++++++++++- packages/stats/app/src/routes/index.css | 259 ++++++++++++++- 3 files changed, 554 insertions(+), 3 deletions(-) diff --git a/packages/stats/app/src/i18n.ts b/packages/stats/app/src/i18n.ts index 1cffda4430..2edb164cb4 100644 --- a/packages/stats/app/src/i18n.ts +++ b/packages/stats/app/src/i18n.ts @@ -210,7 +210,7 @@ const en = { "model.totalModels": "{{count}} models", "model.momentum": "Momentum", "model.vsPreviousWindow": "vs previous window", - "model.usageDescription": "Daily OpenCode Go token volume over the recent two-month window.", + "model.usageDescription": "Daily token volume.", "model.noUsageTitle": "No usage", "model.noUsageDescription": "No usage landed in the current window.", "model.dailyTokenChart": "Daily token usage chart", diff --git a/packages/stats/app/src/routes/[lab]/[model].tsx b/packages/stats/app/src/routes/[lab]/[model].tsx index 1b3c3cd786..f783a60cb7 100644 --- a/packages/stats/app/src/routes/[lab]/[model].tsx +++ b/packages/stats/app/src/routes/[lab]/[model].tsx @@ -15,7 +15,7 @@ import { type UsageRange, } from "@opencode-ai/stats-core/domain/home" import { createAsync, query, useParams } from "@solidjs/router" -import { createMemo, createSignal, For, onMount, Show, type JSX } from "solid-js" +import { createMemo, createSignal, createUniqueId, For, onMount, Show, type JSX } from "solid-js" import { getRequestEvent } from "solid-js/web" import type { FeatureCollection, GeometryObject, GeoJsonProperties } from "geojson" import type { GeometryCollection, Topology } from "topojson-specification" @@ -121,6 +121,7 @@ export default function StatsModel() { const modelHeaderLinks = createMemo(() => [ { href: "#overview", label: i18n.t("nav.overview") }, { href: "#momentum", label: i18n.t("model.momentum") }, + { href: "#usage", label: i18n.t("nav.usage") }, { href: "#efficiency", label: i18n.t("nav.efficiency") }, { href: "#geo-breakdown", label: i18n.t("nav.geoBreakdown") }, { href: "#peers", label: i18n.t("nav.peers") }, @@ -183,6 +184,7 @@ export default function StatsModel() { /> + @@ -561,6 +563,211 @@ function MomentumMetric(props: { label: string; value: string; watermark?: strin ) } +function ModelUsageSection(props: { data: StatsModelData | null }) { + const i18n = useI18n() + const activeLineClipId = createUniqueId() + const activeLineMaskId = createUniqueId() + const [activeIndex, setActiveIndex] = createSignal() + const usage = createMemo(() => props.data?.usage ?? []) + const tokenMax = createMemo(() => Math.max(0, ...usage().map((item) => item.tokens)) || 1) + const linePoints = createMemo(() => + usage().map((point, index) => ({ + point, + x: modelUsagePointX(index, usage().length), + y: modelUsageLineY(point.tokens, tokenMax()), + })), + ) + const tokenLinePath = createMemo(() => modelUsageLinePath(linePoints())) + const activeLineBreak = createMemo(() => { + const index = activeIndex() + if (index === undefined || linePoints().length < 2) return undefined + return modelUsageColumnBounds(index, linePoints().length) + }) + const activeLineClip = createMemo(() => { + const index = activeIndex() + if (index === undefined || linePoints().length < 2) return undefined + return modelUsageColumnInnerBounds(index, linePoints().length) + }) + const activeTooltip = createMemo(() => { + const index = activeIndex() + const point = index === undefined ? undefined : usage()[index] + if (index === undefined || !point) return undefined + const bounds = modelUsageColumnBounds(index, usage().length) + return { + bounds, + index, + point, + tokenY: linePoints()[index]?.y ?? 100, + tooltipY: ((linePoints()[index]?.y ?? 100) * 370) / 450, + } + }) + const monthTicks = createMemo(() => modelUsageMonthTicks(usage(), props.data?.updatedAt ?? null)) + + return ( +
+ + item.tokens > 0)} + fallback={ + + } + > +
{ + if (event.pointerType === "touch") return + setActiveIndex(undefined) + }} + > +
+ + {(path) => ( + <> + + + + {(clip) => ( + + )} + + + )} + + + {(point) => ( + +
+ + {(point, index) => ( +
{ + if (event.pointerType !== "touch") return + setActiveIndex(index()) + }} + onPointerEnter={() => setActiveIndex(index())} + onPointerMove={(event) => { + if (event.pointerType === "touch") return + setActiveIndex(index()) + }} + onClick={() => setActiveIndex(index())} + onFocus={() => setActiveIndex(index())} + onBlur={() => setActiveIndex(undefined)} + onKeyDown={(event) => { + if (event.key !== "Enter" && event.key !== " ") return + event.preventDefault() + setActiveIndex(index()) + }} + > +
+
+ )} + +
+ + {(active) => ( +
usage().length * 0.62 ? "left" : "right"} + style={ + { + "--model-trend-tooltip-left": `${active.bounds.x}%`, + "--model-trend-tooltip-right": `${active.bounds.x + active.bounds.width}%`, + "--model-trend-token-y": `${active.tokenY}`, + "--model-trend-tooltip-y": `${active.tooltipY}`, + } as JSX.CSSProperties + } + > + {formatModelUsageTooltipDate(active.point.date, props.data?.updatedAt ?? null)} + + {formatTokens(active.point.tokens)} {i18n.t("lab.tokens")} + +
+

+ + {i18n.t("lab.dailyTokens")} + + {formatTokens(active.point.tokens)} +

+
+ )} + +
+ +
+ +
+ ) +} + function ModelEfficiencySection(props: { data: StatsModelData | null; catalog: ModelCatalogEntry | null }) { const i18n = useI18n() return ( @@ -1014,6 +1221,93 @@ function formatMomentumDateLabel(date: string) { return `${shortMonths[parsed.getUTCMonth()]} ${parsed.getUTCDate()}` } +function modelUsageStripHeight(value: number, max: number) { + if (value <= 0 || max <= 0) return 0 + return Math.max(2, (value / max) * 40) +} + +function modelUsageLineY(value: number, max: number) { + if (value <= 0 || max <= 0) return 100 + return Math.max(0, 100 - (value / max) * 100) +} + +function modelUsagePointX(index: number, count: number) { + if (count <= 1) return 50 + return ((index + 0.5) / count) * 100 +} + +function modelUsageColumnBounds(index: number, count: number) { + if (count <= 0) return { x: 0, width: 100 } + return { x: (index / count) * 100, width: 100 / count } +} + +function modelUsageColumnInnerBounds(index: number, count: number) { + const bounds = modelUsageColumnBounds(index, count) + const inset = Math.min(bounds.width * 0.1, 0.24) + return { x: bounds.x + inset, width: Math.max(0.001, bounds.width - inset * 2) } +} + +type ModelUsageLinePoint = { x: number; y: number } + +function modelUsageLinePath(points: ModelUsageLinePoint[]) { + if (points.length === 0) return "" + if (points.length === 1) return `M ${formatUsagePathNumber(points[0].x)} ${formatUsagePathNumber(points[0].y)}` + + return points.slice(0, -1).reduce( + (path, point, index) => { + const next = points[index + 1] + const previous = points[index - 1] ?? point + const afterNext = points[index + 2] ?? next + const controlA = { + x: point.x + (next.x - previous.x) / 6, + y: clampUsagePercent(point.y + (next.y - previous.y) / 6), + } + const controlB = { + x: next.x - (afterNext.x - point.x) / 6, + y: clampUsagePercent(next.y - (afterNext.y - point.y) / 6), + } + return `${path} C ${formatUsagePathNumber(controlA.x)} ${formatUsagePathNumber(controlA.y)}, ${formatUsagePathNumber(controlB.x)} ${formatUsagePathNumber(controlB.y)}, ${formatUsagePathNumber(next.x)} ${formatUsagePathNumber(next.y)}` + }, + `M ${formatUsagePathNumber(points[0].x)} ${formatUsagePathNumber(points[0].y)}`, + ) +} + +function formatUsagePathNumber(value: number) { + return Number(value.toFixed(3)) +} + +function clampUsagePercent(value: number) { + return Math.max(0, Math.min(100, value)) +} + +function modelUsageMonthTicks(points: ModelUsagePoint[], updatedAt: string | null) { + const seen = new Set() + return points.flatMap((point, index) => { + const parsed = parseMomentumDate(point.date, updatedAt) + const label = shortMonths[parsed.getUTCMonth()] + if (!label || seen.has(label)) return [] + seen.add(label) + return [ + { + label, + left: modelUsagePointX(index, points.length), + align: index === 0 ? "start" : index >= points.length - 2 ? "end" : "center", + }, + ] + }) +} + +function formatModelUsageTooltipDate(value: string, updatedAt: string | null) { + const date = parseMomentumDate(value, updatedAt) + if (date.getUTCFullYear() === 1970) return value + return new Intl.DateTimeFormat("en", { + month: "short", + day: "numeric", + year: "numeric", + timeZone: "UTC", + }).format(date) +} + function formatRankLabel(rank: number | null) { if (rank === null) return "--" return `#${String(rank).padStart(2, "0")}` diff --git a/packages/stats/app/src/routes/index.css b/packages/stats/app/src/routes/index.css index 739b255343..9e139b31a3 100644 --- a/packages/stats/app/src/routes/index.css +++ b/packages/stats/app/src/routes/index.css @@ -4871,6 +4871,200 @@ background: var(--lab-usage-line); } +[data-page="stats"] [data-component="model-usage-chart"][data-variant="model-trend"] { + --model-trend-gap: 4px; + --model-trend-column-gutter: calc(var(--model-trend-gap) / 2); + --model-trend-line-bottom: 80px; + --model-trend-bar: #dbdbdb; + --model-trend-line: #808080; + --model-trend-active: #3b5cf6; + --model-trend-band: #f2f2f2; + grid-template-rows: minmax(0, 450px) 14px; + gap: 40px; + height: 504px; +} + +[data-page="stats"] [data-slot="model-trend-plot"] { + position: relative; + min-width: 0; + min-height: 0; + overflow: visible; +} + +[data-page="stats"] [data-slot="model-trend-plot"]::after { + position: absolute; + inset: 0; + z-index: 0; + background: linear-gradient(to bottom, transparent 42%, color-mix(in srgb, var(--stats-text) 3%, transparent)); + content: ""; + pointer-events: none; +} + +[data-page="stats"] [data-slot="model-trend-line"] { + position: absolute; + top: 0; + right: 0; + bottom: auto; + left: 0; + display: block; + width: 100%; + height: calc(100% - var(--model-trend-line-bottom)); + overflow: visible; + pointer-events: none; +} + +[data-page="stats"] [data-slot="model-trend-line"][data-layer="base"] { + z-index: 2; +} + +[data-page="stats"] [data-slot="model-trend-line"][data-layer="active"] { + z-index: 4; +} + +[data-page="stats"] [data-slot="model-trend-line"] path { + fill: none; + stroke-linecap: round; + stroke-linejoin: round; + stroke-width: 1.5; + vector-effect: non-scaling-stroke; +} + +[data-page="stats"] [data-slot="model-trend-line-base"] { + stroke: var(--model-trend-line); +} + +[data-page="stats"] [data-slot="model-trend-line-active"] { + stroke: var(--model-trend-active); +} + +[data-page="stats"] [data-slot="model-trend-end-marker"] { + position: absolute; + top: var(--model-trend-end-top); + left: var(--model-trend-end-x); + z-index: 3; + width: 6px; + height: 6px; + background: var(--model-trend-bar); + transform: translate(-50%, -50%); + pointer-events: none; +} + +[data-page="stats"] [data-slot="model-trend-bars"] { + position: absolute; + inset: 0; + z-index: 3; + display: flex; + gap: 0; + min-width: 0; +} + +[data-page="stats"] [data-slot="model-trend-column"] { + position: relative; + box-sizing: border-box; + flex: 1 1 0; + min-width: 0; + height: 100%; + outline: none; + cursor: pointer; +} + +[data-page="stats"] [data-slot="model-trend-column"]::before { + position: absolute; + inset: 0; + z-index: 1; + background: transparent; + content: ""; + pointer-events: none; + transition: background 120ms ease; +} + +[data-page="stats"] [data-slot="model-trend-column"][data-active="true"]::before, +[data-page="stats"] [data-slot="model-trend-column"]:focus-visible::before { + background: var(--model-trend-band); + box-shadow: + inset var(--model-trend-column-gutter) 0 0 var(--stats-bg), + inset calc(var(--model-trend-column-gutter) * -1) 0 0 var(--stats-bg); +} + +[data-page="stats"] [data-slot="model-trend-token-bar"] { + position: absolute; + right: var(--model-trend-column-gutter); + bottom: 0; + left: var(--model-trend-column-gutter); + z-index: 2; + height: var(--model-trend-token-height); + background: var(--model-trend-bar); + transition: + background 120ms ease, + opacity 120ms ease; +} + +[data-page="stats"] [data-slot="model-trend-column"][data-active="true"] [data-slot="model-trend-token-bar"] { + background: var(--model-trend-active); +} + +[data-page="stats"] [data-slot="model-trend-column"][data-muted="true"] [data-slot="model-trend-token-bar"] { + opacity: 0.64; +} + +[data-page="stats"] [data-slot="model-trend-months"] { + position: relative; + height: 14px; + color: var(--stats-muted); + font-size: 11px; + font-weight: 500; + line-height: 14px; +} + +[data-page="stats"] [data-slot="model-trend-months"] span { + position: absolute; + top: 0; + left: var(--model-trend-month-left); + transform: translateX(-50%); +} + +[data-page="stats"] [data-slot="model-trend-months"] span[data-align="start"] { + transform: none; +} + +[data-page="stats"] [data-slot="model-trend-months"] span[data-align="end"] { + transform: translateX(-100%); +} + +[data-page="stats"] [data-component="model-usage-chart"][data-variant="model-trend"] [data-component="chart-tooltip"] { + z-index: 6; + top: clamp(12px, calc(var(--model-trend-tooltip-y) * 1% - 46px), calc(100% - 108px)); + width: 192px; + min-width: 192px; + background: var(--stats-bg); +} + +[data-page="stats"] + [data-component="model-usage-chart"][data-variant="model-trend"] + [data-component="chart-tooltip"][data-placement="right"] { + right: auto; + left: calc(var(--model-trend-tooltip-right) + 8px); +} + +[data-page="stats"] + [data-component="model-usage-chart"][data-variant="model-trend"] + [data-component="chart-tooltip"][data-placement="left"] { + right: calc(100% - var(--model-trend-tooltip-left) + 8px); + left: auto; +} + +[data-page="stats"] [data-component="model-usage-chart"][data-variant="model-trend"] [data-component="chart-tooltip"] p { + height: 16px; + margin: 8px 0; +} + +[data-page="stats"] + [data-component="model-usage-chart"][data-variant="model-trend"] + [data-component="chart-tooltip"] + i[data-kind="tokens"] { + background: var(--model-trend-active); +} + [data-page="stats"] [data-component="model-peer-list"] { display: grid; gap: 8px; @@ -4966,6 +5160,15 @@ --lab-usage-band: #242424; } +[data-page="stats"][data-theme="dark"] [data-component="model-usage-chart"][data-variant="model-trend"], +:root[data-stats-theme="dark"] + [data-page="stats"]:not([data-theme="light"]) + [data-component="model-usage-chart"][data-variant="model-trend"] { + --model-trend-bar: #3a3a3a; + --model-trend-line: #a3a3a3; + --model-trend-band: #242424; +} + [data-page="stats"][data-theme="dark"] [data-component="chart-tooltip"], :root[data-stats-theme="dark"] [data-page="stats"]:not([data-theme="light"]) [data-component="chart-tooltip"] { background: #242424f2; @@ -5131,6 +5334,14 @@ --lab-usage-band: #242424; } + :root:not([data-stats-theme="light"]) + [data-page="stats"]:not([data-theme="light"]) + [data-component="model-usage-chart"][data-variant="model-trend"] { + --model-trend-bar: #3a3a3a; + --model-trend-line: #a3a3a3; + --model-trend-band: #242424; + } + :root:not([data-stats-theme="light"]) [data-page="stats"]:not([data-theme="light"]) [data-component="chart-tooltip"] { background: #242424f2; } @@ -5229,15 +5440,27 @@ [data-page="stats"][data-theme="dark"] [data-component="model-usage-chart"][data-variant="lab-usage"] [data-component="chart-tooltip"], +[data-page="stats"][data-theme="dark"] + [data-component="model-usage-chart"][data-variant="model-trend"] + [data-component="chart-tooltip"], :root[data-stats-theme="dark"] [data-page="stats"]:not([data-theme="light"]) [data-component="model-usage-chart"][data-variant="lab-usage"] [data-component="chart-tooltip"], +:root[data-stats-theme="dark"] + [data-page="stats"]:not([data-theme="light"]) + [data-component="model-usage-chart"][data-variant="model-trend"] + [data-component="chart-tooltip"], :root:not([data-stats-theme="light"]) [data-page="stats"]:not([data-theme="light"]) [data-component="model-usage-chart"][data-variant="lab-usage"] [data-component="chart-tooltip"], -[data-page="stats"] [data-component="model-usage-chart"][data-variant="lab-usage"] [data-component="chart-tooltip"] { +:root:not([data-stats-theme="light"]) + [data-page="stats"]:not([data-theme="light"]) + [data-component="model-usage-chart"][data-variant="model-trend"] + [data-component="chart-tooltip"], +[data-page="stats"] [data-component="model-usage-chart"][data-variant="lab-usage"] [data-component="chart-tooltip"], +[data-page="stats"] [data-component="model-usage-chart"][data-variant="model-trend"] [data-component="chart-tooltip"] { background: var(--stats-bg); } @@ -5514,6 +5737,11 @@ margin-top: 16px; } + [data-page="stats"] [data-component="model-usage-chart"][data-variant="model-trend"] [data-component="chart-tooltip"] { + position: absolute; + margin-top: 0; + } + [data-page="stats"] [data-component="footer"] { padding: 88px 24px 24px; } @@ -5935,6 +6163,35 @@ min-width: 0; } + [data-page="stats"] [data-component="model-usage-chart"][data-variant="model-trend"] { + --model-usage-mobile-bar-width: 20px; + --model-usage-mobile-edge-space: 48px; + --model-usage-mobile-track-width: calc( + var(--model-usage-count) * var(--model-usage-mobile-bar-width) + var(--model-usage-mobile-edge-space) + ); + --model-trend-line-bottom: 104px; + grid-template-rows: minmax(0, 360px) 14px; + gap: 24px; + height: 398px; + } + + [data-page="stats"] [data-component="model-usage-chart"][data-variant="model-trend"] [data-slot="model-trend-plot"], + [data-page="stats"] [data-component="model-usage-chart"][data-variant="model-trend"] [data-slot="model-trend-months"] { + direction: ltr; + width: var(--model-usage-mobile-track-width); + min-width: var(--model-usage-mobile-track-width); + } + + [data-page="stats"] [data-component="model-usage-chart"][data-variant="model-trend"] [data-slot="model-trend-column"] { + flex: 0 0 var(--model-usage-mobile-bar-width); + } + + [data-page="stats"] [data-component="model-usage-chart"][data-variant="model-trend"] [data-component="chart-tooltip"] { + top: auto; + width: auto; + min-width: 0; + } + [data-page="stats"] [data-component="model-peer-list"] a { grid-template-columns: 30px minmax(0, 1fr) minmax(64px, auto); } From fc09646973b68d6646a3ca3b57a17faddfd50f34 Mon Sep 17 00:00:00 2001 From: Adam <2363879+adamdotdevin@users.noreply.github.com> Date: Mon, 6 Jul 2026 15:02:20 -0500 Subject: [PATCH 02/33] fix(data): refine model trend charts --- packages/stats/app/src/i18n.ts | 2 +- .../stats/app/src/routes/[lab]/[model].tsx | 154 +++++++++++++++--- packages/stats/app/src/routes/index.css | 54 +++++- 3 files changed, 179 insertions(+), 31 deletions(-) diff --git a/packages/stats/app/src/i18n.ts b/packages/stats/app/src/i18n.ts index 2edb164cb4..5f7597f122 100644 --- a/packages/stats/app/src/i18n.ts +++ b/packages/stats/app/src/i18n.ts @@ -214,7 +214,7 @@ const en = { "model.noUsageTitle": "No usage", "model.noUsageDescription": "No usage landed in the current window.", "model.dailyTokenChart": "Daily token usage chart", - "model.usersDescription": "Daily unique OpenCode Go users over the recent two-month window.", + "model.usersDescription": "Daily unique users.", "model.noUsersTitle": "No user data", "model.noUsersDescription": "No user-bearing rows landed in the current window.", "model.dailyUserChart": "Daily unique user chart", diff --git a/packages/stats/app/src/routes/[lab]/[model].tsx b/packages/stats/app/src/routes/[lab]/[model].tsx index f783a60cb7..e7f32681b7 100644 --- a/packages/stats/app/src/routes/[lab]/[model].tsx +++ b/packages/stats/app/src/routes/[lab]/[model].tsx @@ -122,6 +122,7 @@ export default function StatsModel() { { href: "#overview", label: i18n.t("nav.overview") }, { href: "#momentum", label: i18n.t("model.momentum") }, { href: "#usage", label: i18n.t("nav.usage") }, + { href: "#unique-users", label: i18n.t("model.uniqueUsers") }, { href: "#efficiency", label: i18n.t("nav.efficiency") }, { href: "#geo-breakdown", label: i18n.t("nav.geoBreakdown") }, { href: "#peers", label: i18n.t("nav.peers") }, @@ -185,6 +186,7 @@ export default function StatsModel() { + @@ -565,19 +567,82 @@ function MomentumMetric(props: { label: string; value: string; watermark?: strin function ModelUsageSection(props: { data: StatsModelData | null }) { const i18n = useI18n() + return ( + point.tokens} + formatValue={formatTokens} + valueUnit={i18n.t("lab.tokens")} + rowLabel={i18n.t("lab.dailyTokens")} + lineTone="muted" + /> + ) +} + +function ModelUniqueUsersSection(props: { data: StatsModelData | null }) { + const i18n = useI18n() + return ( + point.users} + formatValue={formatUsers} + valueUnit={i18n.t("format.users")} + rowLabel={i18n.t("model.uniqueUsers")} + lineTone="active" + activeLineBaseTone="muted" + highlightBars={false} + area + /> + ) +} + +function ModelTrendSection(props: { + data: StatsModelData | null + id: string + title: string + description: string + ariaLabel: string + emptyTitle: string + emptyDescription: string + value: (point: ModelUsagePoint) => number + formatValue: (value: number) => string + valueUnit: string + rowLabel: string + lineTone: "muted" | "active" + activeLineBaseTone?: "muted" | "active" + highlightBars?: boolean + area?: boolean +}) { const activeLineClipId = createUniqueId() const activeLineMaskId = createUniqueId() + const areaGradientId = createUniqueId() const [activeIndex, setActiveIndex] = createSignal() + const highlightBars = createMemo(() => props.highlightBars ?? true) + const lineScale = 326 / 450 + const areaBottom = 100 / lineScale const usage = createMemo(() => props.data?.usage ?? []) - const tokenMax = createMemo(() => Math.max(0, ...usage().map((item) => item.tokens)) || 1) + const valueMax = createMemo(() => Math.max(0, ...usage().map((item) => props.value(item))) || 1) const linePoints = createMemo(() => usage().map((point, index) => ({ point, x: modelUsagePointX(index, usage().length), - y: modelUsageLineY(point.tokens, tokenMax()), + y: modelUsageLineY(props.value(point), valueMax()), })), ) - const tokenLinePath = createMemo(() => modelUsageLinePath(linePoints())) + const linePath = createMemo(() => modelUsageLinePath(linePoints())) + const areaPath = createMemo(() => modelUsageAreaPath(linePoints(), areaBottom)) const activeLineBreak = createMemo(() => { const index = activeIndex() if (index === undefined || linePoints().length < 2) return undefined @@ -597,26 +662,24 @@ function ModelUsageSection(props: { data: StatsModelData | null }) { bounds, index, point, - tokenY: linePoints()[index]?.y ?? 100, - tooltipY: ((linePoints()[index]?.y ?? 100) * 370) / 450, + tooltipY: (linePoints()[index]?.y ?? 100) * lineScale, } }) const monthTicks = createMemo(() => modelUsageMonthTicks(usage(), props.data?.updatedAt ?? null)) return ( -
- +
+ item.tokens > 0)} - fallback={ - - } + when={usage().some((item) => props.value(item) > 0)} + fallback={} >
{ if (event.pointerType === "touch") return @@ -624,9 +687,28 @@ function ModelUsageSection(props: { data: StatsModelData | null }) { }} >
- + {(path) => ( <> + + {(area) => ( + + )} + @@ -673,11 +763,12 @@ function ModelUsageSection(props: { data: StatsModelData | null }) { {(point) => (
- {formatKeybind(item.keybind ?? "", language.t)} + {formatKeybind(item.keybind ?? "", palette.language.t)}
@@ -479,7 +179,7 @@ export function DialogSelectFile(props: { mode?: DialogSelectFileMode; onOpenFil - {getRelativeTime(new Date(item.updated!).toISOString(), language.t)} + {getRelativeTime(new Date(item.updated!).toISOString(), palette.language.t)} From 254a481e5d06914a1ec590cbfaa07e65ebf9d20c Mon Sep 17 00:00:00 2001 From: James Long Date: Mon, 6 Jul 2026 22:11:13 -0400 Subject: [PATCH 05/33] fix(config): handle unavailable config directories (#35632) Co-authored-by: James Long --- packages/core/src/fs-util.ts | 7 +++--- packages/opencode/src/config/config.ts | 1 + packages/opencode/test/config/config.test.ts | 25 ++++++++++++++++++++ 3 files changed, 30 insertions(+), 3 deletions(-) diff --git a/packages/core/src/fs-util.ts b/packages/core/src/fs-util.ts index 124ac6d702..eb68627a55 100644 --- a/packages/core/src/fs-util.ts +++ b/packages/core/src/fs-util.ts @@ -60,9 +60,10 @@ export namespace FSUtil { }) const readFileStringSafe = Effect.fn("FileSystem.readFileStringSafe")(function* (path: string) { - return yield* fs - .readFileString(path) - .pipe(Effect.catchReason("PlatformError", "NotFound", () => Effect.succeed(undefined))) + return yield* fs.readFileString(path).pipe( + Effect.catchReason("PlatformError", "NotFound", () => Effect.succeed(undefined)), + Effect.catchReason("PlatformError", "PermissionDenied", () => Effect.succeed(undefined)), + ) }) const isDir = Effect.fn("FileSystem.isDir")(function* (path: string) { diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts index 35bd2e0cce..86238f1a84 100644 --- a/packages/opencode/src/config/config.ts +++ b/packages/opencode/src/config/config.ts @@ -293,6 +293,7 @@ const layer = Layer.effect( }) const ensureGitignore = Effect.fn("Config.ensureGitignore")(function* (dir: string) { + yield* fs.ensureDir(dir) const gitignore = path.join(dir, ".gitignore") const hasIgnore = yield* fs.existsSafe(gitignore) if (!hasIgnore) { diff --git a/packages/opencode/test/config/config.test.ts b/packages/opencode/test/config/config.test.ts index 4b68725852..e281831ddf 100644 --- a/packages/opencode/test/config/config.test.ts +++ b/packages/opencode/test/config/config.test.ts @@ -923,6 +923,31 @@ it.effect("does not try to install dependencies in read-only OPENCODE_CONFIG_DIR }).pipe(Effect.provide(testInstanceStoreLayer), Effect.provide(LayerNode.compile(CrossSpawnSpawner.node))), ) +it.effect("ignores an inaccessible OPENCODE_CONFIG_DIR", () => + Effect.gen(function* () { + if (process.platform === "win32") return + + const dir = yield* tmpdirScoped() + const configDir = path.join(dir, "inaccessible") + yield* FSUtil.use.ensureDir(configDir) + yield* FSUtil.use.chmod(configDir, 0o000) + yield* Effect.addFinalizer(() => FSUtil.use.chmod(configDir, 0o755).pipe(Effect.ignore)) + + yield* withProcessEnv("OPENCODE_CONFIG_DIR", configDir, Config.use.get().pipe(provideInstanceEffect(dir))) + }).pipe(Effect.provide(testInstanceStoreLayer), Effect.provide(LayerNode.compile(CrossSpawnSpawner.node))), +) + +it.effect("creates a missing OPENCODE_CONFIG_DIR", () => + Effect.gen(function* () { + const dir = yield* tmpdirScoped() + const configDir = path.join(dir, "configdir") + + yield* withProcessEnv("OPENCODE_CONFIG_DIR", configDir, Config.use.get().pipe(provideInstanceEffect(dir))) + + expect(yield* FSUtil.use.readFileString(path.join(configDir, ".gitignore"))).toContain("node_modules") + }).pipe(Effect.provide(testInstanceStoreLayer), Effect.provide(LayerNode.compile(CrossSpawnSpawner.node))), +) + it.effect("installs dependencies in writable OPENCODE_CONFIG_DIR", () => Effect.gen(function* () { const dir = yield* tmpdirScoped() From 6aff2ee169e973d7423890d4969c638287233f29 Mon Sep 17 00:00:00 2001 From: Aarav Sareen <96787824+arvsrn@users.noreply.github.com> Date: Tue, 7 Jul 2026 08:06:27 +0530 Subject: [PATCH 06/33] feat(app): visual improvements (#35134) --- packages/app/src/index.css | 6 ++++-- packages/app/src/pages/home.tsx | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/app/src/index.css b/packages/app/src/index.css index bc6866f976..80a9cdb559 100644 --- a/packages/app/src/index.css +++ b/packages/app/src/index.css @@ -224,7 +224,8 @@ [data-slot="model-selector-scroll"]::before { top: 0; - background: linear-gradient(to bottom, var(--v2-background-bg-layer-01), transparent); + height: 12px; + background: linear-gradient(180deg, color-mix(in srgb, var(--v2-background-bg-layer-01) 80%, transparent) 0%, color-mix(in srgb, var(--v2-background-bg-layer-01) 78.3336%, transparent) 4.7%, color-mix(in srgb, var(--v2-background-bg-layer-01) 76.4824%, transparent) 8.9%, color-mix(in srgb, var(--v2-background-bg-layer-01) 74.368%, transparent) 12.8%, color-mix(in srgb, var(--v2-background-bg-layer-01) 71.9123%, transparent) 16.56%, color-mix(in srgb, var(--v2-background-bg-layer-01) 69.037%, transparent) 20.37%, color-mix(in srgb, var(--v2-background-bg-layer-01) 65.664%, transparent) 24.4%, color-mix(in srgb, var(--v2-background-bg-layer-01) 61.715%, transparent) 28.83%, color-mix(in srgb, var(--v2-background-bg-layer-01) 57.1117%, transparent) 33.84%, color-mix(in srgb, var(--v2-background-bg-layer-01) 51.776%, transparent) 39.6%, color-mix(in srgb, var(--v2-background-bg-layer-01) 45.6296%, transparent) 46.3%, color-mix(in srgb, var(--v2-background-bg-layer-01) 38.5944%, transparent) 54.1%, color-mix(in srgb, var(--v2-background-bg-layer-01) 30.592%, transparent) 63.2%, color-mix(in srgb, var(--v2-background-bg-layer-01) 21.5443%, transparent) 73.76%, color-mix(in srgb, var(--v2-background-bg-layer-01) 11.373%, transparent) 85.97%, color-mix(in srgb, var(--v2-background-bg-layer-01) 0%, transparent) 100%); } [data-slot="manage-models-scroll"]::before { @@ -239,7 +240,8 @@ [data-slot="model-selector-scroll"]::after { bottom: 0; - background: linear-gradient(to top, var(--v2-background-bg-layer-01), transparent); + height: 12px; + background: linear-gradient(0deg, color-mix(in srgb, var(--v2-background-bg-layer-01) 80%, transparent) 0%, color-mix(in srgb, var(--v2-background-bg-layer-01) 78.3336%, transparent) 4.7%, color-mix(in srgb, var(--v2-background-bg-layer-01) 76.4824%, transparent) 8.9%, color-mix(in srgb, var(--v2-background-bg-layer-01) 74.368%, transparent) 12.8%, color-mix(in srgb, var(--v2-background-bg-layer-01) 71.9123%, transparent) 16.56%, color-mix(in srgb, var(--v2-background-bg-layer-01) 69.037%, transparent) 20.37%, color-mix(in srgb, var(--v2-background-bg-layer-01) 65.664%, transparent) 24.4%, color-mix(in srgb, var(--v2-background-bg-layer-01) 61.715%, transparent) 28.83%, color-mix(in srgb, var(--v2-background-bg-layer-01) 57.1117%, transparent) 33.84%, color-mix(in srgb, var(--v2-background-bg-layer-01) 51.776%, transparent) 39.6%, color-mix(in srgb, var(--v2-background-bg-layer-01) 45.6296%, transparent) 46.3%, color-mix(in srgb, var(--v2-background-bg-layer-01) 38.5944%, transparent) 54.1%, color-mix(in srgb, var(--v2-background-bg-layer-01) 30.592%, transparent) 63.2%, color-mix(in srgb, var(--v2-background-bg-layer-01) 21.5443%, transparent) 73.76%, color-mix(in srgb, var(--v2-background-bg-layer-01) 11.373%, transparent) 85.97%, color-mix(in srgb, var(--v2-background-bg-layer-01) 0%, transparent) 100%); } @supports (animation-timeline: --home-projects-scroll) and (timeline-scope: --home-projects-scroll) { diff --git a/packages/app/src/pages/home.tsx b/packages/app/src/pages/home.tsx index efbab7809e..762b8b2c8e 100644 --- a/packages/app/src/pages/home.tsx +++ b/packages/app/src/pages/home.tsx @@ -1278,7 +1278,7 @@ function HomeSessionSearch(props: {
-
@@ -1129,12 +1119,12 @@ function ModelPeersSection(props: { data: StatsModelData | null }) { ) } -function MetricCard(props: { label: string; value: string; detail: string; state?: "positive" | "negative" }) { +function MetricCard(props: { label: string; value: string; detail?: string; state?: "positive" | "negative" }) { return (
{props.label} {props.value} -

{props.detail}

+ {(detail) =>

{detail()}

}
) } @@ -1517,8 +1507,9 @@ function formatMoney(value: number) { return `$${value.toFixed(value >= 10 ? 0 : 2)}` } -function formatCatalogPrice(value: ModelCatalogCost) { - return `${formatModelPrice(value.input)} / ${formatModelPrice(value.output)}` +function formatCatalogUnitPrice(value: number | undefined) { + if (value === undefined) return "-" + return `${formatModelPrice(value)} / 1M` } function formatModelPrice(value: number) { diff --git a/packages/stats/app/src/routes/index.css b/packages/stats/app/src/routes/index.css index 0a7b131ceb..a2b652b15b 100644 --- a/packages/stats/app/src/routes/index.css +++ b/packages/stats/app/src/routes/index.css @@ -3913,6 +3913,50 @@ color: #b82d35; } +[data-page="stats"] [data-slot="model-efficiency-pattern"] { + width: 100%; + height: 16px; + margin-bottom: 40px; + overflow: hidden; + background: color-mix(in srgb, var(--stats-text) 10%, transparent); + mask-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 6 6' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M0 0H2V2H0V0Z' fill='black'/%3E%3C/svg%3E"); + mask-position: center top; + mask-repeat: repeat; + mask-size: 6px 6px; + -webkit-mask-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 6 6' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M0 0H2V2H0V0Z' fill='black'/%3E%3C/svg%3E"); + -webkit-mask-position: center top; + -webkit-mask-repeat: repeat; + -webkit-mask-size: 6px 6px; +} + +[data-page="stats"] [data-component="model-efficiency-grid"] { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 12px; +} + +[data-page="stats"] [data-component="model-efficiency-grid"] [data-component="model-metric"] { + display: flex; + align-items: flex-start; + flex-direction: column; + box-sizing: border-box; + gap: 0; + height: 120px; + min-height: 120px; + padding: 16px; + background: var(--stats-layer); +} + +[data-page="stats"] [data-component="model-efficiency-grid"] [data-component="model-metric"] span { + line-height: 14px; +} + +[data-page="stats"] [data-component="model-efficiency-grid"] [data-component="model-metric"] strong { + margin-top: 32px; + font-size: 28px; + line-height: 42px; +} + [data-page="stats"] [data-section="model-panel"][data-variant="lab-models"] { overflow: hidden; } @@ -5699,6 +5743,7 @@ [data-page="stats"] [data-component="model-metric-grid"], [data-page="stats"] [data-component="model-metric-grid"][data-variant="dense"], + [data-page="stats"] [data-component="model-efficiency-grid"], [data-page="stats"] [data-component="lab-model-grid"] { grid-template-columns: 1fr 1fr; } @@ -6125,6 +6170,7 @@ [data-page="stats"] [data-component="model-metric-grid"], [data-page="stats"] [data-component="model-metric-grid"][data-variant="dense"], + [data-page="stats"] [data-component="model-efficiency-grid"], [data-page="stats"] [data-component="lab-model-grid"] { grid-template-columns: 1fr; } @@ -6133,6 +6179,15 @@ min-height: 112px; } + [data-page="stats"] [data-component="model-efficiency-grid"] [data-component="model-metric"] { + height: 112px; + min-height: 112px; + } + + [data-page="stats"] [data-component="model-efficiency-grid"] [data-component="model-metric"] strong { + margin-top: 24px; + } + [data-page="stats"] [data-component="model-usage-chart"] { --model-usage-mobile-bar-width: 16px; --model-usage-mobile-edge-space: 48px; From 1c25b2f298d49d89ce473646de5766aa754c59f2 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Tue, 7 Jul 2026 11:55:25 +0000 Subject: [PATCH 19/33] chore: generate --- packages/stats/app/src/routes/[lab]/[model].tsx | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/stats/app/src/routes/[lab]/[model].tsx b/packages/stats/app/src/routes/[lab]/[model].tsx index 70647f0679..a71fd8e61b 100644 --- a/packages/stats/app/src/routes/[lab]/[model].tsx +++ b/packages/stats/app/src/routes/[lab]/[model].tsx @@ -884,8 +884,14 @@ function ModelEfficiencySection(props: { data: StatsModelData | null; catalog: M
+
+

+ Compare Another Pair. Choose two models to compare. +

+ 1} + fallback={ +
+ No models found +

The model list could not be loaded.

+
+ } + > + +
+
+ + +