Skip to content
454 linesCodeBlameRaw

Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.

Costs: Cloudflare's subscriptions read from Cloudflare each day, the estimate only until then; sudo's costs split into Costs & margin and Bill & pricing1import { type ReactNode } from "react";
2
3import type { CostsReport, PriceProposal } from "@g1t/contracts";
4
5import type { Route } from "./+types/costs-bill";
6import { CostsHeader } from "~/components/costs-header";
7import { Badge, Button, EmptyState, Field, Input, Notice, Section, When } from "~/components/ui";
Merge Cloudflare's usage over its billing cycle: every page read, included amounts once a cycle, a projection, test-mode charges never money in (billing 0052)8import { basisLabel, billReadNote, countLabel, cycleHeadline, driftLabel, percentLabel, proposalOutcome, unitDollars, unpricedMeters, versionCells } from "~/lib/costs";
Costs: Cloudflare's subscriptions read from Cloudflare each day, the estimate only until then; sudo's costs split into Costs & margin and Bill & pricing9import { type CostsActionResult, costsAction, costsLoader } from "~/lib/costs-route.server";
10import { dollarsField, usd } from "~/lib/money";
11
12export const meta: Route.MetaFunction = () => [{ title: "Bill & pricing · sudo" }, { name: "robots", content: "noindex, nofollow" }];
13
14export const loader = ({ request, context }: Route.LoaderArgs) => costsLoader(request, context);
15export const action = ({ request, context }: Route.ActionArgs) => costsAction(request, context);
16
17/**
18 * Cloudflare's bill as read, what does not add up against it, and the
19 * price book: proposals, versions, mappings and the guardrails on them.
20 */
21export default function CostsBill({ loaderData, actionData }: Route.ComponentProps) {
22 const { range, report, error, done } = loaderData;
23 const failed = actionData && "error" in actionData ? (actionData as CostsActionResult) : null;
24 const description =
25 "Cloudflare's bill as read, line by line, what does not add up against g1t's own counts and price book, and the prices themselves: proposals waiting for a decision, every version, the mappings and the guardrails.";
26 if (!report) {
27 return (
28 <main className="mx-auto max-w-6xl px-4 py-8 sm:py-10">
29 <CostsHeader page="bill" range={range} report={null} done={null} runError={null} description={description} />
30 <div className="mt-5">
31 <Notice tone="warn">Billing did not answer for costs: {error}</Notice>
32 </div>
33 </main>
34 );
35 }
36 return (
37 <main className="mx-auto max-w-6xl px-4 py-8 sm:py-10">
38 <CostsHeader page="bill" range={range} report={report} done={done} runError={failed?.section === "run" ? failed.error : null} description={description} />
39
Merge Cloudflare's usage over its billing cycle: every page read, included amounts once a cycle, a projection, test-mode charges never money in (billing 0052)40 <CycleSection report={report} />
41
Costs: Cloudflare's subscriptions read from Cloudflare each day, the estimate only until then; sudo's costs split into Costs & margin and Bill & pricing42 <Section
Merge Cloudflare's usage over its billing cycle: every page read, included amounts once a cycle, a projection, test-mode charges never money in (billing 0052)43 className="mt-6"
Costs: Cloudflare's subscriptions read from Cloudflare each day, the estimate only until then; sudo's costs split into Costs & margin and Bill & pricing44 id="drift"
45 title="Drift"
46 description="Over the last 7 days: counts g1t and Cloudflare disagree on past a mapping's threshold, a bill far from the price book's cost of the same usage, and leaks (cost nothing charges for, or a Cloudflare meter no one mapped)."
47 >
48 {report.drift.length === 0 ? (
49 <p className="text-sm text-muted">Nothing has drifted.</p>
50 ) : (
51 <div className="-mx-4 overflow-x-auto sm:-mx-5">
52 <table className="w-full min-w-[44rem] text-sm">
53 <thead>
54 <tr className="border-b border-line text-left text-xs text-muted">
55 <th className="px-4 py-2 font-medium sm:px-5">Product</th>
56 <th className="px-4 py-2 font-medium">Kind</th>
57 <th className="px-4 py-2 text-right font-medium">g1t</th>
58 <th className="px-4 py-2 text-right font-medium">Cloudflare</th>
59 <th className="px-4 py-2 text-right font-medium sm:pr-5">Delta</th>
60 </tr>
61 </thead>
62 <tbody>
63 {report.drift.map((d) => {
64 const money = d.kind !== "count";
65 return (
66 <tr key={`${d.bucket}-${d.kind}`} className="border-b border-line align-top last:border-0">
67 <td className="px-4 py-2.5 sm:px-5">
68 {d.title}
69 <span className="mt-0.5 block max-w-md text-xs text-faint">{d.detail.replace(`${d.title}: `, "")}</span>
70 </td>
71 <td className="px-4 py-2.5">
72 <Badge tone={d.kind === "leak" ? "danger" : "warn"}>{driftLabel(d.kind)}</Badge>
73 </td>
74 <td className="tabular px-4 py-2.5 text-right">{money ? usd(d.ours) : countLabel(d.ours)}</td>
75 <td className="tabular px-4 py-2.5 text-right">{money ? usd(d.cloudflare) : countLabel(d.cloudflare)}</td>
76 <td className="tabular px-4 py-2.5 text-right sm:pr-5">{percentLabel(d.deltaPercent, { signed: true })}</td>
77 </tr>
78 );
79 })}
80 </tbody>
81 </table>
82 </div>
83 )}
84 </Section>
85
86 <Section
87 className="mt-6"
88 id="proposals"
89 title="Price proposals"
90 description={`What the keeper and the reconciler measured. Moves within ${report.settings.autoApplyPercent}% apply themselves${report.settings.autoApply ? "" : " (off now)"}; larger ones wait here. A fall applies at once; a rise after ${report.settings.noticeDays} days' notice, and a monthly meter's from the start of the next month.`}
91 >
92 {report.proposals.length === 0 ? (
93 <p className="text-sm text-muted">No proposals yet.</p>
94 ) : (
95 <ul className="space-y-3">
96 {report.proposals.map((p) => (
97 <ProposalRow key={p.id} proposal={p} error={failed?.section === `proposal-${p.id}` ? failed.error : null} />
98 ))}
99 </ul>
100 )}
101 </Section>
102
103 <Section className="mt-6" title="Price versions" description="Every price as it was and will be. A version never changes once written; each charge records the version it was made at.">
104 <div className="-mx-4 overflow-x-auto sm:-mx-5">
105 <table className="w-full min-w-[44rem] text-sm">
106 <thead>
107 <tr className="border-b border-line text-left text-xs text-muted">
108 <th className="px-4 py-2 font-medium sm:px-5">Meter</th>
109 <th className="px-4 py-2 text-right font-medium">Cost</th>
110 <th className="px-4 py-2 text-right font-medium">Price</th>
111 <th className="px-4 py-2 font-medium">From</th>
112 <th className="px-4 py-2 font-medium sm:pr-5">Why</th>
113 </tr>
114 </thead>
115 <tbody>
Merge costs and margin review: gateway query, own spend, discount meters, superseded rises116 {report.versions.map((v) => {
117 const cells = versionCells(v);
118 return (
119 <tr key={v.id} className="border-b border-line align-top last:border-0">
120 <td className="px-4 py-2.5 sm:px-5">
121 <span className="font-mono text-xs">{v.meter}</span> <span className="text-faint">v{v.version}</span>
122 {!v.appliedAt && (
123 <span className="ml-2">
124 <Badge tone="info">Coming</Badge>
125 </span>
126 )}
127 {cells.note && <span className="block text-xs text-faint">{cells.note}</span>}
128 </td>
129 <td className="tabular px-4 py-2.5 text-right">{cells.cost === "—" ? <span className="text-faint">—</span> : cells.cost}</td>
130 <td className="tabular px-4 py-2.5 text-right">{cells.price}</td>
131 <td className="px-4 py-2.5 text-xs text-muted">
132 <When at={v.effectiveAt} time />
133 </td>
134 <td className="px-4 py-2.5 text-xs text-faint sm:pr-5">
135 {v.reason} · {v.createdBy}
136 </td>
137 </tr>
138 );
139 })}
Costs: Cloudflare's subscriptions read from Cloudflare each day, the estimate only until then; sudo's costs split into Costs & margin and Bill & pricing140 </tbody>
141 </table>
142 </div>
143 </Section>
144
145 <Section
146 className="mt-6"
147 title="Cloudflare's lines"
148 description="Every meter Cloudflare billed or counted in the range, as it named it, and which of g1t's products it is a cost of. Not mapped means no one decided what pays for it."
149 >
150 {report.lines.length === 0 ? (
151 <EmptyState title="No lines yet">The bill is read once a day at 04:17 UTC, or now with “Run the analysis now”.</EmptyState>
152 ) : (
153 <div className="-mx-4 overflow-x-auto sm:-mx-5">
154 <table className="w-full min-w-[48rem] text-sm">
155 <thead>
156 <tr className="border-b border-line text-left text-xs text-muted">
157 <th className="px-4 py-2 font-medium sm:px-5">Product / meter</th>
158 <th className="px-4 py-2 text-right font-medium">Quantity</th>
159 <th className="px-4 py-2 text-right font-medium">Cost</th>
160 <th className="px-4 py-2 font-medium sm:pr-5">Cost of</th>
161 </tr>
162 </thead>
163 <tbody>
164 {report.lines.map((l) => (
165 <tr key={`${l.source}-${l.product}-${l.meter}`} className="border-b border-line align-top last:border-0">
166 <td className="px-4 py-2.5 sm:px-5">
167 <span className="font-mono text-xs">
168 {l.product} / {l.meter}
169 </span>
170 <span className="block text-xs text-faint">
171 {l.rawName}
172 {l.source === "artifacts_events" ? " (Artifacts' own count)" : ""}
173 </span>
174 </td>
175 <td className="tabular px-4 py-2.5 text-right">
176 {countLabel(l.quantity)} <span className="text-xs text-faint">{l.unit}</span>
177 </td>
178 <td className="tabular px-4 py-2.5 text-right">{usd(l.costMicros)}</td>
179 <td className="px-4 py-2.5 sm:pr-5">{l.bucket ? <span className="text-fg-soft">{l.bucket}</span> : <Badge tone="danger">Not mapped</Badge>}</td>
180 </tr>
181 ))}
182 </tbody>
183 </table>
184 </div>
185 )}
186 </Section>
187
188 <Section
189 className="mt-6"
190 id="mappings"
191 title="Mappings"
192 description="Which of g1t's products each Cloudflare meter is a cost of. The longest matching meter prefix wins; * is the rest of the product. A price meter is measured from it; an own meter is g1t's count to compare; scaling prices one of g1t's units at as many of Cloudflare's as it took."
193 >
194 <MappingsTable report={report} />
195 <form method="post" action="#mappings" className="mt-5 grid gap-3 sm:grid-cols-2 lg:grid-cols-4">
196 <input type="hidden" name="intent" value="mapping" />
197 <Field label="Cloudflare product">
198 <Input name="product" placeholder="artifacts" defaultValue={failed?.section === "mapping" ? failed.values?.product : ""} required />
199 </Field>
200 <Field label="Meter prefix" hint="Or * for all of it">
201 <Input name="meter" placeholder="*" defaultValue={failed?.section === "mapping" ? failed.values?.meter : ""} />
202 </Field>
203 <Field label="g1t product">
204 <Input name="bucket" placeholder="git" defaultValue={failed?.section === "mapping" ? failed.values?.bucket : ""} />
205 </Field>
206 <Field label="Drift threshold, %">
207 <Input name="driftPercent" inputMode="decimal" placeholder="10" defaultValue={failed?.section === "mapping" ? failed.values?.driftPercent : ""} />
208 </Field>
209 <Field label="Price meter" hint="Optional">
210 <Input name="priceMeter" placeholder="git_operations" defaultValue={failed?.section === "mapping" ? failed.values?.priceMeter : ""} />
211 </Field>
212 <Field label="Own meter" hint="Optional">
213 <Input name="ownMeter" placeholder="git_operations" defaultValue={failed?.section === "mapping" ? failed.values?.ownMeter : ""} />
214 </Field>
215 <Field label="Note">
216 <Input name="note" defaultValue={failed?.section === "mapping" ? failed.values?.note : ""} />
217 </Field>
218 <div className="flex flex-col justify-end gap-2">
219 <label className="flex items-center gap-2 text-sm text-muted">
Merge branch 'worktree-agent-ad7c6d88d93adc817'220 <input type="checkbox" name="scaleToOwn" className="accent-[var(--g1t-accent)]" /> Scale to g1t's count
Costs: Cloudflare's subscriptions read from Cloudflare each day, the estimate only until then; sudo's costs split into Costs & margin and Bill & pricing221 </label>
222 <div className="flex gap-2">
223 <Button type="submit">Save mapping</Button>
224 <Button type="submit" name="remove" value="1" variant="danger">
225 Remove
226 </Button>
227 </div>
228 </div>
229 {failed?.section === "mapping" && (
230 <div className="sm:col-span-2 lg:col-span-4">
231 <Notice tone="error">{failed.error}</Notice>
232 </div>
233 )}
234 </form>
235 </Section>
236
237 <Section className="mt-6" id="guardrails" title="Guardrails" description="What may change prices without a person, and when staff hear about margin.">
238 <form method="post" action="#guardrails" className="grid gap-3 sm:grid-cols-2 lg:grid-cols-4">
239 <input type="hidden" name="intent" value="settings" />
240 <Field label="Apply small moves on their own">
241 <label className="flex h-[38px] items-center gap-2 text-sm text-fg-soft">
Merge branch 'worktree-agent-ad7c6d88d93adc817'242 <input type="checkbox" name="autoApply" defaultChecked={report.settings.autoApply} className="accent-[var(--g1t-accent)]" /> On
Costs: Cloudflare's subscriptions read from Cloudflare each day, the estimate only until then; sudo's costs split into Costs & margin and Bill & pricing243 </label>
244 </Field>
245 <Field label="Up to, either way, %">
246 <Input name="autoApplyPercent" inputMode="decimal" defaultValue={String(report.settings.autoApplyPercent)} />
247 </Field>
248 <Field label="Notice for a rise, days">
249 <Input name="noticeDays" inputMode="numeric" defaultValue={String(report.settings.noticeDays)} />
250 </Field>
251 <Field label="Margin floor, %">
252 <Input name="marginFloorPercent" inputMode="decimal" defaultValue={String(report.settings.marginFloorPercent)} />
253 </Field>
254 <Field label="Alert after, days in a row">
255 <Input name="alertDays" inputMode="numeric" defaultValue={String(report.settings.alertDays)} />
256 </Field>
257 <Field label="Ignore days costing under, $">
258 <Input name="minDailyCost" inputMode="decimal" defaultValue={dollarsField(report.settings.minDailyCostMicros)} />
259 </Field>
260 <Field label="Flag a workspace costing more than its revenue ×">
261 <Input name="anomalyFactor" inputMode="decimal" defaultValue={String(report.settings.anomalyFactor)} />
262 </Field>
263 <Field label="And at least, $">
264 <Input name="anomalyFloor" inputMode="decimal" defaultValue={dollarsField(report.settings.anomalyFloorMicros)} />
265 </Field>
Merge Stripe Tax, the card fee on card payments, and one free workspace per person266 <Field label="Card fee on card payments">
Usage, Billing settings and prepaid AI credit; fixes from the UX audit267 <label className="flex h-[38px] items-center gap-2 text-sm text-fg-soft">
Merge Stripe Tax, the card fee on card payments, and one free workspace per person268 <input type="checkbox" name="cardFee" defaultChecked={report.settings.cardFee ?? true} className="accent-[var(--g1t-accent)]" /> Its own line; never on invoiced billing or bank transfers
Usage, Billing settings and prepaid AI credit; fixes from the UX audit269 </label>
270 </Field>
Costs: Cloudflare's subscriptions read from Cloudflare each day, the estimate only until then; sudo's costs split into Costs & margin and Bill & pricing271 <div className="sm:col-span-2 lg:col-span-4">
272 {failed?.section === "settings" && (
273 <div className="mb-3">
274 <Notice tone="error">{failed.error}</Notice>
275 </div>
276 )}
277 <Button type="submit">Save guardrails</Button>
278 </div>
279 </form>
280 </Section>
281 </main>
282 );
283}
284
285function ProposalRow({ proposal: p, error }: { proposal: PriceProposal; error: string | null }) {
286 const rise = p.proposedCostMicros > p.currentCostMicros;
Merge costs and margin review: gateway query, own spend, discount meters, superseded rises287 const outcome = proposalOutcome(p);
Costs: Cloudflare's subscriptions read from Cloudflare each day, the estimate only until then; sudo's costs split into Costs & margin and Bill & pricing288 const statusTone = p.status === "open" ? "warn" : p.status === "rejected" || p.status === "superseded" ? "plain" : "mint";
289 return (
290 <li className="rounded-md border border-line px-4 py-3">
291 <div className="flex flex-wrap items-baseline justify-between gap-2">
292 <div className="min-w-0">
293 <span className="font-medium">{p.title}</span> <span className="font-mono text-xs text-faint">{p.meter}</span>
294 </div>
295 <div className="flex items-center gap-2">
296 {p.suspect && <Badge tone="danger">Far off: look first</Badge>}
297 <Badge tone={statusTone}>{p.status}</Badge>
298 </div>
299 </div>
300 <p className="tabular mt-1.5 text-sm">
Merge branch 'worktree-agent-ad7c6d88d93adc817'301 Cost {unitDollars(p.currentCostMicros)} → <span className={rise ? "text-warn" : "text-success"}>{unitDollars(p.proposedCostMicros)}</span> per {p.unit || "unit"}{" "}
Costs: Cloudflare's subscriptions read from Cloudflare each day, the estimate only until then; sudo's costs split into Costs & margin and Bill & pricing302 <span className="text-faint">({percentLabel(p.changePercent, { signed: true })})</span>
303 </p>
304 <p className="mt-1 text-xs text-muted">{p.reason}</p>
305 <p className="mt-1 text-xs text-faint">
306 From the {p.source}, <When at={p.createdAt} time />
Merge costs and margin review: gateway query, own spend, discount meters, superseded rises307 {outcome ? ` · ${outcome}` : ""}
308 {p.effectiveAt && p.status !== "superseded" ? (
Costs: Cloudflare's subscriptions read from Cloudflare each day, the estimate only until then; sudo's costs split into Costs & margin and Bill & pricing309 <>
310 {" "}
Merge costs and margin review: gateway query, own spend, discount meters, superseded rises311 · in force from <When at={p.effectiveAt} time />
Costs: Cloudflare's subscriptions read from Cloudflare each day, the estimate only until then; sudo's costs split into Costs & margin and Bill & pricing312 </>
313 ) : null}
314 {p.note ? ` · “${p.note}”` : ""}
315 </p>
316 {p.status === "open" && (
317 <form method="post" action="#proposals" className="mt-3 flex flex-col gap-2 sm:flex-row sm:items-center">
318 <input type="hidden" name="intent" value="decide" />
319 <input type="hidden" name="id" value={p.id} />
320 <Input name="note" placeholder="A note (needed to reject)" className="sm:max-w-sm" />
321 <div className="flex gap-2">
322 <Button type="submit" name="decision" value="approve" variant="lavender">
323 Approve
324 </Button>
325 <Button type="submit" name="decision" value="reject" variant="quiet">
326 Reject
327 </Button>
328 </div>
329 </form>
330 )}
331 {error && (
332 <div className="mt-2">
333 <Notice tone="error">{error}</Notice>
334 </div>
335 )}
336 </li>
337 );
338}
339
Merge Cloudflare's usage over its billing cycle: every page read, included amounts once a cycle, a projection, test-mode charges never money in (billing 0052)340/**
341 * Cloudflare's current billing cycle, the way its Billable usage page shows
342 * it: each meter's use, what the cycle includes, what is past it and what
343 * that costs, with the total so far and the projection.
344 */
345function CycleSection({ report }: { report: CostsReport }) {
346 const cycle = report.cycle;
347 const read = report.billRead ? billReadNote(report.billRead) : null;
348 const unpriced = cycle ? unpricedMeters(cycle.meters) : [];
349 const headline = cycle ? cycleHeadline(cycle) : null;
350 return (
351 <Section
352 className="mt-5"
353 id="cycle"
354 title="This billing cycle"
355 description="Cloudflare's usage bill for the cycle so far, meter by meter. Each meter's included amount is the account's, once a cycle; the cost is Cloudflare's own where its lines carry one, else the list price past the included amount (per-million meters in whole millions), on the days the cycle's total passed it. Subscriptions are apart."
356 >
357 {!cycle || !headline ? (
358 <EmptyState title="No cycle read yet">The bill is read once a day at 04:17 UTC, or now with “Run the analysis now”.</EmptyState>
359 ) : (
360 <>
361 <p className="text-sm">
362 <span className="font-medium text-fg">{headline.title}</span>
363 <span className="block text-muted">{headline.detail}.</span>
364 </p>
365 {unpriced.length > 0 && (
366 <div className="mt-3">
367 <Notice tone="warn">
368 Used with no list price, so counted at $0 until Cloudflare puts a cost on it or a price is added (src/cycle.rs):{" "}
369 {unpriced.map((m) => `${m.product} / ${m.meter}`).join(", ")}.
370 </Notice>
371 </div>
372 )}
373 <div className="-mx-4 mt-3 overflow-x-auto sm:-mx-5">
374 <table className="w-full min-w-[48rem] text-sm">
375 <thead>
376 <tr className="border-b border-line text-left text-xs text-muted">
377 <th className="px-4 py-2 font-medium sm:px-5">Meter</th>
378 <th className="px-4 py-2 text-right font-medium">Used</th>
379 <th className="px-4 py-2 text-right font-medium">Included</th>
380 <th className="px-4 py-2 text-right font-medium">Billable</th>
381 <th className="px-4 py-2 text-right font-medium sm:pr-5">Cost</th>
382 </tr>
383 </thead>
384 <tbody>
385 {cycle.meters.map((m) => (
386 <tr key={`${m.product}-${m.meter}`} className="border-b border-line align-top last:border-0">
387 <td className="px-4 py-2.5 sm:px-5">
388 {m.rawName}
389 <span className="block text-xs text-faint">{basisLabel(m.basis)}</span>
390 </td>
391 <td className="tabular px-4 py-2.5 text-right">
392 {countLabel(m.quantity)} <span className="text-xs text-faint">{m.unit}</span>
393 </td>
394 <td className="tabular px-4 py-2.5 text-right text-muted">{m.included == null ? "—" : countLabel(m.included)}</td>
395 <td className="tabular px-4 py-2.5 text-right">{countLabel(m.billableQuantity)}</td>
396 <td className="tabular px-4 py-2.5 text-right sm:pr-5">{usd(m.costMicros)}</td>
397 </tr>
398 ))}
399 <tr>
400 <td className="px-4 py-2.5 font-medium sm:px-5" colSpan={4}>
401 Usage so far
402 </td>
403 <td className="tabular px-4 py-2.5 text-right font-medium sm:pr-5">{usd(cycle.usageMicros, { cents: true })}</td>
404 </tr>
405 </tbody>
406 </table>
407 </div>
408 </>
409 )}
410 {read && (
411 <p className={`mt-3 text-xs ${read.warn ? "text-warn" : "text-faint"}`}>
412 Last read <When at={report.billRead?.readAt ?? null} time />: {read.text}
413 </p>
414 )}
415 </Section>
416 );
417}
418
Costs: Cloudflare's subscriptions read from Cloudflare each day, the estimate only until then; sudo's costs split into Costs & margin and Bill & pricing419function MappingsTable({ report }: { report: CostsReport }) {
420 return (
421 <div className="-mx-4 overflow-x-auto sm:-mx-5">
422 <table className="w-full min-w-[44rem] text-sm">
423 <thead>
424 <tr className="border-b border-line text-left text-xs text-muted">
425 <th className="px-4 py-2 font-medium sm:px-5">Cloudflare</th>
426 <th className="px-4 py-2 font-medium">g1t product</th>
427 <th className="px-4 py-2 font-medium">Price meter</th>
428 <th className="px-4 py-2 font-medium">Own meter</th>
429 <th className="px-4 py-2 text-right font-medium sm:pr-5">Drift at</th>
430 </tr>
431 </thead>
432 <tbody>
433 {report.mappings.map((m) => (
434 <tr key={`${m.product}-${m.meter}`} className="border-b border-line align-top last:border-0">
435 <td className="px-4 py-2 sm:px-5">
436 <span className="font-mono text-xs">
437 {m.product} / {m.meter}
438 </span>
439 {m.note && <span className="block text-xs text-faint">{m.note}</span>}
440 </td>
441 <td className="px-4 py-2 text-fg-soft">{m.bucket}</td>
442 <td className="px-4 py-2 font-mono text-xs text-muted">{m.priceMeter ?? "—"}</td>
443 <td className="px-4 py-2 font-mono text-xs text-muted">
444 {m.ownMeter ?? "—"}
445 {m.scaleToOwn && <span className="block font-sans text-faint">scaled</span>}
446 </td>
447 <td className="tabular px-4 py-2 text-right text-muted sm:pr-5">{m.driftPercent}%</td>
448 </tr>
449 ))}
450 </tbody>
451 </table>
452 </div>
453 );
454}

This file's history is long; its oldest lines are credited to the oldest commit read.