From b17b6a52b99d7d6531a056c1bde7fed223f5e2ae Mon Sep 17 00:00:00 2001 From: Yash Datta Date: Sat, 12 Sep 2026 21:27:54 +0800 Subject: [PATCH 1/2] feat(web): per-backend spend on the fleet rail MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit First buildable slice of P5.4 (docs/conductor-frontends-design.md §9, §7). The rail's cost figure now opens into a per-backend split — which backend is most of the bill, how many of its sessions are working, and what it has spent. §7 calls this a metaharness differentiator rather than a nicety, and the reason is structural: no single-vendor tool needs a per-backend breakdown because it only ever has one backend. §9 asks for exactly this zoom above a single session. The rollup is pure (`lib/fleet-economics.ts`), so the part worth testing needs no reactive root. Three decisions worth naming: **The daemon's `agg.totalCostUsd` stays the headline number** rather than a sum of these rows. It counts tasks that may have aged off the capped client board, so a locally derived total would drift low exactly on the long-lived fleets where spend matters. This table answers a different question — how the spend SPLITS — and is scoped to the sessions the board actually describes. **A session with no `providerId` is reported as `unknown`**, never folded into the default backend. Attributing spend to a provider that may not have incurred it is worse than admitting the gap, in a view whose entire purpose is comparing backends against each other. **Share is zero when nothing has been spent, never an even split.** Dividing by a zero total to show four backends at 25% would invent a fact from an absence. Sessions with no usage still COUNT while contributing no spend, so a fleet of ten idle sessions reads as ten sessions at $0 rather than as an empty fleet. `active` counts only genuinely in-flight turns — a session at `waiting_approval` is stopped, and belongs in the attention queue, not the concurrency number. Collapsed by default: it answers a question you ask occasionally, and the lanes answer the one you ask constantly. 9 economics tests; 252 web tests; typecheck, lint and build clean. Co-Authored-By: Claude Opus 5 (1M context) --- web/src/components/FleetRail.tsx | 75 ++++++++++++++++-- web/src/lib/fleet-economics.test.ts | 113 ++++++++++++++++++++++++++++ web/src/lib/fleet-economics.ts | 113 ++++++++++++++++++++++++++++ 3 files changed, 296 insertions(+), 5 deletions(-) create mode 100644 web/src/lib/fleet-economics.test.ts create mode 100644 web/src/lib/fleet-economics.ts diff --git a/web/src/components/FleetRail.tsx b/web/src/components/FleetRail.tsx index aa5e8af..54b2c42 100644 --- a/web/src/components/FleetRail.tsx +++ b/web/src/components/FleetRail.tsx @@ -11,9 +11,10 @@ * here — it is the existing attach flow, reached from a fleet node. */ -import { Component, For, Show, createMemo, onCleanup, onMount } from "solid-js"; +import { Component, For, Show, createMemo, createSignal, onCleanup, onMount } from "solid-js"; -import { formatCostUsd } from "../lib/format"; +import { formatCostUsd, formatTokens } from "../lib/format"; +import { costShare, fleetEconomics, type FleetEconomics } from "../lib/fleet-economics"; import { groupIntoLanes, needsYouCount, @@ -77,6 +78,16 @@ const FleetRail: Component = () => { }); const attention = createMemo(() => needsYouCount(lanes())); + // Per-backend spend across the fleet the board actually describes: the + // conductor plus the sessions it has dispatched to or spawned (§9's zoom + // ABOVE a single session). Collapsed by default — it answers a question you + // ask occasionally, and the lanes answer the one you ask constantly. + const [showSpend, setShowSpend] = createSignal(false); + const econ = createMemo(() => { + const b = board(); + return fleetEconomics(b.conductor ? [b.conductor, ...b.workers] : b.workers); + }); + return (