Public: anyone can read this; the crew can change it.

The board's money line: cost, revenue and the gap, per forge and per app, today and this month #10983

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

Site admins now see the money line at the top of `/board`, under the header bar. It reads "This month · cost · revenue · gap · today's gap · Slider N". The gap uses the accent colour when positive and the warning tint when negative. "Slider N" links to `/settings/economics`. Next to the line is a server-built SVG of the last 30 UTC days: daily cost and revenue as two lines, with the space between…

shipped production is serving it deploy cca14ac

opened this

Site admins now see the money line at the top of /board, under the header bar. It reads "This month · cost · revenue · gap · today's gap · Slider N". The gap uses the accent colour when positive and the warning tint when negative. "Slider N" links to /settings/economics. Next to the line is a server-built SVG of the last 30 UTC days: daily cost and revenue as two lines, with the space between them shaded by whether that stretch made or lost money. No chart library. An app's page now has a "This month" card for its admins: cost, revenue (linking to the statement), gap, and awake hours split into owned and cloud.

What to look at

  • One sum, not two: the board's revenue is the sum of the apps' statements, to the cent, and a test checks that. readStatement's inputs now come from monthUse/appUse/ratesOf in src/actions/statement.ts. statementFor now uses a new chargeOf (src/core/revenue.ts), which works for any time range. The board adds up the same values.
  • Cost: costOf over the month so far. For one day, it is costOf over that day.
  • One read for all 30 days: usageBetween (src/core/meter.ts) is now the one-range case of a new usageBins, so each day's numbers equal usageBetween for that day; a test compares them. The crew spend for all 30 days comes from one new query, usageDays (src/core/usage.ts), which uses the existing usage_at index.
  • Per-app cost includes an assumption, printed on the card: the app pays its own cloud time, storage and crew spend in full. It also pays a share of the owned machine's monthly cost equal to its share of the forge's owned awake time this month.
  • No layout shift: the line is always one line and the chart is a fixed 240 × 48, zeros included. A test checks the empty-forge case.
  • Who sees it: the forge's line follows the Settings → Economics rule (a site admin who is a person, not an agent). An app's card follows the statement rule (admins of that app). Everyone else gets not_found, and a test checks this.
  • Files: the logic is in src/actions/money.ts, and the line and chart are drawn in src/web/studio/money_line.ts. The CSS is in src/web/style/content.ts; the new classes are listed in docs/design/areas/chart.md and the design is described in docs/PROFIT.md.

What I chose not to do

  • No MCP tool or API endpoint: those are #511.
  • No terminal screen, because no new page was added.
  • Fresh apps that haven't run yet don't get the card; they still show only their setup progress.
  • Spend rows with no repository can't occur (usage.repo_id is NOT NULL), so I didn't test that case.
  • I couldn't call keep_plan because that tool wasn't available in this run, so the issue has no plan.

Tests and checks

  • test/money.test.ts works the numbers by hand: month, today, an October day, the 5th, the sum across November, and per-app share of the owned machine. It also covers the board-equals-statements check, the SVG's exact points, the page wiring for an admin and a stranger, and the empty forge.
  • These are green: bun scripts/gate.ts, bun x tsc --noEmit, bun scripts/design-lint.ts, and the board, attention, studio, statement, meter, cost, revenue and economics suites (about 580 tests).
  • scripts/fast.ts flagged the change as too broad (1,933 test files), so I picked those suites myself.

How to try it: as a site admin, set prices in Settings → Economics, then open /board. Open an app's page to see its month card.

🤖 Generated with Claude Code

Closes #510

commented

Site admins now see the money line at the top of /board, under the header bar. It reads "This month · cost · revenue · gap · today's gap · Slider N". The gap uses the accent colour when positive and the warning tint when negative. "Slider N" links to /settings/economics. Next to the line is a server-built SVG of the last 30 UTC days: daily cost and revenue as two lines, with the space between them shaded by whether that stretch made or lost money. No chart library. An app's page now has a "This month" card for its admins: cost, revenue (linking to the statement), gap, and awake hours split into owned and cloud.

What to look at

  • One sum, not two: the board's revenue is the sum of the apps' statements, to the cent, and a test checks that. readStatement's inputs now come from monthUse/appUse/ratesOf in src/actions/statement.ts. statementFor now uses a new chargeOf (src/core/revenue.ts), which works for any time range. The board adds up the same values.
  • Cost: costOf over the month so far. For one day, it is costOf over that day.
  • One read for all 30 days: usageBetween (src/core/meter.ts) is now the one-range case of a new usageBins, so each day's numbers equal usageBetween for that day; a test compares them. The crew spend for all 30 days comes from one new query, usageDays (src/core/usage.ts), which uses the existing usage_at index.
  • Per-app cost includes an assumption, printed on the card: the app pays its own cloud time, storage and crew spend in full. It also pays a share of the owned machine's monthly cost equal to its share of the forge's owned awake time this month.
  • No layout shift: the line is always one line and the chart is a fixed 240 × 48, zeros included. A test checks the empty-forge case.
  • Who sees it: the forge's line follows the Settings → Economics rule (a site admin who is a person, not an agent). An app's card follows the statement rule (admins of that app). Everyone else gets not_found, and a test checks this.
  • Files: the logic is in src/actions/money.ts, and the line and chart are drawn in src/web/studio/money_line.ts. The CSS is in src/web/style/content.ts; the new classes are listed in docs/design/areas/chart.md and the design is described in docs/PROFIT.md.

What I chose not to do

  • No MCP tool or API endpoint: those are #511.
  • No terminal screen, because no new page was added.
  • Fresh apps that haven't run yet don't get the card; they still show only their setup progress.
  • Spend rows with no repository can't occur (usage.repo_id is NOT NULL), so I didn't test that case.
  • I couldn't call keep_plan because that tool wasn't available in this run, so the issue has no plan.

Tests and checks

  • test/money.test.ts works the numbers by hand: month, today, an October day, the 5th, the sum across November, and per-app share of the owned machine. It also covers the board-equals-statements check, the SVG's exact points, the page wiring for an admin and a stranger, and the empty forge.
  • These are green: bun scripts/gate.ts, bun x tsc --noEmit, bun scripts/design-lint.ts, and the board, attention, studio, statement, meter, cost, revenue and economics suites (about 580 tests).
  • scripts/fast.ts flagged the change as too broad (1,933 test files), so I picked those suites myself.

How to try it: as a site admin, set prices in Settings → Economics, then open /board. Open an app's page to see its month card.

🤖 Generated with Claude Code

commented

Site admins now see the money line at the top of /board, under the header bar. It reads "This month · cost · revenue · gap · today's gap · Slider N". The gap is in the accent colour when positive and the warning tint when negative, and "Slider N" links to /settings/economics. Beside it is a server-built SVG of the last 30 UTC days: daily cost and revenue as two lines, with the space between them shaded by whether that stretch made or lost money. No chart library. An app's page now has a "This month" card for its admins: cost, revenue (linking to the statement), gap, and awake hours split into owned and cloud.

Fix in this push: the check was red on the previous push because /board ran 99 SQL statements against its budget of 64 in test/pages-budget.test.ts. The red was hidden because the follow-up message quoted the deploy run, which had passed. The money line was reading the eight economics settings one key at a time, several times per page. A new getEconomicsAll (src/settings/economics.ts) reads them in one IN (…) query, and the money line and ratesOf now use it. That brings /board down to 70. The other two failures in that check, both in test/web-code.test.ts, pass here and this pull doesn't touch them; the check machine was at 17% free disk.

What to look at

  • The budget went up, on purpose: /board moves from 64 to 70, with the reason written next to it. The six reads are one settings read, then the meter, storage and ledger reads for the month and for the 30 days. The count stays the same however many apps there are. To go lower, the month would have to be added up from the daily figures, and then the board's revenue would no longer come from the same usageBetween the statement uses.
  • One sum, not two: the board's revenue is the sum of the apps' statements, to the cent, and a test checks that. readStatement's inputs now come from monthUse/appUse/ratesOf in src/actions/statement.ts. statementFor uses a new chargeOf (src/core/revenue.ts), which works for any time range.
  • One read for all 30 days: usageBetween (src/core/meter.ts) is now the one-range case of a new usageBins, and a test checks that each day's numbers equal usageBetween for that day. The crew spend for all 30 days comes from one new query, usageDays (src/core/usage.ts).
  • Per-app cost includes an assumption, printed on the card: the app pays its own cloud time, storage and crew spend in full. It also pays a share of the owned machine's monthly cost equal to its share of the forge's owned awake time this month.
  • No layout shift: the line is always one line and the chart is a fixed 240 × 48, even when every value is zero.
  • Who sees it: the forge's line is for a site admin who is a person, not an agent (the same rule as Settings → Economics). An app's card follows the statement rule (admins of that app). Everyone else gets not_found.

What I chose not to do

  • No MCP tool or API endpoint; those are #511.
  • No terminal screen, because no new page was added.
  • Fresh apps that haven't run yet don't get the card.

Tests

  • test/money.test.ts works the numbers out by hand and checks them.
  • A new test in test/economics.test.ts checks that getEconomicsAll is one statement and gives exactly what the single-key reads give, including an out-of-range stored value falling back to its default.
  • Green here: bun scripts/gate.ts, bun x tsc --noEmit, test/pages-budget.test.ts, test/web-code.test.ts, and the 43 suites that read economics, statements, money or the board (348 tests).
  • scripts/fast.ts named 1,933 files for this pull, too many to run here, so I picked those suites myself.

Try it: as a site admin, set prices in Settings → Economics, then open /board. Open an app's page to see its month card.

🤖 Generated with Claude Code

commented

On /board, site admins now get a money line under the bar. It shows this month's cost, revenue and the gap between them, today's gap, a slider, and the last 30 days drawn as server-rendered SVG. Each app's page gets a month card with the same figures for that app. The line reads the economics settings once per request, which brings /board down from 99 SQL statements to 70.

Fix for the failed check. This branch merges current main. The failing test, "each return of the public door and of answered is floored", was already failing on main without this pull:

  • c73e1f6f7 (#10975) changed the line in answered to .then((r) => securityFloor(resourcePolicy(request, r))).
  • ba38efcd5 (#10954), merged at about the same time, added a test that expects the exact old text, .then(securityFloor).

The test was wrong, not the code: every answer still goes through securityFloor. The test now accepts either form, so it still fails if the compressed answer stops going through securityFloor.

Checks run here. scripts/gate.ts, tsc --noEmit and design-lint pass. scripts/fast.ts named 1,942 test files (about 165 minutes), over its 5-minute budget, so I picked the ones that cover this pull myself. Those 12 files pass, 93 tests: board, board timing, economics, meter, money, page budget, revenue, statements, usage, and the floor-doors test. The rest of the suite is left to the forge's check.

Not done. The money line has no CSV/JSON export or print view yet.

To try it: as a site admin, open /board on the preview, then open any app's page to see its month card.

🤖 Generated with Claude Code

commented

On /board, site admins now get a money line under the bar. It shows this month's cost, revenue and the gap between them, today's gap, a slider, and the last 30 days drawn as server-rendered SVG. Each app's page gets a month card with the same figures for that app. The line reads the economics settings once per request, which brings /board down from 99 SQL statements to 70.

Merge with main. main now fixes the same line in test/security-floor-doors.test.ts that this branch had fixed: the check that answered passes the compressed answer through securityFloor. The conflict resolves to main's version, so the pull no longer changes that test. The money line's code (src/actions/money.ts, src/web/studio/money_line.ts, and the meter, revenue, usage and statement changes) is unchanged by the merge.

Checks run here. tsc --noEmit and scripts/gate.ts pass. The tests for the floor doors, money, board, economics and page budget pass: 5 files, 59 tests. The rest of the suite is left to the forge's check.

Not done. The money line has no CSV/JSON export or print view yet.

To try it: as a site admin, open /board on the preview, then open any app's page to see its month card.

🤖 Generated with Claude Code

Changes requested — before the latest push

Good work on the arithmetic: one chargeOf, one usageBins, hand-worked tests, and a fixed 240x48 slot. Changes needed, all in one round:

  1. FIRST: both doors. Nothing in src/tui/ shows the money line or an app's month, and there is no docs/features/<name>.md; the how-to went into docs/PROFIT.md. 'No new page was added' does not exempt a new read a person can make on the page. Add a TUI screen (entry where AGENTS.md says), reading readForgeMoney/readAppMoney with the same gates, and docs/features/money.md. Keep PROFIT.md to the design.
  2. Design principle 5 (accent for interaction alone): the gap is painted --accent when positive, and the band fill uses --accent too, while 'Slider N' is a link in the accent. That is two meanings of the accent on one page. Show the sign with the text colour plus the +/- already there, and the --attn tint for a loss only; keep the accent for the Slider link.
  3. Design principle 1 and 8: 'Slider 62', 'owned box' and 'Cost carries 100% of the owned box' are jargon, and the app card's note explains what the dl already says. Say it plainly, e.g. 'Economics slider: 62', and cut the note to one sentence or drop it.
  4. Phone (360px): the issue and the docs promise 'always one line', but the <p> has six segments and wraps on a phone, so its height is not reserved (min-height 24px only covers one line). Reserve the real height (e.g. min-height for the wrapped case at <=480px) or let today's gap drop under the line deliberately and size for it.
  5. Principle 3/6: the 'about' card wrapper around a dl is fine, but check the board slot adds no border or box around the chart; keep it plain.
  6. Tests: the test uses a dynamic import inside a test; import it at the top. Add a test that the app's page card is absent for a fresh app (stated behaviour), and a cost-side test that the board cost equals the sum of per-app costs plus the unowned share, not only revenue.
  7. Direction: /board budget raised 64 to 70 and the meter.ts core function was rewritten for a feature while the direction says pipeline first; keep that rewrite as small as possible and say in the pull why the budget bump is not avoidable.

Boxes: 1, 2, 3, 4, 5 are met in the head and ticked; the review is blocked on the both-doors, accent and phone items above.

commented

For site admins, /board now opens on a money line: this month's cost, revenue and the gap between them, today's gap, and the economics slider's position as a link. Beside it is a 30-day chart drawn as server-rendered SVG. An app's page gets a "This month" card for its admins with the same three numbers. The same figures are now in the terminal, as review asked.

Both doors. src/tui/screens/money.ts adds a Money palette entry for site admins and an owner/name — Money command while a repository is open. Both read readForgeMoney/readAppMoney, so the gates are the same as on the web: someone who may not read the figures is not offered the screen and, if they reach it, sees no figures. How to use it is now in docs/features/money.md; docs/PROFIT.md keeps only the design.

Accent. The gap is in the text colour with its + or − sign, and only a loss takes --attn. The chart's profit band is a faint text-colour wash. The accent is left for the slider link.

Plain words. The board says "This month: cost … · revenue … · gap …" and "Today: gap … · Economics slider: 62". The app card says "on the forge's own server" / "on cloud", and its note is one sentence about its share of that server.

Phone. The line is two rows of 24px: the month, then today and the slider. 48px is reserved, and 72px at 480px and under, where the first row wraps once.

Chart box. The chart has no border or box around it.

Tests.

  • The dynamic import is now at the top of the file.
  • A fresh app's page has no card.
  • Two tests check the cost side by hand: with all owned time shared by apps, the forge's cost equals the sum of the apps' costs. With no app awake on the forge's own server, the forge carries the server's whole month.
  • test/tui-money.test.ts covers the terminal rows, the two commands and the gates.

Where I disagree. Review asked for the cost test to include crew spend on no repository, but every crew-spend record must name a repository, so that term can't occur. I dropped it from the test and the docs.

Why /board costs 6 more statements and meter.ts changed.

  • The line needs the settings, the month's meter, storage and crew-spend reads, and the 30 days' meter, storage and crew-spend reads. That count does not grow with the number of apps.
  • Calling usageBetween once per day would add about 60 statements, not 6.
  • Adding usageBins next to an unchanged usageBetween would mean two copies of the same sum. Instead usageBetween is now the one-range case of usageBins, and a test checks that each day's figures match usageBetween for that day.
  • Building the month out of the day figures would save one meter pass. But it adds the seconds in a different order, which can move a statement total by a cent, and then the board's revenue would no longer equal the sum of the apps' statements.

Checks run here. gate.ts, tsc --noEmit and design-lint pass. The tests for money, the terminal screen, statements, meter, revenue, economics, board, page budget, floor doors, both doors, palette, attention, design and docs pass. The rest of the suite is left to the forge's check.

Not done. The money line has no CSV/JSON export or print view.

To try it: as a site admin, open /board; in the terminal, press : and pick Money; with a repository open, pick owner/name — Money.

🤖 Generated with Claude Code

commented

Site admins now see the money line at the top of /board, under the header bar: "This month · cost · revenue · gap · today's gap · Slider N". "Slider N" links to /settings/economics. Beside it is a server-drawn SVG of the last 30 UTC days, no chart library: daily cost and revenue as two lines, with the space between them shaded by whether that stretch made or lost money. An app's page has a "This month" card for its admins: cost, revenue (linking to the statement), gap, and awake hours split into owned and cloud. The terminal palette has a matching Money screen.

What to look at

  • One sum, not two: the board's revenue is the sum of the apps' statements, to the cent, and a test checks that. readStatement and the board both get their figures from monthUse/appUse/ratesOf in src/actions/statement.ts. The charge comes from chargeOf in src/core/revenue.ts, which works over any time range.
  • Cost: costOf over the month so far, and over one day for the daily figure.
  • One read for all 30 days: usageBetween in src/core/meter.ts is now the one-range case of a new usageBins. A test checks that each day's bin equals usageBetween for that day.
  • Economics settings read once per request: /board drops from 99 SQL statements to 70.

Test changes

  • test/palette-roster.test.ts lists every palette entry, so it now includes the new money entry. The code was right; the list was out of date.
  • test/traffic-truth.test.ts failed three tests in the last check. They pass here and nothing in this branch touches that code. My best explanation, not reproduced: the test got each commit id by spawning git rev-parse on a hard-coded path and ignored any failure, and an empty id breaks exactly those three tests. It now takes the id that commitToBranch returns and stops if it isn't a full id. That fixes the test, not the code.

Checks run here

  • bun scripts/gate.ts and bun x tsc --noEmit pass.
  • After merging main, scripts/fast.ts named 2040 test files, too many to run, so I ran the ones that cover this branch: money, tui-money, economics, pages-budget, traffic-truth and palette-roster, 54 tests, all passing.

Not done: no forecast or alerting on the gap; the line only reports what has happened.

Try it: as a site admin, open /board. The money line and its 30-day chart sit under the bar. Then open an app's page to see its month card.

🤖 Generated with Claude Code

Approved

Clean. One formula (chargeOf) and one meter pass (usageBins) feed both the statement and the board, and the tests work the numbers by hand. Both doors exist (src/tui/screens/money.ts, docs/features/money.md, with the same gates as the page). Reads are gated to a site admin who is a person, and to the app admins for an app. No new dependency. The accent is kept for the slider link, so principle 5 holds. The 240x48 chart and the two reserved rows keep the slot from shifting. Note for later, not blocking: the board now does a 30-day meter pass on every load with no cache. Watch it as the meter table grows. Box 1 says the gap takes the accent when positive. The head deliberately uses the text colour plus the sign, because DESIGN.md principle 5 reserves the accent for interaction, so I mark that box left.

commented

Left #510 open: 1 unticked item remain (one line on the board: "This month · cost $359.34 · revenue $412.00 · +$52.66" with the gap in the accent when positive and the warning tint when negative; the slider's position beside it as a link to Settings → Economics).

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs