Skip to content

refactor(web): unify mobile breakpoint into one shared isMobile store (TASK-2028)#886

Merged
xarmian merged 1 commit into
mainfrom
refactor/task-2028-unify-mobile-breakpoint
Jul 9, 2026
Merged

refactor(web): unify mobile breakpoint into one shared isMobile store (TASK-2028)#886
xarmian merged 1 commit into
mainfrom
refactor/task-2028-unify-mobile-breakpoint

Conversation

@xarmian

@xarmian xarmian commented Jul 9, 2026

Copy link
Copy Markdown
Collaborator

TASK-2028 — Unify the mobile breakpoint (PLAN-1984 Web-UX audit)

Two competing JS isMobile breakpoints (768 and 639.98) disagreed in the 640–768px band across ~15 self-rolled implementations. This replaces all of them with one shared, reactive, matchMedia-backed store.

Chosen breakpoint: 768px (<=768 is mobile)

Verified against the codebase rather than defaulting to the sm (639.98) boundary:

  • 768 is the app-chrome / mobile-nav switchuiStore.isMobile (≤768) already drives Sidebar, TopBar, BottomNav, and MobileContextBar.
  • 768 is the dominant CSS breakpoint — 16 files carry @media (max-width: 768px) layout queries; the chrome CSS keys off it.
  • The 639.98 sites were pure-JS bottom-sheet gates ({#if isMobile}<BottomSheet>), with no matching @media (max-width: 639.98px) layout query. Aligning them up to 768 therefore introduces no CSS mismatch and avoids a sprawling CSS rewrite (which choosing 639.98 would have forced on the 20 chrome queries).
  • The codebase already contained a workaround (WorkspaceSwitcher mobile prop) documenting the 768-vs-639.98 conflict; unifying removes the underlying disagreement.

New store — web/src/lib/stores/breakpoint.svelte.ts

export const MOBILE_BREAKPOINT = 768;
export const MOBILE_MEDIA_QUERY = '(max-width: 768px)';
export const viewport = { get isMobile() { … } };  // reactive

One app-wide matchMedia('change') listener writes a single $state; components only READ it (directly or via uiStore.isMobile). No $effect in the store (CONVE-1688). SSR-safe: browser-guarded, returns false on the server and resolves to the real value at client module init — the same pattern uiStore already used.

Call sites unified (11 files)

  • uiStore.isMobile now delegates to the store; its resize listener became a single matchMedia change listener that only runs the sidebar/detail-panel side effects.
  • Swapped the local detection out of: BoardView, Editor, FilterBar, ReactionPicker, FieldEditor, EmojiPickerButton, QuickActionsMenu, WorkspaceSwitcher, the [collection] list page, and the [collection]/[slug] item page.
  • The three "close the sheet when the viewport crosses back above mobile" side effects (FilterBar, FieldEditor, collection page, WorkspaceSwitcher) were reimplemented as small guard $effects that read viewport.isMobile and write only the local sheet-open flag (CONVE-1688 safe).
  • All existing isMobile && open BottomSheet gates preserved (CONVE-639); WorkspaceSwitcher's mobile prop retained (mobile-chrome slots still force the sheet). Updated the now-stale TopBar comment.

Deliberately NOT changed

BottomSheet.svelte's internal @media (min-width: 640px) (centered-modal presentation on tablet widths) is left as-is. It is established behavior already relied on by the 768-gated mobile chrome (MobileContextBar → YouSheet/QuickCaptureSheet, WorkspaceSwitcher's TopBar slot), which have always mounted the sheet in the 640–768 band. Moving it to 768 would regress those consumers; leaving it makes the newly-unified consumers adopt the same coherent tablet presentation.

Gates

  • npm run check — 0 errors (pre-existing warnings only)
  • npm run build — success
  • npm run test — 153/153 pass
  • Codex review — CLEAN

A focused unit test for the store was not feasible: the web vitest config runs in node with no Svelte plugin (the suite is "plain TS, no Svelte components"), so a .svelte.ts rune module importing $app/environment can't be compiled there without adding Svelte+jsdom test infra (out of scope). Noted as a follow-up.

@xarmian xarmian merged commit f49c14c into main Jul 9, 2026
3 of 4 checks passed
@xarmian xarmian deleted the refactor/task-2028-unify-mobile-breakpoint branch July 9, 2026 21:20
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