| 1 | /** |
| 2 | * The workspace overview's ground truth, shaped from one reading of the |
| 3 | * work (lib/signals.ts) for components/ground-truth.tsx: the pulse, spend |
| 4 | * threaded to the work, each team's last two weeks, what waits on a first |
| 5 | * review, and the quiet changes, each list in sentences. Imports only types |
| 6 | * and pure functions, so the page's parts can be drawn and tested alone. |
| 7 | */ |
| 8 | import type { SignalData } from "./signals.server"; |
| 9 | import { |
| 10 | type Group, |
| 11 | dailySignal, |
| 12 | dayList, |
| 13 | harnessWords, |
| 14 | namesWords, |
| 15 | newGround, |
| 16 | newHabits, |
| 17 | providerQuarter, |
| 18 | quarterSpend, |
| 19 | rhythm, |
| 20 | silos, |
| 21 | spendFlow, |
| 22 | spoken, |
| 23 | spreading, |
| 24 | waitingRows, |
| 25 | } from "./signals"; |
| 26 | |
| 27 | /** Entries each quiet change lists. */ |
| 28 | export const QUIET_ROWS = 4; |
| 29 | |
| 30 | /** What the rhythm's rows are: teams by their projects, else the projects. Null when identity did not answer. */ |
| 31 | export type Groups = { groups: Group[]; by: "team" | "project" } | null; |
| 32 | |
| 33 | export function shapeGround(found: SignalData, groups: Groups, now: number) { |
| 34 | const name = (id: string) => found.repos[id]?.name ?? "a project"; |
| 35 | const path = (id: string) => (found.repos[id] ? `/${found.repos[id]!.namespace}/${found.repos[id]!.name}` : null); |
| 36 | const quarter = quarterSpend({ repos: found.repos, signals: found.signals, quarterUsage: found.quarterUsage, quarter: found.range.quarter }); |
| 37 | // Practices catching on in each team (else across the workspace), the newest first. |
| 38 | const scopes = |
| 39 | groups?.by === "team" |
| 40 | ? groups.groups.map((group) => ({ key: group.key, label: group.label, only: new Set(group.repoIds) as ReadonlySet<string> | undefined })) |
| 41 | : [{ key: "all", label: "the workspace", only: undefined }]; |
| 42 | const spreads = scopes.flatMap((scope) => spreading(found.signals.harnesses, now, { only: scope.only }).map((spread) => ({ scope, spread }))); |
| 43 | return { |
| 44 | tiles: dailySignal(found).tiles, |
| 45 | complete: found.complete, |
| 46 | capped: found.signals.pullsCapped, |
| 47 | flow: spendFlow({ slug: found.slug, repos: found.repos, signals: found.signals, quarterUsage: found.quarterUsage, quarter: found.range.quarter }), |
| 48 | sized: (quarter?.total ?? 0) > 0 ? ("spend" as const) : ("pulls" as const), |
| 49 | quarterSpend: quarter?.total ?? null, |
| 50 | sessionSpend: quarter?.sessions ?? 0, |
| 51 | rhythm: groups ? { by: groups.by, rows: rhythm(found.signals.days, dayList(found.range.until, found.range.days), groups.groups) } : null, |
| 52 | waiting: waitingRows(found.signals.waiting, found.repos).map((pull) => ({ ...pull, age: spoken(Math.max(0, (now - Date.parse(pull.createdAt)) / 1000)) })), |
| 53 | silos: silos(found.signals.authors, now) |
| 54 | .slice(0, QUIET_ROWS) |
| 55 | .map((silo) => ({ |
| 56 | key: silo.repoId, |
| 57 | to: path(silo.repoId), |
| 58 | where: name(silo.repoId), |
| 59 | line: `${silo.name} merged ${silo.theirs} of its ${silo.recent} changes in 90 days`, |
| 60 | more: silo.joining.length > 0 ? `${silo.joining.map((who) => who.name).join(", ")} started this month` : "No one else has started there", |
| 61 | })), |
| 62 | newGround: newGround(found.signals.authors, now) |
| 63 | .slice(0, QUIET_ROWS) |
| 64 | .map((row) => ({ |
| 65 | key: `${row.ownerId}:${row.repoId}`, |
| 66 | to: path(row.repoId), |
| 67 | where: name(row.repoId), |
| 68 | line: `${row.name} merged a first change here`, |
| 69 | more: `${row.elsewhere} merges elsewhere first`, |
| 70 | })), |
| 71 | spreading: spreads.slice(0, QUIET_ROWS).map(({ scope, spread }) => ({ |
| 72 | key: `${scope.key}:${spread.harness}`, |
| 73 | to: null, |
| 74 | where: `${harnessWords(spread.harness)} in ${scope.label}`, |
| 75 | line: `${namesWords(spread.recent.map((person) => person.name))} started using it this month, after ${spread.first.name}`, |
| 76 | more: `${spread.people} people use it now`, |
| 77 | })), |
| 78 | habits: newHabits(found.signals.pulls, now) |
| 79 | .slice(0, QUIET_ROWS) |
| 80 | .map((row) => ({ |
| 81 | key: row.ownerId, |
| 82 | to: path(row.repoId), |
| 83 | where: name(row.repoId), |
| 84 | line: `${row.name} merged a first agent-made change`, |
| 85 | more: `after ${row.byHand} by hand`, |
| 86 | })), |
| 87 | }; |
| 88 | } |
| 89 | |
| 90 | export type Ground = ReturnType<typeof shapeGround> & { providers: ReturnType<typeof providerQuarter> }; |