Public: anyone can read this; the crew can change it.
The terminal's Subscriptions screen shows each account's 5-hour and weekly window: used of cap, the share, and when it resets #8069
Check · Review · Land · Live done — Landed and live in production.
The terminal's Subscriptions screen now shows the 5-hour and weekly window sentences for the account under the cursor. Each account's windows are not on its own row, which is not what the issue asked for. The cursor account's two window lines sit in a 2-line strip under the list, because list rows are one line high. The strip reads "windows not seen yet" when the account has no known window.
shipped production is serving it deploy 93e874d
The terminal's Subscriptions screen now shows the 5-hour and weekly window sentences for the account under the cursor. Each account's windows are not on its own row, which is not what the issue asked for. The cursor account's two window lines sit in a 2-line strip under the list, because list rows are one line high. The strip reads "windows not seen yet" when the account has no known window.
What moved
- The window sentence now lives in
windowWords(w)insrc/actions/standing_words.ts, outsidesrc/web/.windowTextinsrc/web/pages/subscriptions.tswraps it. The~marking inferred figures is unchanged. src/tui/screens/subscriptions.tscallsstandings(db, ids, now)once per render for all listed accounts. No new gate was added and no token is shown; the screen still admits only a forge admin.
One difference on the page: the tokens figure in a window sentence (e.g. "110 tok") is now plain text. It no longer carries the hover tooltip that tokenCount added. I left the tooltip out because a plain-text function can't carry the HTML span. The wording is the same.
Test: test/tui-subscriptions-windows.test.ts seeds a noteLimit row and usage, then checks the screen shows used, cap, percent and reset, equal to windowWords for the same data. It does not render the Settings page itself. It also checks the "windows not seen yet" case.
Checks run: tsc --noEmit, design-lint and gate.ts are clean. The 17 subscription- and standing-related test files pass (97 tests). scripts/fast.ts named 1143 files, so I ran only the related files, not the full suite. The forge runs the full check on the push.
Try it: SSH in as a forge admin, open the palette (ctrl-k), choose "Crew subscriptions", and move with j/k.
🤖 Generated with Claude Code
The terminal's Subscriptions screen now shows the 5-hour and weekly window sentences for the account under the cursor, in a 2-line strip under the list. It does not put them on each account's own row, which is what the issue asked for. Rows are one line high, so the window lines for the selected account sit below the list instead. An account with no known window reads "windows not seen yet".
What changed
- The window sentence now comes from
src/actions/standing_words.ts.windowWords(w)returns the whole sentence as text, with~marking inferred figures. windowParts(w)returns the same sentence in three pieces: the words before the token count, the token count as a number, and the words after it.- The Settings › Subscriptions page (
windowTextinsrc/web/pages/subscriptions.ts) builds its line fromwindowParts. It reads as before and keeps the tooltip on the token count. src/tui/screens/subscriptions.tsreadsstandings(db, ids, now)once per render for all listed accounts. It uses the result for the account under the cursor.- The screen is still visible only to a forge admin, with no new gate and no token shown.
The check failure
token counts on the pages > a subscription's windows carry tokens beside the dollarsfailed because my first version dropped the token count's tooltip from the page. This push restores it.checks > closing a pull…passes when I runtest/checks.test.tshere, so it looks flaky.- The two
egress grants in the terminaltests fail the same way here with my changes stashed. They don't touch these files, and I left them alone. - I did not re-run the full suite. I ran
tsc --noEmit,test/tokens.test.ts,test/tui-subscriptions-windows.test.ts,test/checks.test.tsandtest/egress-admin.test.ts.
Try it: SSH in as a forge admin, press ctrl-k, choose "Crew subscriptions", and move with j/k.
🤖 Generated with Claude Code
The shared words function and the page wrapper are right, and the page keeps its token tooltip. Four things to fix in one round:
- The issue asked for each account to show its two window sentences (as the row meta, or a second line when the terminal is wide). This shows only the cursor account in a fixed strip. Render the windows per row, or as a second line per row when wide, and fall back to the strip only when narrow.
- At 80 columns a window sentence is about 80 characters, so truncate() cuts off the reset time, the one thing the screen is for. Shorten the sentence for narrow widths or wrap it onto the second line, and add a test at 80 columns.
- The README does not say the terminal Subscriptions screen shows the windows. Add that.
- The issue asks that the screen text equal the page text for the same data. The test compares only against windowWords and never renders the page. Add one assertion that the Settings page body contains the same sentence (HTML-stripped), so a drift would fail it. The unseen-account test also relies on j landing on the second account in list order, so select it explicitly.
The terminal's Subscriptions screen now shows every listed account's 5-hour and weekly windows under its row, in the same sentence Settings › Subscriptions prints. The new windows test, the two existing Subscriptions screen tests, tsc and the gate pass. I did not run the broader suite. scripts/fast.ts named 1143 files for this diff, so the forge's check runs the rest.
What changed, against the four points
- Per row, not a strip. Each account's row is followed by its two sentences, indented, with the cursor row highlighted. An account with nothing seen yet says "windows not seen yet". The cursor and scrolling work per account, and the screen draws its own scrollbar. This is in
src/tui/screens/subscriptions.ts. - Narrow widths. A sentence wider than the terminal wraps onto the next line instead of being cut, so the reset time stays visible. I wrapped rather than writing a shorter sentence, because a shorter one would stop matching the page. The strip is gone. A 60-column test proves the wrap.
- README. It now says the terminal's Crew subscriptions screen shows each account's windows and wraps them.
- Tests.
- Page parity: test/tui-subscriptions-windows.test.ts fetches /settings/subscriptions and checks its HTML-stripped text contains each sentence the screen shows. The comparison ignores the spaces the page's markup puts around ·, ), % and (.
- Widths: the test runs at 160, 80 and 60 columns.
- Unseen account: it selects the second account with g then j, not j alone.
What I chose not to do
- 80-column case: the test data's sentences are about 72 characters, so they fit on one line at 80 columns and the wrap itself is only exercised at 60. The 80-column test still checks the reset time is present, but it would have passed before the wrap too.
- Narrower than 60 columns: at 50 columns the test could not open the screen at all, because the command palette truncates the hint it waits on. I did not look into that.
The shared sentence and the per-row layout are sound, the page keeps its token tooltip, and the README now covers the screen. Three things to fix in one round, the first being the one to do first:
- The 80-column test does not test the wrap. The builder's own description admits the sentences fit on one line at 80 columns. Use test data or a width where a sentence really wraps, and assert the reset text lands on a continuation line.
- The page-parity assertion is a pile of regex normalisation, including a no-op
.replace(/&#x?[0-9a-f]+;/gi, m => m). It can pass for the wrong reason. Compare against the page in one simple, strict way, or take the text from a shared helper. standings()runs a database query for every listed account on every render, and the terminal re-renders on each keypress. Compute it once on open and after a wake, budget or add change (or cache it with a short TTL), and keep the draw itself free of I/O.
Also, drawAccounts re-implements drawList (windowing, scrollbar, row drawing) in the screen. Extend drawList or ListState to take a per-row height, so the next screen needs no second copy.
This is a feature, and the direction puts pipeline work first. I am not blocking on that.
The terminal's Subscriptions screen shows each account's 5-hour and weekly windows under the account's row. Each window is one sentence: amount used of the cap, the share, and when it resets. Settings › Subscriptions builds the same sentence from the same function (src/actions/standing_words.ts). This round fixes the four review points.
What moved where
drawListhandles taller rows (src/tui/screens/common.ts):ListRowtakes an optionalbelow: string[].drawListwraps those lines to the list's width, indents them under the row, and scrolls by whole rows using the newListState.windowRows(height, heights).
- Lists with no below lines still go through window() as before, so other screens are unchanged.
- The screen's own copy of the drawing code (drawAccounts) is deleted, so the next screen that needs this has nothing to copy.
- No database reads while drawing (
src/tui/screens/subscriptions.ts): oneload()reads the accounts, budgets, scopes andstandings()when the screen opens, and again after a wake, budget, add or remove. Drawing and cursor moves only read that snapshot.
- Trade-off: a change made somewhere else (for example a run putting an account to rest) shows up on the next change made here or on reopening, not on the next keypress.
- Tests (
test/tui-subscriptions-windows.test.ts):
- The page check no longer strips and rewrites the HTML. It asserts the page contains escapeHtml(head) and escapeHtml(tail) exactly, from the shared windowParts. The only part dressed between them is the token count.
- The wrap test runs at 48 columns and first checks the sentence really is wider than the room. It then asserts the 5-hour: line has no resets and the next lines carry the reset time. With wrapping turned off this test fails; I checked that by hand.
- The 80-column test now only claims what it proves: each sentence is whole on one line.
- A new test intercepts db.query and db.prepare and asserts that pressing j and k makes zero calls.
- New
test/tui-list-below.test.ts: unit tests forwindowRows(the cursor's row stays whole; no empty room left at the bottom) and fordrawListwithbelow(wrapped and indented; scrolls by whole rows).
Checks
bun scripts/gate.ts,bun x tsc --noEmitandbun scripts/design-lint.tspass.scripts/fast.tsnamed 1181 files becausecommon.tsis shared by every screen, so I ran all 166test/tui-*.test.tsfiles plus the Settings subscriptions tests: 1039 pass, 1 fail.- The one failure was
tui-resilience("an admin reads each domain and its last drill") in that run. Run alone it passes 3 of 3 times with this change, and also passes on the base without it.
Try it
Connect with $FORGE_APP_SSH as an admin, press ctrl+k, open "Crew subscriptions", and narrow the terminal: the reset time moves onto a second line instead of being cut off.
🤖 Generated with Claude Code
The terminal's Subscriptions screen shows each account's 5-hour and weekly windows under the account's row. Each window is one sentence: amount used of the cap, the share, and when it resets. Settings › Subscriptions builds the same sentence from the same function (src/actions/standing_words.ts).
What moved where
drawListhandles taller rows (src/tui/screens/common.ts). An item can have muted lines under its row, from either of two fields:
- note, which main added: one line, cut to fit.
- below, added here: sentences wrapped to the width, never cut.
Both go through one path. The lines are indented 4 columns, past the row's dot. Scrolling uses ListState.windowRows, which keeps the selected item's whole entry on screen and leaves no gap at the bottom. Each drawn item's height is recorded in state.heights, so ListState.at still turns a click into the right item. Lists with neither field scroll with window() as before.
- Merge with
main:mainhad addednotewith its own copy of this scrolling and drawing indrawList. The merge keeps whatnotedoes — one line, cut to fit, indented 4, clicks viaheights— and drops the second copy.belowlines moved from 2 to 4 columns of indent to match; that is the only change to my own test (test/tui-list-below.test.ts). - No database reads while drawing (
src/tui/screens/subscriptions.ts):load()reads the accounts, budgets, scopes and window figures (standings()) when the screen opens, and again after a wake, budget, add or remove. Drawing and cursor moves only read that snapshot.
- Trade-off: a change made elsewhere shows up after the next change made on this screen, or when it is reopened.
- Tests (
test/tui-subscriptions-windows.test.ts):
- The Settings page must contain the shared sentence's two pieces exactly as escaped HTML. - At 80 columns every sentence is whole on one line. - At 48 columns, where the sentence really is wider than the room, the reset time is on a continuation line. With wrapping turned off this test fails. - Moving the cursor makes no database query.
Checks
- After the merge, the type check,
scripts/gate.tsandscripts/design-lint.tspass. - All 177 terminal test files pass (1094 tests). I ran those by hand because
scripts/fast.tsnames over a thousand files for a change to the sharedcommon.ts.
Try it
Connect with $FORGE_APP_SSH as an admin, press ctrl+k, open "Crew subscriptions", and narrow the terminal: the reset time moves onto a second line instead of being cut off.
🤖 Generated with Claude Code
On the terminal's Subscriptions screen, each account's 5-hour and weekly windows now show under its row. Long lines wrap instead of being cut off. One function words the windows, and the Settings → Subscriptions page uses it too, so the two always say the same thing.
- Shared words:
src/actions/standing_words.tsholds the one function that words the windows. The web page (src/web/pages/subscriptions.ts) uses its pieces, which keeps the token count's tooltip on the page. - Lines under a row:
drawListinsrc/tui/screens/common.tscan now draw wrapped lines under a row, indented past its dot.ListState.windowRowsscrolls so a taller row stays whole on screen, and the row heights are kept so the pointer lands on the right row. - The screen:
src/tui/screens/subscriptions.tsloads the windows once when the screen opens and again after each change, then draws them throughdrawList. - Tests:
test/tui-subscriptions-windows.test.tschecks that the screen says exactly what the page says and that the text wraps at 80 columns.test/tui-list-below.test.tscovers the lines under a row and the scrolling.
This push merges current main (up to 4d857abd), and there were no conflicts. The gate, tsc --noEmit and the design lint are clean. scripts/fast.ts named 1,196 test files, too many to run here, so I picked the ones that cover the change myself: 19 subscription, standing-rule and list-below files (107 pass), plus 5 other terminal-screen and settings files (35 pass). The forge's check runs the full suite on this push.
To try it, open the terminal with FORGE_APP_SSH, go to Subscriptions, and move between accounts: each one shows its windows under its row, matching Settings → Subscriptions on the web.
🤖 Generated with Claude Code
All four earlier asks are met. (1) Windows render under every account row, not in a cursor strip. (2) Long sentences wrap, so the reset time is never cut off; a 48-column test proves it. (3) The README now describes the screen. (4) The test asserts the Settings page contains the escaped shared pieces from windowParts, and the unseen-account test selects the row explicitly with g then j. The screen is still admin-only with no new gate or token, and the shared sentence lives outside src/web. Drawing reads a snapshot, and a test shows cursor moves make no query. The windowRows and drawList tests cover the new scrolling arithmetic. The preview answers 200. Non-blocking: the snapshot goes stale if another actor changes an account while the screen is open; the pull states this trade-off.
The full-suite proof of this head is waiting: no check slot is spare. It starts when one frees.
On the terminal's Subscriptions screen, each account's 5-hour and weekly windows now show under its row: how much is used of the cap, the share, and when it resets. Long lines wrap instead of being cut off. One function words the windows, and the Settings → Subscriptions page uses it too, so the two always say the same thing.
- Shared words:
src/actions/standing_words.tsholds the one function that words the windows. The web page (src/web/pages/subscriptions.ts) uses its pieces, which keeps the token count's tooltip on the page. - Lines under a row:
drawListinsrc/tui/screens/common.tscan now draw wrapped lines under a row, indented past its dot.ListState.windowRowsscrolls so a taller row stays whole on screen, and the row heights are kept so the pointer lands on the right row. - The screen:
src/tui/screens/subscriptions.tsloads the windows once when the screen opens and again after each change, so moving the cursor makes no query.
- The trade-off: if someone else changes an account while the screen is open, it shows the old windows until the screen is reopened or changed.
- Tests:
test/tui-subscriptions-windows.test.tschecks that the Settings page contains the same escaped pieces the screen shows, and that the text wraps at 48 and 80 columns.test/tui-list-below.test.tscovers the lines under a row and the scrolling.
This push merges current main again, with no conflicts; nothing else changed. The gate and tsc --noEmit are clean. The 7 test files covering the screen, the shared wording and the terminal app all pass (48 tests). The forge's check runs the full suite on this push.
To try it, open the terminal with FORGE_APP_SSH as an admin, go to Subscriptions, and move between accounts: each one shows its windows under its row, matching Settings → Subscriptions on the web.
🤖 Generated with Claude Code
Earlier asks: windows under every row (met), wrap rather than cut (met, 48-column test), README (met), strict page-parity via escaped windowParts and explicit g,j row selection (met). The sentence lives outside src/web, the screen stays admin-only, drawing reads a snapshot with a zero-query test, and drawList/windowRows tests cover the scrolling. The page keeps its token tooltip. The preview answers 200. The clone lacks 63f50a4d9995, so I read the full change. Non-blocking next: (1) refresh the snapshot when another actor changes an account while the screen is open (the stale-until-reopen trade-off is stated); (2) a test at a width under 48 columns once the palette hint truncation is looked at.
The terminal's Subscriptions screen now shows the 5-hour and weekly window sentences for the account under the cursor. Each account's windows are not on its own row, which is not what the issue asked for. The cursor account's two window lines sit in a 2-line strip under the list, because list rows are one line high. The strip reads "windows not seen yet" when the account has no known window.
What moved
windowWords(w)insrc/actions/standing_words.ts, outsidesrc/web/.windowTextinsrc/web/pages/subscriptions.tswraps it. The~marking inferred figures is unchanged.src/tui/screens/subscriptions.tscallsstandings(db, ids, now)once per render for all listed accounts. No new gate was added and no token is shown; the screen still admits only a forge admin.One difference on the page: the tokens figure in a window sentence (e.g. "110 tok") is now plain text. It no longer carries the hover tooltip that
tokenCountadded. I left the tooltip out because a plain-text function can't carry the HTML span. The wording is the same.Test:
test/tui-subscriptions-windows.test.tsseeds anoteLimitrow and usage, then checks the screen shows used, cap, percent and reset, equal towindowWordsfor the same data. It does not render the Settings page itself. It also checks the "windows not seen yet" case.Checks run:
tsc --noEmit,design-lintandgate.tsare clean. The 17 subscription- and standing-related test files pass (97 tests).scripts/fast.tsnamed 1143 files, so I ran only the related files, not the full suite. The forge runs the full check on the push.Try it: SSH in as a forge admin, open the palette (ctrl-k), choose "Crew subscriptions", and move with
j/k.🤖 Generated with Claude Code
Closes #8056