feat(mobile): Wave 4 — RegisterPage responsive + TransferIndicator on settings + MessageInput sheets
- RegisterPage: scroll envelope, iOS-zoom-safe inputs, ≥44px tap targets, mobile-friendly invite chip + swatch row - TransferIndicator: mounted via MobileScreenHeader.rightActions across every settings/instance screen and inline in MobileChatScreen; touch-close listener; viewport-safe panel width - MessageInput popovers (emoji/GIF/mention) now route through new InputPopover wrapper — desktop popover, mobile bottom-sheet - EmojiPicker mobile branch: dynamicWidth + scoped CSS (.emoji-picker-wrapper--mobile) so the <em-emoji-picker> custom element fills the sheet; bigger touch targets (perLine 8, button 40, emoji 28); desktop unchanged - MessageInput trigger row: mobile-first sizing with md: desktop overrides (40×40 buttons + gap on mobile, 34×34 unchanged on desktop) - Spec updates: docs/systems/auth.md (RegisterPage responsive contract), docs/systems/uploads.md (TransferIndicator mobile surfaces)
This commit is contained in:
@@ -563,6 +563,8 @@ Called by `useAuth()` hook when token exists but user object is null:
|
||||
|
||||
**Auth-token source of truth.** During the step-2 avatar upload, the JWT lives in `localStorage` only -- `authStore.token` (Zustand) is still null because step 3 hasn't fired. Both the home `api` client (`api/client.ts`) and the home-origin branch of `setTokenForOriginResolver` in `instanceStore.ts` therefore read the home JWT from `localStorage.getItem('backspace_token')`, never from `authStore.token`. This keeps `transferStore.startUpload` (and any other path that resolves a home-origin bearer) authenticated during the registration window. The two stores are written together everywhere else (`initSession`/`logout`), so the divergence only matters between steps 1 and 3 here.
|
||||
|
||||
**Responsive contract.** Auth pages render outside `MobileShell` (they are pre-layout). The `RegisterPage` outer wrapper is a self-contained scroll container -- `h-full overflow-y-auto` on the outermost `<div>` because `#root` is `h-full overflow-hidden` (see `globals.css`). An inner `min-h-full flex items-center justify-center` wrapper centers the card vertically when content fits, and falls back to top-aligned scroll when content exceeds the viewport (as on iOS Safari with the keyboard up, where the visible viewport shrinks by ~300 px). Card width is `max-w-[480px]` with `px-4` outer gutters, `p-6 md:p-8` inner padding (smaller on mobile to reclaim 16 px content area at 360 px viewports). All `<input>` elements override the shared `input-standard` class's `text-sm` with `text-base md:text-sm` -- iOS Safari auto-zooms when an input has font-size <16 px. Primary submit buttons use `py-3 md:py-2.5` to satisfy Apple HIG's ≥44 px tap-target rule on mobile. The closed-registration URL-token chip switches from `inline-flex` (desktop pill) to `flex` (mobile full-width banner) so longer error copy ("Invalid invite link -- please request a new one") wraps cleanly inside a 360 px viewport instead of forcing a single-line pill that overflows. The avatar color swatch row uses `gap-2 md:gap-2.5` so the 7 swatches fit within the 360 px content area. **Note:** `LoginPage` does NOT yet apply the same scroll/iOS-zoom/tap-target treatment; this is a known follow-up since LoginPage's shorter form is less likely to clip. Update both together if revisiting.
|
||||
|
||||
---
|
||||
|
||||
## 8. Federation-Aware Identity Utilities
|
||||
|
||||
@@ -203,6 +203,18 @@ When a user sets an avatar/banner or a space sets an icon/banner:
|
||||
|
||||
The attachment record for profile images is intentionally deleted -- the authoritative reference moves to the `users.avatar`/`users.banner` or `spaces.icon`/`spaces.banner` column.
|
||||
|
||||
### Mobile Transfer-Chrome Surfaces
|
||||
|
||||
In-flight transfers (chat attachments, profile/banner uploads) are surfaced via the shared `TransferIndicator` component. Mount points:
|
||||
|
||||
- **Chat header (desktop):** every `MainContent.tsx` header variant — DM, group DM, space text channel, voice.
|
||||
- **Chat header (mobile):** `MobileChatScreen.tsx` mounts `<TransferIndicator />` in its custom header.
|
||||
- **Settings/Instance screens (mobile):** mounted via `MobileScreenHeader.tsx`'s `rightActions` slot on every settings screen — `MobileSettingsScreen` (hub + each direct panel), `MobileInstancePanel`, and the six `settings-instance-*` sub-panel wrappers in `MobileShell.tsx`. This guarantees that a profile-picture or banner upload triggered from settings has visible progress chrome and an abort affordance regardless of the user's current screen.
|
||||
|
||||
The component is lightweight when idle — the underlying `transferStore` Map subscription is a single `useMemo` over `Array.from(...).filter(t => t.tray)`, and the rendered button is a small icon (no badge) until at least one transfer is active. Safe to mount on every settings screen without performance impact.
|
||||
|
||||
The dropdown panel uses `touchstart` + `mousedown` listeners for click-outside dismissal so a single tap on iOS Safari closes the tray. Panel width is `min(300px, calc(100vw - 16px))` to prevent right-edge clipping on narrow viewports while keeping the desktop panel size unchanged.
|
||||
|
||||
---
|
||||
|
||||
## 5. Thumbnail Generation Details
|
||||
|
||||
Reference in New Issue
Block a user