Skip to content

v0.329.0 — UI review release D: shared async states + clipped-header fix

Choose a tag to compare

@budgets-au budgets-au released this 13 Aug 12:44
· 42 commits to main since this release

Container

ghcr.io/budgets-au/budgets:0.329.0
ghcr.io/budgets-au/budgets:latest

Last of four UI-consistency releases — shared async states — plus the fix for the clipped table header reported against 0.328.

Fixed

  • Report table headers were clipped by the filter bar. On /reports two filter bars now stack (the date-range + report picker row, then the active report's own toggle bar), so real vertical chrome above a table is ~200px where --table-chrome reserved 180. The page scrolled by the difference, sliding the table's sticky top-0 header up underneath the sticky filter bar — and because both sat at z-10, the bar won and cut the month labels in half. The z-30 corner cell escaped, which is why "Category" stayed readable while "Jan '26" didn't.
    • /reports declares its own --table-chrome: 15rem, which is what the token was added for. The page no longer scrolls, so the overlap doesn't arise.
    • The z-ladder is re-cut so it degrades gracefully if a page ever does scroll: header corner z-40, rest of the thead z-20 (clear of the bar's z-10), sticky first-column body cells z-10. Documented as an explicit contract in ui/table-scroller.tsx so the next table to adopt it doesn't rediscover this.
    • accounts-cashflow-report's header cells were also semi-transparent — now opaque, same reason the frozen column needed it in 0.328.
  • Failed report fetches no longer masquerade as an empty ledger. Cashflow and Category checked !data but never error, so a failed GET fell through to "No data for this period." — telling you your ledger is empty when the request had actually failed. Both now check error first and offer a retry.

Added

  • ui/state-message.tsx — LoadingState, EmptyState, ErrorState.
    • LoadingState replaces 24 hand-written Loading… paragraphs across 23 files, which had drifted across nine padding combinations and two type sizes. Carries aria-live="polite" / aria-busy so screen readers announce the transition rather than sitting on a silently stale region.
    • EmptyState standardises the container and typography but not the copy. "No transactions in this category" and "No accounts yet" answer different questions — one says your filter matched nothing, the other says you haven't created anything. An optional action slot covers the second case.
    • ErrorState is new behaviour, not a consolidation: read failures were silent almost everywhere. Toasts remain the pattern for write failures (99 call sites, user-initiated). A read failure needs a persistent marker in the region that failed, with a retry.
    • Deliberately not skeletons — these sit above tables and charts whose height isn't known until the fetch lands, so a shimmer block of the wrong size reads worse than a line of text.

Verify

  • tsc clean, pnpm build clean, 733/743 unit.
  • 42 e2e: pages-smoke 12/12, reports-tabs 15/15, monkey-goals 15/15.

Upgrade

Pull ghcr.io/budgets-au/budgets:0.329.0 and roll. No migrations. The visible change is that report table headers stay fully readable while you scroll, and a failed report fetch now says so instead of claiming you have no data.