Skip to content

QoL: borderless cards, PWA cache strategy, avatar dropdown crash, wide→narrow overflow - #13

Merged
mike-doctorina merged 3 commits into
mainfrom
feat/qol-pwa-cards-adaptive
Jun 2, 2026
Merged

QoL: borderless cards, PWA cache strategy, avatar dropdown crash, wide→narrow overflow#13
mike-doctorina merged 3 commits into
mainfrom
feat/qol-pwa-cards-adaptive

Conversation

@mike-doctorina

Copy link
Copy Markdown
Collaborator

Summary

Quality-of-life sweep prompted by three user reports + a multi-agent review pass.

Three commits, three concerns:

fix(deploy) — PWA shell updates were stuck

  • firebase/firebase.json: explicit no-store, no-cache, must-revalidate, max-age=0 for /index.html, /sw.js, /workbox-*.js, /registerSW.js, /manifest.webmanifest. Hashed assets keep their immutable 1-year cache.
  • vite.config.ts: drop *.html from precache, override the plugin's default navigateFallback: "index.html" (which would emit createHandlerBoundToURL("index.html") — a route that throws at install once *.html isn't precached), add a NetworkFirst handler for request.mode === "navigate" that explicitly skips /__/auth/ so Firebase Auth's redirect handler is untouched. skipWaiting + clientsClaim so a deploy lands on the next navigation.
  • src/shared/api/firebase.ts: switch to initializeFirestore({ localCache: memoryLocalCache() }) — the IDB cache is a foot-gun for a PWA (stale docs survive sign-out on shared machines; write lock kept the freshly-deployed shell reading old data). Memory cache is rebuilt each load.

fix(ui) — avatar dropdown crashed

DropdownMenuLabel rendered Kobalte's Menu.GroupLabel directly inside DropdownMenuContent with no surrounding Menu.Group, so opening the avatar menu immediately threw useMenuGroupContext must be used within a Menu.Group. Wrapped the GroupLabel in KMenu.Group inside the primitive. Same caller ergonomics.

feat(qol) — UI polish + responsive

  • Borderless cards / shot tiles: drop the 1px border, lean on shadow-sm + a soft ring (black/0.04 light, white/0.06 dark). Card radius bumps to xl. Interactive cards lift to shadow-md on hover.
  • Drop "Doctorina · Screenshots" word-mark: tab title + favicon already brand the app. Brand link keeps a 36-px tap area so the click target didn't shrink to a 10×10 dot.
  • No horizontal scroll on wide→narrow resize: min-w-0 on app-bar flex container + nav; shrink-0 on brand, back-link, and right cluster; overflow-x-clip + min-w-0 on layout root (clip, not hidden, so sticky still works).
  • Run page at 320 px: the implicit grid track on <div class="grid gap-5"> was sizing to its content's min-width, letting the RunHeader card overflow ~64 px past the viewport. Switch to grid-cols-1 min-w-0. Branch chip gets max-w-[9rem] sm:max-w-[14rem].
  • Badge stays selectable: an earlier attempt baked select-none into the Badge variant, which silently broke copying branch names / PR # / commit SHAs. Reverted; select-none lives on the decorative language pills in run-card.tsx where it belongs.

Multi-agent review pass

Spawned 3 reviewers in parallel (project-rules, correctness, CSS/responsive/a11y) before committing. Findings folded back into the diff:

  • HIGH — workbox emitted a NavigationRoute(createHandlerBoundToURL("/index.html")) even after I removed navigateFallback, because vite-plugin-pwa silently defaults it to "index.html". Confirmed by reading node_modules/vite-plugin-pwa/dist/index.js:838. Fixed by setting navigateFallback: undefined explicitly.
  • HIGHselect-none on Badge variant base blocked copying. Reverted, moved to specific decorative chips.
  • HIGH — the new NetworkFirst handler caught /__/auth/*. Added denylist to its urlPattern.
  • MEDIUM:where(...) ::selection rule was dead because of cascade order. Dropped (the existing user-select: none on interactive roles already covers it).
  • MEDIUM — brand link tap target shrunk to 10×10. Restored to 36-px hit area.
  • MEDIUM — run-header chip at 14rem clipped at 320px. Tightened to 9rem on mobile.
  • MEDIUMoverflow-x-clip was hiding a real card-overflow at 320px on the run page. Fixed the underlying grid track sizing instead of just hiding it.

Test plan

  • bun run check (Biome) — clean
  • bun run typecheck — clean
  • bun run test — 140/140
  • bun run build — generated SW grepped to confirm NavigationRoute is gone and /__/auth/ is denylisted
  • Playwright walk: home + run pages at 1400 / 1024 / 768 / 420 / 320; light + dark; avatar dropdown opens; no horizontal scroll at any width on either page; branch / PR / commit-SHA badges are user-selectable on the run page
  • Manual deploy to a preview channel — confirm Firebase Hosting headers land (Cache-Control: no-store on /index.html, /sw.js)
  • Live sign-in flow with a real Google account — confirm /__/auth/handler redirect is NOT intercepted by the SW

🤖 Generated with Claude Code

PWA shell updates were sometimes stuck on the previous deploy because both
Firebase Hosting (no-cache for HTML/SW) and the SW (precached index.html)
served stale content.

- firebase/firebase.json: explicit `no-store, no-cache, must-revalidate,
  max-age=0` for /index.html, /sw.js, /workbox-*.js, /registerSW.js, and
  /manifest.webmanifest. Hashed assets still get immutable 1y cache.
- vite.config.ts: drop `*.html` from precache; override the plugin's
  default `navigateFallback: "index.html"` (which would emit a broken
  `createHandlerBoundToURL("index.html")` route that throws at install
  time once *.html isn't precached); add a NetworkFirst handler for
  `request.mode === "navigate"` with an explicit `/__/auth/` denylist so
  the SW doesn't intercept Firebase Auth's redirect handler;
  skipWaiting + clientsClaim so a fresh deploy lands on the next nav
  without a manual reload prompt.
- src/shared/api/firebase.ts: switch to `initializeFirestore({
  localCache: memoryLocalCache() })`. The IDB cache was a foot-gun for a
  PWA — stale docs survived sign-out on shared machines, and the IDB
  layer occasionally held a write lock that kept a freshly-deployed
  shell stuck reading old data. Memory cache is rebuilt each page load
  — predictable and ACL-safe.
…roup

Clicking the avatar in the app bar opened a popover that immediately
threw `[kobalte]: useMenuGroupContext must be used within a Menu.Group`
— DropdownMenuLabel rendered Kobalte's `Menu.GroupLabel` directly inside
`DropdownMenuContent` with no surrounding `Menu.Group`, so the context
hook had nothing to read.

Wrap the GroupLabel in `KMenu.Group` inside the primitive so callers
keep the same `<DropdownMenuLabel>` ergonomics. The wrapper adds one
extra `<div role="group">` to the DOM; no padding/layout change since
the inner span still owns the spacing.
UI polish + responsive fixes:

- Card / ShotTile: drop the visible 1px border in favour of
  shadow-sm + a soft ring (black/0.04 light, white/0.06 dark). Hover
  on interactive cards lifts to shadow-md. Card radius bumps to xl.
- App bar: remove the "Doctorina · Screenshots" word-mark — the tab
  title + favicon already brand the app. Just the dot stays (plus the
  FAKE chip in fake-backend mode). The link itself keeps a 36-px tap
  area so dropping the text didn't shrink the click target to a 10×10
  dot.
- App bar / layout: harden the wide→narrow resize path. Add `min-w-0`
  to the app-bar flex container and nav, `shrink-0` to the brand /
  back-link / right cluster, and `overflow-x-clip` + `min-w-0` on the
  layout root so an inner flex child measuring its min-content for one
  frame can't push the page wider than the viewport. `clip` is chosen
  over `hidden` so the sticky AppBar keeps working.
- Run page: the implicit grid track on `<div class="grid gap-5">` was
  sizing to its content's min-width, which at 320px viewport let the
  Run-header card overflow ~64 px past the right edge (clipped silently
  by overflow-x-clip). Switch to `grid-cols-1 min-w-0` so the column
  shrinks. Branch chip in RunHeader also gets `max-w-[9rem]
  sm:max-w-[14rem]` so the chip itself fits at 320 px.
- Run card: language pills keep `select-none` on the decorative
  per-element spans; Badge variant stays SELECTABLE (we reverted the
  earlier base-level `select-none` because branch names, PR numbers,
  and commit SHAs render through Badge and users copy them).
@github-actions

github-actions Bot commented Jun 2, 2026

Copy link
Copy Markdown

Visit the preview URL for this PR (updated for commit f80e5fc):

https://doctorina-test--pr13-feat-qol-pwa-cards-a-ile7u211.web.app

(expires Tue, 09 Jun 2026 08:54:18 GMT)

🔥 via Firebase Hosting GitHub Action 🌎

Sign: 940779a7026f52ecc13c172c30df0d2af2c19e77

@mike-doctorina
mike-doctorina merged commit 2d85373 into main Jun 2, 2026
3 checks passed
@mike-doctorina
mike-doctorina deleted the feat/qol-pwa-cards-adaptive branch June 2, 2026 09:59
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.

1 participant