Skip to content

feat(frontend): fluid root font-size for app-wide responsive scaling#325

Merged
dackclup merged 13 commits into
mainfrom
claude/optimistic-brown-UUcXA
May 30, 2026
Merged

feat(frontend): fluid root font-size for app-wide responsive scaling#325
dackclup merged 13 commits into
mainfrom
claude/optimistic-brown-UUcXA

Conversation

@dackclup
Copy link
Copy Markdown
Owner

@dackclup dackclup commented May 29, 2026

What

User: the UI looked distorted at larger screens because text/elements didn't scale with the viewport, then asked to verify all platforms and audit that the layout is well-arranged with balanced whitespace (not too sparse, not too cramped).

Fix 1 — fluid root font-size — a3e496c9

globals.css: html { font-size: clamp(1rem, 0.89rem + 0.45vw, 1.25rem) }. The rem-based app scales every text/spacing/gap/chart dimension proportionally — ~16px phone → ~20px desktop — one rule. Mobile unchanged (clamp floor); rem terms (not pure vw) keep zoom working (WCAG 1.4.4).

Fix 2 — micro-labels scale too — ae132e8c

~44 fixed-px text-[10px]/text-[11px] → rem equivalents (pixel-identical at base, now scale on desktop). Only Recharts tick fontSize (SVG) + StockLogo px-prop stay px (self-contained).

Fix 3 — cross-platform layout-density (dual-audit) — ac344f50

Two parallel read-only audits (expert-user-explorer empirical render across 10 widths 360→1920 × home + detail × dark/light · frontend-design-reviewer code review) both PASSED the scaling and converged on whitespace imbalances (side-effects of rem growing 16→20px). Fixed:

  • Detail hero broke at exactly 1024px — 2-col lg:flex-row fired when the sidebar left only ~666px content → left block crushed to ~156px. Raised split to xl: (1280, ~1040px → balanced); lg/1024 now stacks cleanly; left col capped xl:max-w-2xl (no ultrawide spread); dropped the no-op lg:justify-between.
  • Content max-w-6xl (72rem) expanded to 1440px at the 20px root → sparse on 1920px. Pinned to fixed max-w-[1152px] (viewport-stable; inner rem still scales).
  • Sidebar inflated 240→300px → capped md:max-w-[240px] / md:max-w-[64px] (content +60px).
  • Mobile card min-h-[112px]min-h-[7rem] · search inline minWidth:200pxmin-w-[12.5rem] · home header + detail disclaimer max-w-3xl (prose line-length).

Verified

Playwright measure: hero=column@1024 / row@1280-1920 · content=1152px@1920 (was 1440) · sidebar=240@desktop (was 300) · zero horizontal overflow at all 10 widths · mobile (≤414) unchanged · data all correct. ruff + tsc + next build (506 routes) clean across all 3 commits.

Deferred (judgment calls flagged to user)

  • Audit suggested lowering the fluid ceiling 1.25rem→1.125rem (20→18px) for tighter data-density — kept 20px (user asked for larger text); offered as a knob.
  • Home table partially clips the Sector column at 768–834px (7 cols in ~500px content) — a tablet-breakpoint decision, deferred to a focused follow-up.

Scope

Frontend-only (globals.css + ~18 components); no schema / compute / scoring / valuation change. CLAUDE.md §Gotcha (fluid root) + AGENTS.md mirror + PHASE_STATUS_INFLIGHT.md entries.

https://claude.ai/code/session_0144kHrCYNaamMPH57b7xdM7


Generated by Claude Code

Text and layout were a flat 16px at every viewport width (414 -> 1280, measured)
— phone-tuned sizes sat unchanged on desktop, so text looked tiny and the hero
content drifted apart in the wide canvas. The app is rem-based (Tailwind), so a
fluid ROOT font-size scales every text + spacing + gap + chart dimension
proportionally with the viewport in one rule, preserving all proportions + the
design system + tabular-nums.

globals.css: html { font-size: clamp(1rem, 0.89rem + 0.45vw, 1.25rem) } —
~16px on phones (clamp floor, mobile unchanged) -> ~20px on desktop (ceiling).
The rem terms (not pure vw) keep browser zoom / user font-size prefs working
(pure-vw font-size breaks WCAG 1.4.4 resize-text).

Verified (Playwright, dark mode): root scales 16.1->17.7->18.8->20px across
414->768->1024->1280, price 24->30px; detail page + home ranking table both
read well with no horizontal overflow (scrollW == docW) at every width. Mobile
(<=414) unchanged. Documented as a CLAUDE.md §Gotcha + AGENTS.md mirror.

Frontend-only; no schema/compute/scoring/valuation change.

https://claude.ai/code/session_0144kHrCYNaamMPH57b7xdM7
@vercel
Copy link
Copy Markdown

vercel Bot commented May 29, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
quantrank Ready Ready Preview, Comment May 30, 2026 7:57am

Follow-up to the fluid root font-size (a3e496c), per the frontend-design-reviewer
audit: ~44 arbitrary text-[10px]/text-[11px] classes across 14 components (chip/
badge labels, table column headers, chart legend, and the FairPriceBarChart
headline delta % — the one primary numeric) were FIXED px and would not follow
the fluid root, drifting relatively smaller on desktop.

Converted to rem equivalents — text-[10px]->text-[0.625rem], text-[11px]->
text-[0.6875rem] — pixel-identical at the 16px base (zero mobile change) but now
scaling with the root on desktop. Only the Recharts tick fontSize SVG number +
StockLogo px-prop letter-avatar remain px (self-contained coordinate systems,
intentional).

Reviewer verdict on the root-font change: PASS on all five axes (WCAG 1.4.4
sound, no compounding font-size, layout safe, design tokens intact). tsc +
next build (506) clean; 1280 screenshot confirms labels render + scale.

https://claude.ai/code/session_0144kHrCYNaamMPH57b7xdM7
…ow-up)

After two parallel read-only audits (expert-user-explorer empirical render across
10 widths + frontend-design-reviewer code review) of the fluid-scaling change,
fix the whitespace-balance imbalances they converged on — all side-effects of the
rem root growing 16->20px on desktop:

- Detail hero broke at exactly 1024px: the 2-col lg:flex-row fired when the
  sidebar left only ~666px content, crushing the left block to ~156px. Raise the
  split to xl: (1280, ~1040px content), so 1024 stacks cleanly; cap the left col
  xl:max-w-2xl so it doesn't spread 1000px+ on ultrawide; drop the no-op
  lg:justify-between (flex-1 already consumed the free space).
- Content max-w-6xl (72rem) expanded to 1440px at the 20px root -> sparse
  table/cards on 1920px. Pin to fixed max-w-[1152px] (main + footer) so the cap
  is viewport-stable while inner rem text/spacing still scales.
- Sidebar inflated 240->300px at desktop: cap md:max-w-[240px] / collapsed
  md:max-w-[64px]; content gains ~60px.
- Mobile card min-h-[112px] -> min-h-[7rem] (scales); search wrapper inline
  minWidth:200px -> class min-w-[12.5rem]; home header + detail disclaimer gain
  max-w-3xl for prose line-length on ultrawide.

Kept the 20px fluid ceiling (user asked for larger text); the audit's suggested
20->18px reduction + the 768-834px table Sector-clip are deferred/flagged.

Verified (Playwright measure + screenshots): hero=column@1024 / row@1280-1920,
content=1152px@1920 (was 1440), sidebar=240@desktop (was 300), zero overflow at
any width, mobile unchanged. tsc + next build (506) clean.

https://claude.ai/code/session_0144kHrCYNaamMPH57b7xdM7
On a md+ viewport (landscape phone / tablet / desktop) with the sidebar
COLLAPSED, the green Q logo box (28px) and the expand-chevron toggle (32px)
overlapped in the 64px rail header — px-3 leaves only ~40px content, and 28+32
don't fit side-by-side (user-reported on a real device; reproduced collapsed
@900px: Q 14-46px, chevron 35-49px -> overlap).

Fix: when collapsed at md+, hide the Q home-link (md:hidden) and center the
chevron (md:mx-auto) as the sole header control; the Q returns on expand.
Expanded + mobile-drawer states unchanged (Q + wordmark + chevron/close-X all
show with room).

Verified (Playwright): collapsed @900 overlap=false (link hidden, chevron
centered); expanded @900 Q 14-46 / chevron 189-225 (no overlap); before/after
screenshots confirm. tsc + next build (506) clean.

https://claude.ai/code/session_0144kHrCYNaamMPH57b7xdM7
Per user "ตามที่แนะนำ" (go with the recommended option) on both flagged items:

1. Fluid font ceiling 20px -> 18px (globals.css clamp 1.25rem -> 1.125rem) for
   tighter desktop data-density (audit MAJOR-3). Now caps at ~835px so tablet+
   is a flat 18px (H1 27px not 37.5px, table cells ~15.75px); mobile keeps the
   16px floor. CLAUDE.md §Gotcha + AGENTS.md mirror updated to match.

2. Ranking table<->card breakpoint md -> lg (RankingTable.tsx): portrait tablets
   (768-1023px, only ~530px content beside the sidebar) now use the mobile card
   list instead of the 7-col table that clipped the Sector column (audit
   MINOR-4); the table returns at lg (1024, ~784px content).

Verified (Playwright): root 16.1->17.7->18.0px capping at 834px; view = cards
@414/768/834, TABLE @1024/1280/1920; zero overflow at all widths; screenshots
confirm 768 cards clean + 1280 table dense-but-scannable. tsc + next build (506)
clean.

https://claude.ai/code/session_0144kHrCYNaamMPH57b7xdM7
… rail

Revises the earlier overlap fix (which hid the Q when collapsed) per user
request to keep the green Q visible. When collapsed at md+, the header now
switches to a vertical STACK (md:flex-col, auto-height): the Q logo on top
(still a home link; the "QuantRank" wordmark stays hidden) + the expand-chevron
centered below. No overlap (stacked, not side-by-side).

Verified (Playwright, collapsed @900): Q visible y14-45 / chevron below y52-88,
overlap2D=false, both centered in the 64px rail; expanded unchanged (Q left /
chevron right, 63px header). tsc + next build (506) clean.

https://claude.ai/code/session_0144kHrCYNaamMPH57b7xdM7
…ne row

Per user clarification (supersedes the vertical-stack approach): keep the Q logo
and the expand-chevron in the SAME ROW, not overlapping. A 64px rail can't fit
both, so the collapsed rail widens to a fixed 96px (md:w-[96px]) and the header
stays a row but centers the group (md:justify-center md:gap-1 md:px-2); the
chevron drops ml-auto when collapsed so it sits beside the Q.

Verified (Playwright, collapsed @900): rail 96px, Q x12-43 + chevron x48-84
(same row, 5px gap), no overlap, header back to 63px; expanded unchanged.
tsc + next build (506) clean.

https://claude.ai/code/session_0144kHrCYNaamMPH57b7xdM7
Per user request: the expand-arrow button was a 32px square; make it a portrait
rectangle. md:h-12 md:w-6 when collapsed (27x54px @18px root, taller than wide)
beside the Q; expanded keeps the h-8 w-8 square.

Verified (Playwright, collapsed @900): chevron w27 x h54 (vertical), Q 32x32,
still one row / no overlap / 96px rail. tsc + next build (506) clean.

https://claude.ai/code/session_0144kHrCYNaamMPH57b7xdM7
The narrower vertical chevron (27px vs the old 32px square) left the 96px rail
with ~7px excess slack each side. Narrow the collapsed rail to a fixed 84px
(md:w-[84px]) = 32px Q + gap + 27px chevron + px-2 padding.

Verified @900: rail 84px, Q x10-42 / chevron x46-73 (fills the content area with
~1-2px slack), no overlap, expanded unchanged. tsc + next build (506) clean.

https://claude.ai/code/session_0144kHrCYNaamMPH57b7xdM7
The md:h-12 (48px) collapsed expand-chevron stood taller than the Q logo and
stuck out above/below it. Change to md:h-7 md:w-6 — height now equals the Q
(h-7, both 32px @18px root, tops/bottoms flush) while staying narrower (w-6) so
it still reads as a slim portrait rectangle.

Verified @900: Q 32x32 / chevron 27x32, both y15-47 (aligned), no overlap;
expanded unchanged. tsc + next build (506) clean.

https://claude.ai/code/session_0144kHrCYNaamMPH57b7xdM7
…arrow below)

Per user request, restore the earlier stack arrangement (over the side-by-side
row): when collapsed at md+, the header is a vertical stack (md:flex-col,
auto-height) with the green Q logo on TOP and the square expand-chevron centered
BELOW; rail narrowed back to a fixed 64px (md:w-16 md:max-w-[64px]).

Verified @900: rail 64px, Q y14-45 (top) / chevron y52-88 (below), centered,
no overlap; expanded + mobile drawer unchanged. tsc + next build (506) clean.

https://claude.ai/code/session_0144kHrCYNaamMPH57b7xdM7
… clone

Fixes the recurring intermittent "Python (lint + test)" CI failure on this
branch. test_list_ranking_commits_returns_real_commits runs
`git log -- frontend/public/data/rankings.json` then asserts >= 1 commit. On
CI's shallow clone (actions/checkout@v6 -> fetch-depth: 1), when the tip commit
doesn't touch rankings.json (all of this PR's commits are frontend/docs),
git log returns empty and the unconditional assert fails — intermittently,
depending on whether the runner reused a warm workspace with deeper history.

Add a pytest.skip() guard when the commit list is empty (shallow clone),
matching the PR #284 (a820cae) precedent that fixed the same pattern in a
sibling test but missed this one. A full clone still exercises the real
assertion + the sort/well-formedness checks below.

Root-caused by ci-triage-engineer; NOT caused by this PR's frontend code.
Verified: pytest tests/test_validation/test_ranking_history.py -> 18 passed
(full clone); ruff clean.

https://claude.ai/code/session_0144kHrCYNaamMPH57b7xdM7
Per user request, size + shape the collapsed expand-chevron like the nav-item
boxes below it (rounded-sm full-width box) instead of a 32px square. Collapsed
header gains md:px-2 (matches the nav's px-2); the chevron becomes
md:h-auto md:w-full md:py-1.5 with a subtle md:bg-slate-100 md:dark:bg-slate-800
fill so it renders as a full-width rounded rectangle.

Verified @900: chevron 45x28 / nav-item 45x30 — same width + x-position + fill;
expanded + mobile drawer unchanged. tsc + next build (506) clean.

https://claude.ai/code/session_0144kHrCYNaamMPH57b7xdM7
@dackclup dackclup marked this pull request as ready for review May 30, 2026 08:03
@dackclup dackclup merged commit 732853c into main May 30, 2026
3 of 4 checks passed
@dackclup dackclup deleted the claude/optimistic-brown-UUcXA branch May 30, 2026 08:03
dackclup pushed a commit that referenced this pull request May 30, 2026
…shair on sidebar toggle; guard 2 flaky tests

Bug A — sidebar "opens then shrinks back by itself" on refresh OR
portrait↔landscape rotation:
- Pre-paint a `.sidebar-collapsed` class (inline script in layout.tsx) +
  a globals.css rule renders the collapsed rail at its narrow width before
  React hydrates (the static export bakes the EXPANDED markup) — no
  wide→narrow flash on refresh. AppShell keeps the class synced to live state.
- Gate the aside width/transform transition behind an `animate` flag that's
  on only for ~250ms around an EXPLICIT user toggle; refresh + rotation +
  resize switch instantly (no shrink animation). Explicit toggles still
  animate smoothly.

Bug B — chart crosshair jumps to the far left when the sidebar expands/
collapses:
- Replace the orientation-only matchMedia re-park with a width-delta
  ResizeObserver on the chart wrapper that debounce-bumps the AreaChart
  remount key after the container re-measures, re-parking defaultIndex at the
  latest point. Subsumes rotation + now also catches sidebar toggle + resize.

Flaky tests (finish the shallow-clone / network hardening):
- test_ranking_history smoke: explicit pytest.skip on an empty frame (shallow
  CI clone, fetch-depth=1) — mirrors the #325 + PR #284 guard; proven on a
  real depth-1 clone (46/46 git-dependent tests pass).
- test_osap import scout: check the OpenAP API surface on the class instead of
  instantiating (the openassetpricing 0.0.2 ctor does a live Google-Drive
  fetch → flaky on Drive quota); the live path stays in the @network test.

Verified: ruff clean; pytest -m "not network" 1407 passed; tsc clean on edited
files; next build 506 routes; Playwright empirical Bug A x3 + Bug B all PASS.

https://claude.ai/code/session_0144kHrCYNaamMPH57b7xdM7
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants