Skip to content

feat(mobile): the phone shell - #6767

Open
tsahimatsliah wants to merge 24 commits into
mainfrom
claude/mobile-shell-1
Open

tsahimatsliah wants to merge 24 commits into
mainfrom
claude/mobile-shell-1

Conversation

@tsahimatsliah

@tsahimatsliah tsahimatsliah commented Oct 1, 2026 •

Copy link
Copy Markdown
Member

Step 1 of the mobile shell work (Storybook Mobile UX / 10. The build, PRs 1.1 to 1.8 delivered together as agreed): the phone shell under 656px. Tablet, laptop, layout v2 and the extension keep the code they run today.

Built on top of #6765 (the four fixes); merge that one first and this diff shrinks to the shell alone.

What changed

The bottom cluster (1.1) ShellCluster replaces the docked footer: Home · Explore (compass) · Squads · Activity as a floating bar on the flat blur material, the Create square beside it opening the composer directly, the brand bubble on Activity only, Tint selection (filled glyph, the rest at 72%), the shrink on scroll (56→44px, inset 20→40, radius 22→18 over 96px), re-tap the lit tab to scroll to top, the lit tab from the root that owns the URL (Squads lights on a squad page, Home on a post). Visitors: Explore and Squads open; Home, Activity and Create open the sign-up with a context line. The cluster is in the server HTML (tablet:hidden), so it paints with the page. The Charm footer still replaces it at its trigger; the post action bar sits above it as before. Deleted: MobileFooterNavbar, FooterNavBarTabs, FooterNavBarItem, FooterPlusButton, MobileFeedActions.

The You page (1.2) /you, behind the avatar: name row, View profile, then daily.dev Plus (Upgrade / Manage / Renew), Custom feeds, My squads, Following, Bookmarks, History; Your progress (Achievements, Streak as the streak sheet, DevCard, Hot takes, Game center); Core wallet, Invite friends, Settings; Help as the top action. The settings gear leaves the Home row.

The top block on roots (1.3) ShellBlock replaces FeedNav's phone branch: Home = logo (Plus mark for members), streak, the Plus square for free members (opens Plus, fires UpgradeSubscription), the 38px square avatar; Explore, Squads and Activity get the name row they never had (Activity with the notification-settings square). Today's rows ride under it (the chip strip on Home, the Spotlight trigger on Explore). The block is a fixed overlay; content takes --shell-top padding; it hides as one solid piece while reading down (64px travel, 96px dead zone, 24/8px tolerances, 300ms stop, 220ms snap on the interaction curve) and returns on any short scroll up, never on arrival, and reveals when a field takes focus. useShellScroll is the one scroll reader for block and cluster.

The top block on pages (1.4) The page row: back (38px square), the title (20px), the page's actions. GoBackHeaderMobile feeds it under 656px (title prop for the profile sub-pages), as do the profile header, the no-sidebar layout, the squad sub-page header and the settings container (its sections menu becomes a ⋮ square). Every leaf has a back now; back is history when the previous entry is ours, otherwise the owning root. The old sticky bars stay for tablet.

Sign-up, two touches (1.5) On a phone the gated sign-up closes with an X top left (not a chevron) and shows one line under the title keyed by the trigger (Follow, Bookmark, Upvote, Comment, Join squad, Create…). Providers, order, email, terms, flow and analytics untouched.

The sheet (1.6) Drawer: 300ms in on the travel curve, 200ms out, scrim 150ms, a grabber on bottom sheets, drag to dismiss past a third of the height or on a fast flick (up barely moves, back springs in 220ms), inner lists keep scrolling. Every Drawer consumer gets it.

Menus as sheets (1.7) DropdownMenu on a phone is modal and renders its Radix content as a bottom sheet (48px rows, grabber, the sheet's timings) through shell-menu-sheet; it no longer closes on the scroll the collapsing address bar fires. Items, handlers and events are untouched, which keeps PostOptionButton and the other 35 consumers as they are.

People are rounded squares (1.8) Referral slots, embedded tweets, profile metadata and the feedback widget drop their circles; squads and sources keep theirs.

The rows (added on review, from 2.1, 2.2, 2.4, 2.5 and 2.8) ShellRow with the three weights of the 28px chip: Segments (which list, replacing history), Chips (narrow this list), MenuLabel (a sheet). Home's row is For you · Happening now ▾ · Following · your custom feeds · +; the chevron on Happening now opens the channel sheet (Headlines, All, every channel), and the Highlights page shows the channel on its own line instead of its old title and tab strip. Explore's row is the search field and the places (Tags, Sources, Leaderboard, Discussions); its sort is a text menu on the feed line (Popular ▾, with the period inside for the two sorts that have one) instead of the sticky tab strip. Squads root puts its categories in the block as chips; Activity its types; Bookmarks its lists as segments under a Bookmarks title; Tags, Sources, Leaderboard and History get their names in the block and lose their in-page navbars and breadcrumbs. The old strip's other chips are reachable from Explore, You or the bar. Tablet keeps every old row.

The buttons (second review) A page keeps no button row of its own above the content once the block exists. The Squads root carries New Squad as a plus square (the Primary button under the title is gone on phones). Home shows the streak and the quest count beside the Plus square. Happening now loses the channel line and the copy link under the segments; the chevron sheet is the one channel control and marks the channel you are on. Search results are a page: back, the query as the title, a Filters square (the same drawer), the field in the row. The squad page block carries Search and the menu; the hero keeps the link and Join. The profile block carries the menu only; the hero keeps Follow and Award. Bookmarks carries Sort, Share and the folder menu in the block; the search field stays in the content. Explore loses the 40px band the old sticky tabs reserved. ShellPage now portals actions into the block's slot from the page's own tree, so an action keeps its page's providers (the squad menu needs SquadPageContext); the hero copies are gated by viewport in JS so a label exists once in the DOM.

Every page (third review) A route-by-route pass over 110 member routes at 393px. The pages that still drew a header row of their own under the block now register with it: Sources (Suggest new source as a plus square), Jobs (title, How it works, preferences), the Core wallet (title, Buy Cores), the briefings (title, Generate, settings), squad moderation, post analytics (title, Boost), the gear rankings, the best-of archives (title and copy link in the block, breadcrumbs gone on phones), the experience lists (the second back arrow gone), the squad form (title and Create Squad in the block, the back square replaces Cancel), the leaderboard detail and the tag explore page (titles). The composer page hides the block over its own chrome. FormWrapper gained an inBlock mode for page forms. Pages with their own standalone layout (Plus, Cores) are unchanged: they never render the block.

Plus: shell/constants.ts (the numbers), shell.css (material, hit areas, sheet timings, menu sheet), ShellSquare, ShellPageContext, ShellRow, HomeSegments, ExploreSortMenu.

Verified

  • New specs: useShellScroll (dead zone, tolerance, hide, snap, reveal), ShellCluster (lit root, badge, both events, composer, visitor sign-up), ShellBlock (brand row, root names, page title and actions, hidden pages, the slide). Shared and webapp suites green, strict typecheck and lint clean, extension builds.
  • Local webapp at 393px (Pixel 5, Chromium) as a member and a visitor, light and dark: every root and the main leaves screenshotted at rest and scrolled, 0 horizontal overflow, no new page errors; the squad page, a profile, search results and Bookmarks re-shot after the buttons moved. A recorded walkthrough and the screenshots are attached to the review thread.
  • iOS Simulator (iPhone 17 Pro, Safari): Explore and You rendered and checked for the material, safe areas and the cluster above the toolbar.

Known and deferred (by the plan)

  • The cluster paints with the server HTML; the block still arrives after hydration like FeedNav did (desktop HTML would otherwise carry it).
  • Explore keeps its 40px top padding under the places row (the page container's own padding); covers and the name in the block on things are 2.6 and 2.7; the post page capsule is 3.1; the pager (segments following the thumb) is 2.9. Sub-sheets inside the post menu (Not interested) are not built yet; the menu is the flat list it is today, rendered as one sheet.
  • In the iOS wrapper the status bar keeps today's opaque cover (safeArea.css); the no-frame look there is step 4.
  • Bottom "Close" buttons inside sheets remain where consumers set them.

🤖 Generated with Claude Code

Preview domain

https://claude-mobile-shell-1.preview.app.daily.dev

tsahimatsliah and others added 2 commits October 1, 2026 10:00
Step 0 of the mobile shell work (Storybook, Mobile UX / 10. The build):
the fixes that need no design decision, shipped together.

TabContainer's swipe decided from the total movement at touch end with a
40px threshold, so a thumb scrolling at a slight angle switched the
Highlights channel. The swipe now locks its axis on the first 10px, commits
past 56px inside a 27 degree cone or on a fast flick past 32px, and the
surface takes touch-action: pan-y. The numbers live in shell/constants.ts.

FooterNavBarLayout waited for the window load event before rendering the
bar; it renders once hydrated. The footer tabs fire a click with the footer
as target and the tab in extra, and Activity fires click notification icon
with NotificationTarget.Footer, which existed and was never used.

HighlightCardOptions and SquadOptionsButton were hidden until hover on
every device, so their actions did not exist on phones; they use the
laptop-and-mouse-only hover class the post cards use. The Explore period
drawer gets a Close, the notification menu icon stops being rotated,
Unfollow uses the remove-user icon, "Manade Ad" is "Manage ad" and the
settings menu labels match their page titles.

shell.css carries the shell's two curves and four durations and the press
class (scale 0.96, 150ms) applied to the footer tabs, the plus button, the
header controls, the feed chips and the post bar; the post titles balance.

Two pre-existing strict type errors in the touched files are fixed so the
strict guard passes.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@vercel

vercel Bot commented Oct 1, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
daily-webapp Ready Ready Preview Oct 1, 2026 1:52pm UTC

Request Review

tsahimatsliah and others added 2 commits October 1, 2026 11:32
Step 1 of the mobile shell work (Storybook, Mobile UX / 10. The build,
PRs 1.1 to 1.8 together): the shell under 656px. Tablet, laptop, layout v2
and the extension keep the code they run today.

The floating cluster replaces the docked footer: Home, Explore, Squads and
Activity on the flat blur material, the Create square beside it opening the
composer, the brand bubble on Activity, the shrink on scroll, the lit tab
from the root that owns the URL, visitors sent to sign up from Home,
Activity and Create. It is in the server HTML so it paints with the page.

The top block replaces the phone header: the brand row on Home (logo,
streak, the Plus door for free members, the square avatar opening the new
You page), a name row on Explore, Squads and Activity, and back, title and
actions on every leaf, fed by GoBackHeaderMobile and the page headers that
drew their own bars. It is a fixed overlay that hides as one piece while
reading down and returns on any short scroll up; content keeps a constant
top padding from --shell-top. One scroll reader drives block and cluster.

Drawer becomes the sheet (travel curve in, faster out, a grabber, drag to
dismiss), DropdownMenu renders as a modal bottom sheet on phones, the gated
sign-up closes with an X and names what the action gets you, and people's
pictures are rounded squares where they were circles.

Deleted: MobileFooterNavbar, FooterNavBarTabs, FooterNavBarItem,
FooterPlusButton, MobileFeedActions.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
# Conflicts:
#	packages/shared/src/components/MainFeedLayout.tsx
#	packages/shared/src/components/feeds/FeedNav.spec.tsx
#	packages/shared/src/components/feeds/FeedNav.tsx
#	packages/shared/src/components/feeds/MobileFeedActions.tsx
#	packages/shared/src/components/post/GoBackHeaderMobile.tsx
#	packages/shared/src/components/profile/Header.tsx
On review Tsahi asked for the header's rows in the same PR so no page
shows a second header. ShellRow carries the three weights of the 28px
chip (segments, chips, a menu label) and a sheet of choices.

Home's row is For you, Happening now with its channel sheet, Following,
the member's custom feeds and the plus; the Highlights page shows the
channel on its own line instead of its title and tab strip. Explore's row
is the search field and the places, and its sort is a text menu on the
feed line instead of the sticky tab strip. Squads root puts its categories
in the block, Activity its types, Bookmarks its lists as segments under a
title; Tags, Sources, Leaderboard and History take their names in the
block and lose their in-page navbars. Tablet keeps every old row.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
On review every phone page still kept a button row of its own under the
block: New Squad under the Squads title, the channel line and copy link
under Happening now, Search and the menu in the squad hero, Follow, Award
and the menu twice on a profile, Sort and Share in the Bookmarks header,
the full-width Filters on search results. They move into the block.

ShellPage now portals its actions into a slot the block exposes instead of
passing the nodes through context, because the block renders outside the
page's providers and the squad menu reads SquadPageContext. The hero
copies are gated by viewport in JS rather than CSS so each label exists
once in the DOM, which is also what the page tests query.

The streak was never hidden: the local proxy's fake member had no streak
response. The proxy stubs one now so the shots show it.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A route-by-route pass at 393px over the member app found the pages that
still drew a header row of their own under the block: Sources (a
full-width Suggest new source button), Jobs, the Core wallet, the
briefings, squad moderation, post analytics, the gear rankings, the
best-of archives (breadcrumbs and a copy link row), the experience lists
(a second back arrow), the squad form (Cancel and Create under a back
square), the leaderboard detail (no title at all), the tag explore page
(no title), and the composer page (the block over the composer's own
chrome). Each now registers its title and actions with the block on a
phone and keeps its row for tablet and up; the composer hides the block.

FormWrapper gets an inBlock mode for page forms so the squad form's
submit rides in the block and the back square replaces Cancel.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
tsahimatsliah and others added 2 commits October 1, 2026 13:56
# Conflicts:
#	packages/webapp/pages/sources/index.tsx
#	packages/webapp/pages/users.tsx
# Conflicts:
#	packages/shared/src/components/MainLayout.tsx
The 72% rest opacity sat on the whole tab, so the unread bubble dimmed
with the glyph and read as disabled next to the lit tab. The dim moves
onto the glyph alone; the bubble is the same colour in both states.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
BootProvider.spec "refetch boot on focus within the interval when another
tab changed the Plus status" counted three boot requests instead of two
on CI; it passes alone and this branch does not touch contexts/.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…finger

The lit tab now has a soft pill behind it (the Instagram reference Tsahi
gave), sliding on the travel curve when the root changes. Press and hold
the bar, move along it, and the pill follows the finger with the glyph
under it lit; lifting selects that tab. A 6px travel threshold keeps a
tap a tap, and the click the browser fires for the release is swallowed
so the drag's choice is not navigated twice. No glass: the flat material
only.

The Squads root also loses most of its top padding on phones, since the
chips moved into the block.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Suggest new source, New Squad and Job preferences need an account, so
their squares in the block render for members only; visitors keep Log in
and Open app in the row and nothing they cannot use.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A finger on the bar scales the whole bar to 1.04 from its bottom edge for
as long as it stays down (150ms ease-out in, 220ms on the no-bounce curve
out), the lift Instagram's and iOS 26's bars give; the per-tab 0.96 press
goes, since the bar now answers as one piece. The pill slides on the same
no-bounce curve, per chapter 9l. Reduced motion drops the lift.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
tsahimatsliah and others added 2 commits October 1, 2026 16:30
The finger is measured on screen while the bar is scaled to 1.04, but the
pill moves in the bar's own pixels, so at the far right it ran past the
edge by the scale difference. The finger now maps through the layout
width, so both edges clamp the same.

Also: a color-scheme meta keyed to the theme beside theme-color, and a
valid apple-mobile-web-app-status-bar-style value (black or default) in
place of the hex it had, so Safari's chrome and the standalone status bar
follow the app's background.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Chris's review: a fast diagonal flick that happened to lock sideways in
its first 10px could still change the channel through the velocity path,
which skipped the cone. The cone now gates both paths, with a spec for
that flick; the swipe specs stamp event.timeStamp (react-swipeable's
clock) so the distance and speed paths are each tested on their own.

Also from the review: the swipe surface keeps pinch zoom
(touch-pinch-zoom); the footer tabs log their own target
(mobile footer nav) with whether the tap was a member's, so they separate
from the app footer's events; the shell constants and stylesheet carry
only what this PR consumes (the motion values come with their consumers
in the shell PR), and the explanatory blocks moved to the PR description.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
tsahimatsliah and others added 2 commits October 1, 2026 16:39
Brings Chris's review fixes: the swipe cone on both paths, the stamped
swipe specs, pinch zoom on the swipe surface, and the footer tabs' own
analytics target. The bar logs the same target (mobile footer nav) with
logged_in in the extra. The shell constants and stylesheet keep this
branch's versions, since their consumers live here.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

This branch was successfully deployed

1 active deployment
Preview — d6656d23 Deployed Oct 1, 2026 by vercel[bot]
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