Skip to content

Mobile usability pass: native drill-down screens, column overview, edge-to-edge, iOS fixes#16

Merged
DocNR merged 16 commits into
mainfrom
claude/jank-mobile-review-x9mzlj
Jun 16, 2026
Merged

Mobile usability pass: native drill-down screens, column overview, edge-to-edge, iOS fixes#16
DocNR merged 16 commits into
mainfrom
claude/jank-mobile-review-x9mzlj

Conversation

@DocNR

@DocNR DocNR commented Jun 16, 2026

Copy link
Copy Markdown
Owner

Summary

A focused pass to make jank's mobile (iOS Safari/PWA) experience genuinely usable while keeping the desktop deck identity untouched. Desktop is unchanged except it gains the new column overview button.

Navigation & layout

  • Native push/pop drill-downs (mobile). Tapping a note/profile/detail link pushes a full-screen screen that slides in from the right, with the page's own back chevron, an edge swipe-back gesture, and hardware/browser back integration — instead of spawning deck columns. Feed-shaped standing surfaces (hashtag/relay/search/notifications/bookmarks) still open as columns. Desktop still spawns transient columns.
  • Column overview ("exploding tabs"). A grid of all open columns to jump to / close / add. Opens from the bottom-bar grid button on mobile and a top-bar button on desktop.
  • Edge-to-edge columns on mobile (no card border/margin); desktop keeps the floating-card look.
  • Reorganized mobile bottom bar: Overview · New column · Compose (the old + that meant "compose" was confusing).
  • Deck switcher now shows in the mobile top bar.

Fixes

  • iOS viewport hardening (safe-area column width, 100dvh popover, visualViewport-aware --vh).
  • Input-focus zoom (16px floor on mobile form controls) — fixes the bunker-URI / npub-search fields leaving the page zoomed.
  • Clipped mobile page-dot pager; page dots now track swipes via IntersectionObserver.
  • Gap above columns under the toolbar; mute/settings/profile columns cut off at the bottom.
  • Edge-swipe no longer triggers Safari's back-navigation (white screen).
  • Stray browser HTTP Basic-auth sign-in prompt from relay NIP-11 and NIP-05 verification (credentials: 'omit').
  • Overflowing column-picker tile labels.

Also merged the tombstone deck-sync fix from main (PR #15) into the branch along the way.

Test plan

  • npm run build green
  • npm run test suite green (889 + new link-routing tests)
  • Manually verified on iOS Safari preview: push/swipe-back, overview, edge-to-edge, bottom bar, no auth popup, no layout crop

Ships release notes + version bump to 26.15.0 (arms the in-app "What's new").

https://claude.ai/code/session_01V6XfpQLFEfySw8FLiPntcb


Generated by Claude Code

claude added 16 commits June 16, 2026 01:11
The mobile column page indicator positioned itself with the orphaned
`--bottom-bar-offset` CSS variable, which the Phase-2 Shell refactor stopped
setting. It fell back to a flat 3rem, ignoring `env(safe-area-inset-bottom)`,
so on home-indicator iPhones the pill tucked behind the taller bottom bar.
Offset by the bar's true height (3rem + safe-area inset) instead.

Also surface the DeckSwitcher on mobile by rendering it in the previously
empty middle of the top bar, so the active deck name is visible and decks are
switchable without desktop — mobile users run several decks too. The deck-name
chip tightens to 120px on small screens to keep clear of the action icons.

https://claude.ai/code/session_01V6XfpQLFEfySw8FLiPntcb
Three fixes for the "screen cropped weirdly" report on iOS Safari:

- Mobile deck column width subtracts env(safe-area-inset-left/right). Plain
  100vw ignores the notch/Dynamic-Island insets in landscape, so columns ran
  wider than the visible viewport and their trailing edge was cropped. Insets
  are 0 in portrait / on desktop, so this is a no-op there.

- FeedButton popover max-height uses 100dvh instead of 100vh so it tracks the
  toolbar-aware dynamic viewport rather than overflowing behind the chrome.

- --vh now also recomputes on visualViewport resize and is rAF-deferred, so it
  stays accurate through Safari toolbar collapse/expand and isn't stale right
  after orientationchange. Still reads window.innerHeight (not
  visualViewport.height) so the on-screen keyboard doesn't collapse the shell.

https://claude.ai/code/session_01V6XfpQLFEfySw8FLiPntcb
iOS Safari zooms in on focus of any input with font-size < 16px and never
cleanly resets, so inline inputs using text-sm (14px) — the hashtag/search
pickers, search column, message compose, bunker-URI and npub-search fields —
left the page stuck partially zoomed. Add an unlayered mobile rule flooring all
text form controls at 16px so focus never triggers the zoom; gated to the
mobile breakpoint so desktop keeps its smaller md: sizes.

Also let column-picker tile labels wrap (break-words + hyphens) so a long
single word like "Notifications" no longer bleeds past the tile border.

https://claude.ai/code/session_01V6XfpQLFEfySw8FLiPntcb
The deck scroller's 8px top padding rendered as a dark band between the top
toolbar and the column on mobile — conspicuous in the terminal preset, where
the deck surface (3% L) sits darker than the card (5% L) with a bright top
stripe. Drop the top padding below the mobile breakpoint so columns sit flush
under the toolbar; desktop keeps its floating-card top margin.

https://claude.ai/code/session_01V6XfpQLFEfySw8FLiPntcb
A relay that answers its NIP-11 info request with 401 + WWW-Authenticate: Basic
(e.g. a defunct/parked host like astral.ninja) made Safari pop its native
username/password sheet over the app. NIP-11 is public data, so fetch it with
credentials: 'omit' — that also keeps WebKit from engaging the auth challenge,
suppressing the prompt for any relay that 401s this way.

https://claude.ai/code/session_01V6XfpQLFEfySw8FLiPntcb
Edge-to-edge: on mobile, columns now fill the full viewport (no side padding,
no card border/rounding/shadow) so the feed bleeds to the screen edges like a
native client — the card frame only ever separated side-by-side columns, which
never happens on mobile. Desktop keeps its floating-card frame. Theme-agnostic
(breakpoint utilities + CSS vars, not keyed to the theme preset).

Page-dots: the indicator tracked the active column via scroll/scrollend events,
but scrollend only shipped in Safari 18.2 and iOS momentum scrolling throttles
scroll, so the active dot stayed put after a swipe (taps worked — they set the
id directly). Replace it with an IntersectionObserver on the deck scroller: the
most-visible column (>=50%) becomes active, so the dots update live as you pan.

https://claude.ai/code/session_01V6XfpQLFEfySw8FLiPntcb
- Mute column: the last section (Muted words) scrolled under the fixed bottom
  bar + page-dot pager with no room to clear them. Add mobile bottom padding
  (6rem + safe-area inset) so it's reachable. Desktop keeps the tight pb-4.

- Edge-swipe-back: swiping past the deck's left edge chained into Safari's
  history back-gesture, navigating off the SPA and flashing a white page. Add
  overscroll-x-contain to the deck scroller and overscroll-behavior: none on
  body/html so horizontal overscroll no longer triggers browser navigation.

https://claude.ai/code/session_01V6XfpQLFEfySw8FLiPntcb
The bottom cutoff wasn't mute-specific — every column's content shared the same
scroll area with no room to clear the fixed bottom bar + page-dot pager, so
finite-content columns (settings, profile, mute) had their last rows hidden.
Move the bottom padding from MuteListColumnBody onto the shared column-body
scroll container (mobile only, 6rem + safe-area inset). Feeds just gain a little
scroll-past room (NoteList adds no bottom padding of its own, so no double-pad).

https://claude.ai/code/session_01V6XfpQLFEfySw8FLiPntcb
A full-screen grid of every open column in the active deck. Tap a card to jump
to that column (routes through focusedColumnRequestAtom so DeckArea sets it
active + scrolls it in), tap × to close it, or tap the + card to add one. Cards
are static metadata (account-hue stripe + type icon + label via the existing
columnLabel/COLUMN_TYPES sources), not live thumbnails — every column owns a
subscription, so real snapshots would be costly; a cached first-note preview
can come later. Esc closes it.

Entry points: a grid button added to the mobile page-dot pill (dot taps still
jump, + still adds — distinct targets) and a LayoutGrid button in the desktop
top bar. Drag-reorder in the grid is deferred to a follow-up.

https://claude.ai/code/session_01V6XfpQLFEfySw8FLiPntcb
- The bottom Home button (which only scrolled to the first column) becomes an
  Overview button (grid icon) that opens the column overview — the deck's "home
  base". The brand tap still scrolls to the first column.
- The bottom "+" now means New column (opens the picker), not compose — a plus
  reading as compose was confusing. Compose moves to its own pencil button.
- The page-dot pill drops its grid + add-column buttons; it's pure position +
  tap-to-jump again. Overview / add / compose all live in the bottom bar.
- Add-column button in the top bar is now desktop-only (the bottom bar owns it
  on mobile), so there's a single, unambiguous add entry per surface.

https://claude.ai/code/session_01V6XfpQLFEfySw8FLiPntcb
In-feed drill-downs on mobile (note threads, profiles, settings, following
lists, ...) now push a full-screen native screen that slides in from the right,
with the page's own back-chevron titlebar + hardware/gesture back, instead of
spawning a deck column. This fixes the "tapping a note opens a confusing new
column" problem while keeping the deck identity intact. Desktop is unchanged —
it still spawns transient columns.

How it works:
- mobileNavStackAtom holds the push stack; MobileNavStack renders each entry as
  a SECONDARY_ROUTES page (reusing the dispatch + SecondaryPageLayout's back
  button), lower layers kept mounted (display:none) so scroll/state survive a
  back-out.
- DeckManager.pushSecondaryPage intercepts on mobile: opensAsColumnOnMobile()
  routes feed-shaped standing surfaces (hashtag/relay/search/notifications/
  bookmarks/mutes) to columns as before; everything else becomes a screen.
- popstate pops one screen per back press (re-arming a history entry); the
  bottom bar stays visible above the stack so Overview is a one-tap escape to
  the deck from any depth.
- normalizeToSecondaryRoute maps canonical /p/<id> to the /users/:id page route.

v1 scope: no swipe-back gesture yet (back chevron + hardware back + Overview
cover it), no slide-out-on-pop, LTR-only slide-in. Hashtag/relay/search stay
columns. Drill-downs sign as the active account (no per-screen AccountScope).

https://claude.ai/code/session_01V6XfpQLFEfySw8FLiPntcb
Two fixes to the mobile push-stack:
- Back no longer animates the previous screen in from the right. Lower layers
  were display:none; revealing one flipped it none→block, which restarts the
  CSS slide-in animation. Drop display:none so lower layers stay painted and the
  previous screen is simply revealed beneath the top one (also makes the push
  slide-in show the previous screen behind it, like native).
- Add an edge swipe-back gesture: a left-edge drag moves the top screen with the
  finger; releasing past ~1/3 width slides it off and pops. Vertical-dominant
  drags fall through to page scroll. The back chevron animates the same
  slide-out via closeTop().

https://claude.ai/code/session_01V6XfpQLFEfySw8FLiPntcb
Swiping a screen back revealed the live deck behind it — the feed you came
from, which often shows the very note you opened, so it read as a second/doubled
screen behind the one being dismissed. Add an opaque backdrop beneath the screen
stack that hides the deck while any screen is open, so a swipe-back reveals a
clean surface (or the immediate previous screen when stacked). The deck
reappears once the last screen is fully popped.

https://claude.ai/code/session_01V6XfpQLFEfySw8FLiPntcb
The relay NIP-11 fix missed the actual on-scroll culprit: the Nip05 component
renders on every note, so scrolling verifies each author's NIP-05 by fetching
their /.well-known/nostr.json. An author whose NIP-05 domain answers 401 +
WWW-Authenticate: Basic (e.g. a parked host like astral.ninja) made Safari pop
its native sign-in sheet. NIP-05 is public, so fetch it (and the domain pubkey
lookup) with credentials: 'omit' to keep WebKit from engaging the challenge.

https://claude.ai/code/session_01V6XfpQLFEfySw8FLiPntcb
Native push/pop drill-down screens, edge-to-edge columns, the column overview,
the reorganized bottom bar, the mobile deck switcher, and the iOS layout/auth
fixes. Arms the in-app "What's new" dialog.

https://claude.ai/code/session_01V6XfpQLFEfySw8FLiPntcb
@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying jank with  Cloudflare Pages  Cloudflare Pages

Latest commit: 6025558
Status: ✅  Deploy successful!
Preview URL: https://bde3edcd.jank-4ii.pages.dev
Branch Preview URL: https://claude-jank-mobile-review-x9.jank-4ii.pages.dev

View logs

@DocNR
DocNR merged commit 679875f into main Jun 16, 2026
2 checks passed
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