4 Commits

Author SHA1 Message Date
isra el
3eba8d3444 perf(web): register only the Prism languages used, scope Google OAuth
The API guide imported react-syntax-highlighter's default entry, which is
the highlight.js build with every language compiled in. It also applied
Prism themes to it, and the two use different class names, so the samples
were largely uncoloured on top of being heavy. Switch to PrismLight and
register the six languages the guide actually renders.

GoogleOAuthProvider wrapped the whole app but only the login and register
pages use it, so the Google Identity SDK initialised on every dashboard
page. Move it inside LoginWithGoogle, which is where both consumers go
through.

Total client chunks drop from 3.1M to 2.3M.

Also fixes four e2e tests that were relying on page.goto returning a
fully loaded page. Adding loading.tsx introduced a short skeleton state
that did not exist before, and each test measured or interacted during
it. They now wait on real signals instead of timing:

- bulk send waits for the dropzone row cap, which comes from
  useSubscription inside that page's own hook. The previous wait on the
  devices response was wrong: the dashboard layout requests the same
  query key, so it can resolve before the page hydrates.
- the mobile overflow sweep and the API guide replace networkidle, which
  needs a 500ms window with zero connections that link prefetching keeps
  pushing out of reach.
- the footer/tab-bar check re-scrolls as the page grows, instead of
  scrolling once while the skeleton is still short.
- the overflow sweep measures the billing tab rather than
  /dashboard/account, a redirect stub with no layout of its own whose
  client-side redirect tore down the measurement.

A new test asserts the samples carry Prism token markup in all five
languages, so a revert to the highlight.js entry fails instead of
silently dropping the colours.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-22 14:36:34 +03:00
isra el
9913b7cbe7 feat: rebuild the send page with recipient chips and segment counting
Brings the send screen up to the standard of the rebuilt bulk send flow.

Fixes real defects:
- The single-device auto-select never worked. It was computed in
  defaultValues from devices?.data?.length === 1, but react-hook-form reads
  defaultValues once on mount, while the devices query is still pending, so
  the condition was always false. It now preselects once devices resolve.
- Submitting before devices loaded showed "Required" on the device field,
  which then auto-filled a moment later, leaving a stale error beside a
  populated field. Submit is disabled until devices load.
- Moved off the legacy raw useQuery(['devices']) plus devices.data, the shared
  cache key with a different unwrapped shape that caused the devices?.filter
  crash previously.
- Every field used a placeholder as its label. Placeholders vanish on focus
  and are not reliably announced, so all three now have real labels.

Recipients are chips instead of a growing stack of inputs. They commit on
Enter, comma or blur, so a number typed and left uncommitted is not silently
dropped at send time. Pasting a list adds several at once.

Splitting a pasted list needed care, and an e2e caught it: splitting on
whitespace shredded "+1 (415) 555-0101" into three fragments. Only unambiguous
separators split first; whitespace splitting is a fallback for tokens that
cannot be a single number.

Also adds a segment counter, inline alerts for success and failure instead of
bare text, and a form reset after sending that keeps the device selected.

Phone helpers moved to lib/sms.ts alongside the segment helpers, so the send
page and bulk send share one tested implementation.

One test correction: the desktop footer assertion required links to sit on
exactly one row, but the row layout wraps by design, so it failed whenever
font metrics pushed a link to a second line. It now asserts the property the
design actually guarantees, that links are not one-per-row.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 22:04:35 +03:00
isra el
ffbbabdbd3 fix: stack footer links vertically on mobile
Wrapped inline links produced a ragged two-line block with small tap targets.
Links now stack in a single column below sm and return to one row above it.

E2e asserts both directions: every link has a distinct y at 375px, and they
share a row at 1280px.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 21:27:21 +03:00
isra el
0ea85c05e0 feat: clean up app chrome, rebuild footer and fix sidebar overlap
Top bar (app-header):
- Removed the Contribute button. It stays reachable from the footer.
- Removed the theme selector; it moves to the sidebar footer.
- Removed the mobile sheet for signed-in users: it duplicated the bottom tab
  bar for navigation and the avatar menu for identity. Signed-out visitors
  keep it, since auth pages have no tab bar.
The header is now brand on the left, account on the right.

Footer:
- Rebuilt as a slim single bar. A logged-in user is already converted, so the
  app does not need the marketing site's multi-column link farm. It borrows
  that footer's visual language (muted surface, muted-to-foreground hovers,
  green status pill) so the two still read as one product.
- Fixed the "cut by the side nav" bug. The footer was rendered in
  (app)/layout.tsx as a sibling of <main>, so it spanned the full viewport
  while the dashboard's fixed sidebar painted over its left 240px, and the
  fixed mobile tab bar covered its bottom edge. It is now rendered per
  section, inside each content column, with the dashboard copy padded clear
  of the tab bar.

Theme control:
- Moved to the sidebar footer and restyled from a dropdown into a three-way
  Light/Dark/System segmented control, which now has room to show all three
  states at once.

Sidebar icon:
- Dashboard uses LayoutDashboard instead of Home, matching the icon the
  avatar menu already uses for the same destination.

E2e harness, two real fixes found while verifying:
- The suite ran against `next dev`, which compiles routes on demand, so
  parallel workers hitting cold routes timed out at random. Switched to a
  production build; the suite went from 1.3m to ~31s.
- The survey modal (a Math.random() coin flip) and the update-app prompt
  (whenever a mocked device reports an old version) could open over any page.
  A Radix dialog marks the rest of the page aria-hidden while open, so every
  getByRole query found nothing and a different test failed each run. Both are
  now suppressed deterministically in the session helper.
Full suite: 16/16 on four consecutive runs.

New chrome.spec.ts guards the footer geometry against both the sidebar and
the mobile tab bar, asserts the top bar stays reduced, and asserts the
sidebar theme control actually toggles the dark class.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 20:21:54 +03:00