From 5f8ea8876878490938be9746a94df8089f03749a Mon Sep 17 00:00:00 2001 From: Vimox Shah <10022243+vimoxshah@users.noreply.github.com> Date: Mon, 7 Sep 2026 19:36:39 +0530 Subject: [PATCH 1/2] fix(ui): size hero values to their card, and let the Overview grid collapse on a phone "Excellent" on the Data health card was set at the hero figure size, which is tuned for numbers; the word is wider than one grid card at that size, so it read "Excelle" with the rest cut off. The Cost hero ("$364.52") and the Peaks hero ("53.3M") had the same defect on narrower windows. A hero value is now sized to the column it sits in with container units, between a readable floor (20px scaled by the theme) and the hero size: 19% of the width fits the longest figure the formatters emit in the widest figure font, 17% fits the longest grade. The grade card takes a double-width slot so the word shows at full size on anything wider than a phone. The 860px rule now lowers the cap instead of pinning the size, so it cannot reintroduce the overflow. The Overview's two-up chart grid asked for 420px columns, which pushed the page 64px past a 380px viewport; the minimum now yields to the available width. A wide card falls back to one column below 480px, and branch names in the Live receipts list wrap instead of spilling out of their column. Verified by rendering all 21 views in the three themes at 1440, 1000, 700 and 380px and probing every card for text past its edge and the page for sideways scroll: none remain. Co-Authored-By: Claude Fable 5.1 --- src/ui/app.js | 10 ++++++---- src/ui/styles.css | 26 +++++++++++++++++++++++--- src/ui/styles/live.css | 2 +- 3 files changed, 30 insertions(+), 8 deletions(-) diff --git a/src/ui/app.js b/src/ui/app.js index e242371..e9f6fc5 100644 --- a/src/ui/app.js +++ b/src/ui/app.js @@ -1172,13 +1172,13 @@ function btn(label, onClick, cls = '', id = null) { function kpi(label, value, sub, opt = {}) { const c = el('div', { class: 'card' + (opt.hero ? ' hero-card' : '') }); - const k = el('div', { class: 'kpi' + (opt.onClick ? ' clickable' : '') }); + const k = el('div', { class: 'kpi' + (opt.hero ? ' kpi-hero' : '') + (opt.onClick ? ' clickable' : '') }); k.appendChild(el('span', { class: 'k-label' }, [ document.createTextNode(label), opt.badge ? el('span', { class: 'badge ' + (opt.badgeKind || ''), text: opt.badge, title: opt.badgeTitle || '' }) : null, ])); k.appendChild(el('span', { - class: 'k-value' + (opt.hero ? ' hero' : '') + (opt.str ? ' str' : ''), + class: 'k-value' + (opt.hero ? ' hero' : '') + (opt.str ? ' str' : '') + (opt.word ? ' word' : ''), text: value, })); if (sub) k.appendChild(el('span', { class: 'k-sub' }, [typeof sub === 'string' ? document.createTextNode(sub) : sub])); @@ -1240,7 +1240,7 @@ function viewOverview() { root.appendChild(compositionCard()); root.appendChild(providerDailyCard()); - const two = el('div', { class: 'grid', style: 'grid-template-columns:repeat(auto-fit,minmax(420px,1fr))' }); + const two = el('div', { class: 'grid', style: 'grid-template-columns:repeat(auto-fit,minmax(min(420px,100%),1fr))' }); two.appendChild(shareCard('provider', 'Provider distribution', v.dimensions.providers, S.colors.provider, 'provider')); two.appendChild(shareCard('model', 'Model distribution', v.dimensions.models, S.colors.model, 'model')); two.appendChild(interfaceCard()); @@ -2593,7 +2593,9 @@ function viewHealth() { root.appendChild(sectionTitle('Data health')); const cards = el('div', { class: 'cards' }); - cards.appendChild(kpi('Data health', h.grade, `${pct(h.missingTokenFieldRate)} of token fields not reported`, { hero: true })); + const gradeCard = kpi('Data health', h.grade, `${pct(h.missingTokenFieldRate)} of token fields not reported`, { hero: true, word: true }); + gradeCard.classList.add('wide'); + cards.appendChild(gradeCard); cards.appendChild(kpi('Records', int(h.records), `${int(h.sourceFiles)} source files tracked`)); cards.appendChild(kpi('Date coverage', h.coverage.from ? `${shortDate(h.coverage.from)} → ${shortDate(h.coverage.to)}` : '—', `${h.coverage.from ? daysBetween(h.coverage.from, h.coverage.to) + 1 : 0} days`)); cards.appendChild(kpi('Providers', int(h.providers), `${int(h.models)} models · ${int(h.clients)} clients`)); diff --git a/src/ui/styles.css b/src/ui/styles.css index b1e6488..9b2b677 100644 --- a/src/ui/styles.css +++ b/src/ui/styles.css @@ -383,8 +383,23 @@ label.fld { display: flex; flex-direction: column; gap: 4px; font-size: 11px; co letter-spacing: var(--tracking-h); line-height: var(--lh-figure); font-family: var(--figure); font-variant-numeric: tabular-nums; } -/* The one hero number on a view: bigger, and tighter still as type grows. */ -.kpi .k-value.hero { font-size: calc(var(--fs-hero) * var(--fig-scale)); letter-spacing: calc(var(--tracking-h) - .01em); line-height: var(--lh-hero); } +/* The one hero number on a view: bigger, and tighter still as type grows. + A hero cannot wrap, and at the full size a 7-character figure ("$364.52") + or the word "Excellent" ran past the edge of a narrow card. So the hero is + sized to the card it sits in, between a readable floor and the hero size: + 19% of the column's width fits the longest figure the formatters emit in the + widest of the three figure fonts, 17% fits the longest grade. The inner kpi + column is the container, not the card, so grid sizing is untouched. A wide + card reaches the hero size; a narrow one shrinks instead of clipping. */ +.kpi.kpi-hero { container-type: inline-size; } +.kpi .k-value.hero { + --hero-cap: calc(var(--fs-hero) * var(--fig-scale)); --hero-k: 19cqi; + font-size: var(--hero-cap); letter-spacing: calc(var(--tracking-h) - .01em); line-height: var(--lh-hero); +} +.kpi .k-value.hero.word { --hero-k: 17cqi; } +@supports (font-size: 1cqi) { + .kpi .k-value.hero, .kpi .k-value.hero.word { font-size: clamp(calc(20px * var(--fig-scale)), var(--hero-k), var(--hero-cap)); } +} /* A long identifier is not a hero figure: keep it readable and let it wrap. */ .kpi .k-value.str { font-size: 17px; font-weight: 600; line-height: 1.25; overflow-wrap: anywhere; } .card.wide { grid-column: span 2; } @@ -669,9 +684,14 @@ dialog h2 { margin: 0; font-size: 15px; } @media (max-width: 860px) { .kpi .k-value { font-size: 21px; } - .kpi .k-value.hero { font-size: 34px; } + .kpi .k-value.hero { --hero-cap: 34px; } .bar-row { grid-template-columns: 96px 1fr 62px; } } +/* Below this the card grid has one column, and a card spanning two would open + an implicit column off the right edge of the page. */ +@media (max-width: 480px) { + .card.wide { grid-column: auto; } +} @media print { header.top { position: static; } diff --git a/src/ui/styles/live.css b/src/ui/styles/live.css index 06a4ae4..1856496 100644 --- a/src/ui/styles/live.css +++ b/src/ui/styles/live.css @@ -68,7 +68,7 @@ } .live-receipt:first-child { border-top: 0; } .live-receipt-repo { font-weight: 600; overflow-wrap: anywhere; } -.live-receipt-where p, .live-receipt-where .hint { margin: 0; } +.live-receipt-where p, .live-receipt-where .hint { margin: 0; overflow-wrap: anywhere; } .live-receipt-cost { font-variant-numeric: tabular-nums; text-align: right; white-space: nowrap; } .live-receipt-n { font-variant-numeric: tabular-nums; text-align: right; margin: 0; } .live-receipt-total { border-top: 1px solid var(--border-strong); font-weight: 600; } From 7ae93b346e4f1dfa476251f4dfc4558e89290cae Mon Sep 17 00:00:00 2001 From: Vimox Shah <10022243+vimoxshah@users.noreply.github.com> Date: Mon, 7 Sep 2026 19:36:39 +0530 Subject: [PATCH 2/2] chore(release): 1.3.4 Co-Authored-By: Claude Fable 5.1 --- CHANGELOG.md | 21 +++++++++++++++++++++ action/README.md | 2 +- docs/receipts-on-github.md | 2 +- package.json | 2 +- 4 files changed, 24 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f4a5058..b413ba2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,27 @@ All notable changes to TokenFlow are recorded here. Versions follow [semantic versioning](https://semver.org/spec/v2.0.0.html). +## 1.3.4 — 2026-09-07 + +Interface fixes only; nothing about how TokenFlow reads or prices usage changes. Every one of +the 21 views was rendered in the three themes at 1440, 1000, 700 and 380 pixels wide and probed +for text that leaves its card or a page that scrolls sideways. None remains. + +### Fixed + +- **The Data health grade fits its card.** "Excellent" was set at the hero figure size, which is + tuned for numbers, and at that size the word is wider than one card of the grid, so it read + "Excelle" with the rest cut off. A hero value is now sized to the card it sits in, between a + readable floor and the hero size, and the grade card takes a double-width slot so the word + shows at full size on anything wider than a phone. +- **The Cost and Peaks heroes no longer clip on narrower windows.** The same rule catches + "$364.52" and "53.3M", which ran 5 to 57 pixels past the card edge: in the Editorial theme + even at 1440 pixels, and in every theme below 1000. +- **The Overview no longer scrolls sideways on a phone.** Its two-up chart grid asked for columns + at least 420 pixels wide, so on a 380-pixel screen the page was 64 pixels wider than the + viewport. The minimum now yields to the width available. +- **Branch names in the Live receipts list wrap** instead of spilling out of their column. + ## 1.3.3 — 2026-09-07 No change to how TokenFlow reads or prices anything. This release exists so the GitHub Action diff --git a/action/README.md b/action/README.md index 33c95f4..fe14fd6 100644 --- a/action/README.md +++ b/action/README.md @@ -34,7 +34,7 @@ jobs: - uses: actions/checkout@v4 with: fetch-depth: 0 # notes live outside the default shallow fetch - - uses: vimoxshah/tokenflow@v1.3.3 + - uses: vimoxshah/tokenflow@v1.3.4 # or, dogfooding this repo's own checkout: uses: ./ with: token: ${{ secrets.GITHUB_TOKEN }} # default; only pass to override diff --git a/docs/receipts-on-github.md b/docs/receipts-on-github.md index 3af85b0..1cd0855 100644 --- a/docs/receipts-on-github.md +++ b/docs/receipts-on-github.md @@ -44,7 +44,7 @@ network call. - uses: actions/checkout@v4 with: fetch-depth: 0 # notes live outside the default shallow fetch - - uses: vimoxshah/tokenflow@v1.3.3 + - uses: vimoxshah/tokenflow@v1.3.4 with: max-usd: '50' max-usd-per-100-lines: '5' diff --git a/package.json b/package.json index 79cdf65..44fd017 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@vimoxshah/tokenflow", - "version": "1.3.3", + "version": "1.3.4", "description": "Local-first, provider-agnostic analytics for the AI tokens you actually spend. Zero dependencies, nothing leaves your machine.", "type": "module", "license": "MIT",