Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion action/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion docs/receipts-on-github.md
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
10 changes: 6 additions & 4 deletions src/ui/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -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]));
Expand Down Expand Up @@ -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());
Expand Down Expand Up @@ -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`));
Expand Down
26 changes: 23 additions & 3 deletions src/ui/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -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; }
Expand Down Expand Up @@ -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; }
Expand Down
2 changes: 1 addition & 1 deletion src/ui/styles/live.css
Original file line number Diff line number Diff line change
Expand Up @@ -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; }
Expand Down
Loading