Public: anyone can read this; the crew can change it.
Each subscription in the /boards windows strip is a plain row with its meters, not a boxed card link #10022
Check · Review · Land · Live done — Landed and live in production.
scout/forge:scout/10010-each-subscription-in-the-boards-windows → main 3 commits fork 2 threads · 2 unresolved
2 files
changed +35
−28
src/web/pages/board.ts +26 −28
| @@ -608,46 +608,44 @@ function windowsStrip(ctx: Ctx): Html | null { | ||
| 608 | 608 | id === PLATFORM |
| 609 | 609 | ? "every app no subscription is granted to" |
| 610 | 610 | : (scopes.get(id) ?? []).join(", "); |
| 611 | const card = (s: Subscription): Html | null => { | |
| 611 | const row = (s: Subscription): Html | null => { | |
| 612 | 612 | const st = all.get(s.id); |
| 613 | 613 | if (st === undefined) return null; |
| 614 | 614 | return html`<a |
| 615 | class="card" | |
| 615 | class="bar small muted" | |
| 616 | 616 | href="/settings/subscriptions" |
| 617 | 617 | data-key="sub:${s.id}" |
| 618 | 618 | > |
| 619 | <span class="card-body bar small muted"> | |
| 620 | <b>${s.name}</b> | |
| 621 | ${st.windows.map((w) => { | |
| 622 | const words = windowWords(w); | |
| 623 | return html`<span class="bar" data-window="${w.window}" | |
| 624 | >${words.name} | |
| 625 | ${ | |
| 626 | words.usedPct === null | |
| 627 | ? null | |
| 628 | : meter( | |
| 629 | words.usedPct, | |
| 630 | `${words.name} used`, | |
| 631 | words.usedPct >= 100 ? "attn" : undefined, | |
| 632 | ) | |
| 633 | } | |
| 634 | <span class="num" | |
| 635 | >${figures([words.used, words.remaining, words.resets])}${when( | |
| 636 | w.tokensIn + w.tokensOut > 0, | |
| 637 | () => html` · ${tokenCount(w.tokensIn + w.tokensOut, "counted")}`, | |
| 638 | )}</span | |
| 639 | ></span | |
| 640 | >`; | |
| 641 | })} | |
| 642 | ${when(scopeWords(s.id) !== "", () => html`<span>for ${scopeWords(s.id)}</span>`)} | |
| 643 | </span> | |
| 619 | <b>${s.name}</b> | |
| 620 | ${st.windows.map((w) => { | |
| 621 | const words = windowWords(w); | |
| 622 | return html`<span class="bar" data-window="${w.window}" | |
| 623 | >${words.name} | |
| 624 | ${ | |
| 625 | words.usedPct === null | |
| 626 | ? null | |
| 627 | : meter( | |
| 628 | words.usedPct, | |
| 629 | `${words.name} used`, | |
| 630 | words.usedPct >= 100 ? "attn" : undefined, | |
| 631 | ) | |
| 632 | } | |
| 633 | <span class="num" | |
| 634 | >${figures([words.used, words.remaining, words.resets])}${when( | |
| 635 | w.tokensIn + w.tokensOut > 0, | |
| 636 | () => html` · ${tokenCount(w.tokensIn + w.tokensOut, "counted")}`, | |
| 637 | )}</span | |
| 638 | ></span | |
| 639 | >`; | |
| 640 | })} | |
| 641 | ${when(scopeWords(s.id) !== "", () => html`<span>for ${scopeWords(s.id)}</span>`)} | |
| 644 | 642 | </a>`; |
| 645 | 643 | }; |
| 646 | 644 | const crew = pool.filter((s) => s.id === PLATFORM || scopes.has(s.id)); |
| 647 | 645 | const apart = pool.filter((s) => s.id !== PLATFORM && !scopes.has(s.id)); |
| 648 | 646 | const group = (name: string, key: string, subs: readonly Subscription[]): Html => |
| 649 | 647 | html`<div class="stack stack-tight" data-group="${key}"> |
| 650 | <span class="muted small">${name}</span>${subs.map(card)} | |
| 648 | <span class="muted small">${name}</span>${subs.map(row)} | |
| 651 | 649 | </div>`; |
| 652 | 650 | return html`<div |
| 653 | 651 | class="stack" |
test/board-windows.test.ts +9
| @@ -307,6 +307,15 @@ describe("efficiency and standing, grouped", () => { | ||
| 307 | 307 | expect(page).toContain("account 1"); |
| 308 | 308 | expect(page).toContain("5-hour window"); |
| 309 | 309 | expect(page).toContain("weekly window"); |
| 310 | // A plain row per subscription, never a boxed card, inside the strip. | |
| 311 | const strip = page.slice(page.indexOf('id="windows"'), page.indexOf('id="spend-detail"')); | |
Add an assertion for data-group="apart" (seed an unscoped credential) so both groups are covered, as box 3 says. |
||
| 312 | expect(strip).not.toContain('class="card'); | |
| 313 | expect(strip).toContain('data-group="crew"'); | |
| 314 | // A credential no app is scoped to sits in its own group. | |
| 315 | expect(strip).toContain('data-group="apart"'); | |
| 316 | expect(strip).toContain('data-key="sub:'); | |
| 317 | expect(strip).toContain('data-window="5h"'); | |
| 318 | expect(strip).toContain('data-window="week"'); | |
| 310 | 319 | expect(page).toMatch(/data-variant="outline"\s*>inferred</); |
| 311 | 320 | expect(page).toContain(">full<"); |
| 312 | 321 | // ($3.00 + $1.50 + $0.50) ÷ 2 merged this week = $2.50. |
Principle 7 is met here, but the surrounding helper is still called
card; rename it torowand fix the indentation of the body below.