Skip to content

feat(layout-v2): label rail icons and pin streak/cores/reputation#6177

Open
tsahimatsliah wants to merge 140 commits into
mainfrom
claude/strange-jang-4a3ea2
Open

feat(layout-v2): label rail icons and pin streak/cores/reputation#6177
tsahimatsliah wants to merge 140 commits into
mainfrom
claude/strange-jang-4a3ea2

Conversation

@tsahimatsliah

@tsahimatsliah tsahimatsliah commented Jun 10, 2026

Copy link
Copy Markdown
Member

What

Addresses the top council feedback on the new reading-focused layout (v2): "everything is hidden, you have to remember the icons, and streak/cores/reputation aren't visible."

  • Labels under every rail icon — Home, Squads, Discover, Saved, Quests, Profile (plus an "Alerts" label on the notifications bell). No more guessing what an icon does. The rail widens 64px → 80px to fit, with the content offset, rail/panel separator, and collapse-toggle positions updated to match.
  • Pinned streak / cores / reputation cluster — a compact SidebarRailStats sits at the bottom of the always-visible rail, so the loved gamification signals stay at a glance regardless of whether the context panel is collapsed/expanded or which category is selected. Previously these only rendered in the expanded Home panel. Reuses the existing handlers (streak popover, wallet, reputation docs); the streak popover was generalized to open to the right (bottom-anchored) so it doesn't clip near the viewport bottom.
  • Renamed "Game Center" → "Quests" on the rail to fit one line and reflect what lands first.

Notes

  • All v2-only; gated behind layout_v2 (laptop+).
  • Two shared positioning constants left intentionally: InteractivePopup line 142 (laptop:left-16) is shared with v1, so it's untouched to avoid a v1 regression; RAIL_HOVER_PROFILE_ALIGN_OFFSET may want minor re-tuning now that rail items are taller (cosmetic, collapsed hover only).

Testing

  • tsc clean on all changed files; eslint clean; sidebar specs pass (16/16).
  • Not yet eyeballed in a running browser.

🤖 Generated with Claude Code

Preview domain

https://claude-strange-jang-4a3ea2.preview.app.daily.dev

Make the v2 desktop rail legible and keep gamification signals at a glance:
- Add a text label under every rail category icon; widen the rail 64px->80px
  and update the coupled content offset, separator, and toggle positions.
- Rename the Game Center rail category to "Quests".
- Pin a compact streak / cores / reputation cluster to the rail so the stats
  stay visible regardless of collapsed/expanded state or selected panel.
- Label the rail notifications bell ("Alerts") to match.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@vercel

vercel Bot commented Jun 10, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
daily-webapp Ready Ready Preview Jun 16, 2026 8:06am
1 Skipped Deployment
Project Deployment Actions Updated (UTC)
storybook Ignored Ignored Jun 16, 2026 8:06am

Request Review

largeNumberFormat returns string | null; widen RailSlot value type to
match (mirrors SidebarHeaderStats) so the strict typecheck passes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Reduce the rail's horizontal padding (px-2 -> px-1.5) so it feels less bulky.
- Remove the Profile tab from the top category list and relocate it to the
  bottom of the rail (below the settings icon) as the profile avatar, with the
  same click/panel navigation the tab had.
- Move the streak / cores / reputation cluster below the avatar.
- Drop the avatar + name + stats block from the expanded Home panel (the stats
  now live only on the rail; create-post action stays).
- Extract StreakPopover into its own file and delete the now-unused
  SidebarHeaderStats component.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Make every rail button's hover the same width; fix the Alerts bell that
  collapsed to content width (bare wrapper div). Search stays icon-only with
  the shortcut hint outside the hover.
- Add account-synced compact mode via the sidebarCompact flag (stored in the
  settings flags bag, no backend migration): hides the rail labels and search
  hint and narrows the rail back to its icon-only width. Toggle on the rail
  (utilities group) and a switch in Settings > Appearance (v2 only).
  MainLayout content offset mirrors both width sets.
- Move the profile avatar to the very bottom, below the stats cluster.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Remove the rail's compact/hide-labels toggle button (menu icon); compact
  mode stays available from Settings > Appearance.
- Give the Home expanded panel the same title header ("Home") as every other
  dedicated panel, replacing the New post button (still available on the rail).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Remove the Discover rail category; surface Explore from the Home panel
(after Following) and map its sub-routes (/posts, /tags, /sources, /users,
/discussed) to the Home category for active highlighting. Step 1 of the
Explore restructure — the dedicated pages become Explore tabs next.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Rail order is now Home, Squads, Alerts, Quests, Saved, Settings (bell
  moved before Quests; Settings lifted from the bottom utilities into the
  top nav as a labeled tab).
- Combine the streak / cores / reputation cluster and the profile avatar
  into one bordered card at the bottom (stats on top, avatar below), and
  enlarge the stats for prominence.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
When the rail is too short to fit every nav item, the lowest-priority items
fold into the Settings button, which becomes a 3-dots "More" dropdown — fold
order Saved → Quests → Alerts; Home, Squads and New post always stay. The
fold point is measured against the nav list's height via ResizeObserver, so
it tracks the viewport like Slack's sidebar. Extracted a renderCategoryTab
helper to share tab markup between the rail and the More menu.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Stack each rail stat as icon-on-top / value-below to match the nav items.
- Remove the dark filled card (border + background + dividers) around the
  stats and profile avatar so the cluster reads bright and open on the rail.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a shared ExploreSectionTabs primary navbar (Explore / Tags / Sources /
Leaderboard / Discussions) so the pages folded out of the old Discover hub
stay one click apart:
- Explore feed header (MainFeedLayout) stacks the section tabs above the
  existing sort tabs (Popular / upvotes / comments / date / best) — the
  grouped two-level layout.
- Tags, Sources and Leaderboard pages render the section tabs in their
  PageHeader so the hub persists across the directory pages.

Discussions persistence (its header routes through the feed heading) is a
follow-up.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Reframe the squad favorite as a "pin" in v2 (reuses favoritedAt /
toggleFavoriteSource — no backend change):
- SquadFavoriteButton shows a pin icon + "Pin/Unpin" label in v2 (v1 keeps
  the star/favorite look).
- New PinnedSection lists the user's pinned squads under a "Pinned" header in
  the Home panel; renders nothing when empty.
- NetworkSection no longer floats pinned squads to the top of the Squads list
  in v2 (plain alphabetical order) since they now live in the Pinned section.
- Add sidebarPinnedExpanded settings flag for the section's collapse state.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
SquadFavoriteButton is a shared, provider-light button; reading
useLayoutVariant() (which needs AuthContext) crashed its unit test. Take an
`asPin` prop instead and have the v2 callers (NetworkSection, PinnedSection)
pass it, so the button stays context-free and v1 keeps the star/favorite
labels.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
tsahimatsliah and others added 2 commits June 11, 2026 01:34
- Keep the pin icon hover-only even when a squad is pinned (the Pinned
  header already conveys the state), so the column isn't busy. v1 favorites
  are unaffected.
- Always render the "Pinned" section header (even when empty) so users
  discover the feature.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a "Recent" section listing the last 5 non-post pages the user visited
(profiles, feeds, tags, sources, etc.). Post permalinks (/posts/[id]) are
skipped — they belong to reading History.

- recentPages.ts: tiny localStorage-backed external store.
- useRecentPages / useRecordRecentPages: read via useSyncExternalStore;
  record route changes (gated to v2) reading the settled document.title.
- RecentSection mounted in the Home panel under Pinned; hidden when empty.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
tsahimatsliah and others added 2 commits June 11, 2026 01:59
Slack-style profile menu replaces the Profile sidebar panel:
- Bottom-rail avatar opens an InteractivePopup with Your profile, Analytics,
  Jobs, Settings, DevCard (Cores wallet omitted — the stats cluster links
  there). New SidebarProfileMenu popup position.
- Remove the Settings rail tab; the overflow "More" is now a standalone
  3-dots button (no longer the Settings anchor) and no longer lists Settings.
- Profile sub-routes resolve to their natural category (devcard → Settings);
  updated sidebarCategory tests accordingly.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…b row

Replace the stacked sort tabs on the Explore feed header with a right-aligned
"Sort" dropdown next to the section tabs — sorting a feed isn't navigating to
a sibling page, so a dropdown reads cleaner (Reddit/GitHub pattern). Each
sort stays its own route; the date-range control still shows for the
upvotes/comments sorts. v1 inline explore header is unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
tsahimatsliah and others added 28 commits June 15, 2026 18:06
Set the streak component back to the variation we liked: a bordered box (54x76,
frame radius 19 / fill 16) wrapping the 40px avatar at top and the flame+number
chip at the bottom, two distinct hover buttons. Keeps the photo-friendly avatar
hover (brighten+scale) and the chip-anchored tooltip from later fixes. State
visuals are unchanged (driven by useStreakRingState).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- raise the rail top padding (pt-1.5 -> pt-3) so the streak tile's top gap
  matches the ~13px it has on each side
- reduce the tile's bottom margin (mb-3 -> mb-2) to bring the search icon a bit
  closer

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…rder

Approach #2: the box grows a little (54->62px) and the avatar is centred, giving
the flame+number room at the bottom without touching the frame. State visuals
unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- root cause of the non-square avatar: the ProfilePicture <img> is display:inline,
  so its baseline descender made the wrapping button taller than wide. Force the
  image to `block` (both streak + no-streak branches).
- root cause of the broken tooltip: the shared Tooltip bakes in
  collisionPadding {top:75} (old top-header leftover); the avatar sits ~20px from
  the viewport top so that no-go zone shoved the tooltip down and mangled the
  arrow. Pass RAIL_TOOLTIP_COLLISION_PADDING on the avatar + chip tooltips.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Use border-subtlest-quaternary (the soft divider between the rail icons and the
panel) for the calm streak states instead of the slightly stronger -tertiary.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The flame + number now sit on the box's bottom border and break through it like
a fieldset legend — the chip uses the sidebar background to mask the line where
it crosses, so a long number just opens a wider gap. Box is shorter (54x56,
frame radius 16 / fill 13); all state visuals stay driven by useStreakRingState.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Set the rail top padding to 13px to exactly match the tile's side gap (54px tile
centred in 68px content = 7px + 6px px-1.5), so top = left = right.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…r glow

- useInteractivePopup now accepts a group; popups in the same group close each
  other on open (needed because wrapHandler stops propagation, so opening one
  popup never reaches the others' outside-click listeners). Wire support, the
  profile menu and the reading-streak popover into one group so only one shows
  at a time.
- avatar hover is now a plain scale-up (removed hover:brightness-110 glow).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- box 56 -> 66 so the legend chip sits well below the avatar instead of
  crowding it (chip stays on the bottom border, just lower)
- calm border quaternary (8% alpha, near-invisible) -> tertiary (20%) so the
  ring reads against the dark surface
- chip keeps bg-background-default (the exact sidebar background) so it masks
  the border seamlessly

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- extract the streak indicator's "border legend" visual into a presentational
  StreakRing component (packages/shared); it takes `state` as a prop and never
  calls the hook, so it stays provider-light. State visuals come from
  useStreakRingState's now-exported class maps, so the rail and Storybook render
  identically.
- SidebarDesktopV2 now renders <StreakRing> and passes the interactive avatar
  button + chip props; no behaviour change.
- add Components/Sidebar/StreakRing story: a Playground (state/count/hasReadToday
  controls) and an AllStates grid covering every stage, on the dark rail bg.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Brings the streak chip a little closer to the avatar. The chip already uses
bg-background-default — the exact sidebar background — so it stays seamless.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The new StreakRing story makes typecheck_strict_changed run the storybook tsc,
which follows shared's icon barrel into the SVGR `*.svg` imports. The storybook
package had no `*.svg` ambient declaration (only packages/shared does), so every
icon failed TS2307 and the changed icons (Help, NewPost) tripped the gate. Add
a storybook svg.d.ts mirroring shared/custom.d.ts.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- rest day (frozen): flame icon is now filled (keeps the blue colour)
- celebration flame + number now use the read-today pink (was white)
- earn pop reworked: only the border animates — it pops bigger while shifting
  gray -> pink, then settles back to default size; the fill fades to a pink tint
  on the way back with no size change (new streak-earn-border / streak-earn-fill
  keyframes). transition-colors on frame + fill smooths the settle.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The v2 rail isn't bg-background-default — at laptop+ it's overridden to the
tinted page colour (color-mix surface-secondary 3% over background-default).
The chip used pure background-default, so it read as a slightly different shade.
Use the same color-mix on the chip (and the Storybook decorator) so it masks the
border seamlessly and blends with the rail.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…out shift)

The streak tile only rendered once the streak query resolved (isEnabled required
the fetched data), so it swapped in from the small avatar-only fallback —
shifting the rail. Now:
- useReadingStreakSummary derives isEnabled from settings (logged in + not opted
  out), known before the data loads, and exposes isLoading.
- StreakRing renders at full size immediately and shows a same-size skeleton
  (neutral border + placeholder flame/number) until the data arrives.
- celebration pop is gated on !isLoading so the loading->loaded settle never
  fires a false earn animation.
- Storybook: isLoading control + a Loading story.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Earn pop now feels like a gong: sharp fast strike (border snaps bigger +
  gray->pink by 20%), then rings out smoothly back to default size with the pink
  fill in harmony. Faster (1.5s -> 0.7s), no plateau, and CELEBRATION_MS 1800 ->
  800 so it never holds before settling.
- Urgency tooltip: auto-opens for at_risk AND critical (was critical-only, 5s
  timer) and stays open until the user hovers the streak, then reverts to
  hover-only; re-arms each time the streak re-enters an urgent state.
- The StreakRing skeleton / no-shift work and all earlier Storybook changes are
  shared, so they're already live in the rail (webapp + extension).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… tooltip

- Earn pop is now a smooth single "breathe": ease-in-out, gentle 1.16 expand +
  settle, faster (0.7s -> 0.5s, CELEBRATION_MS 800 -> 600), no snappy attack or
  lingering tail — fluid with the gray->pink colour shift.
- Urgency tooltip is critical-only again: auto-opens for ~5s when the streak
  turns critical, hides on the 5s timer or as soon as the user hovers the
  streak; re-arms on re-entering critical. (Dropped the at-risk auto-open.)
- Storybook: add a CriticalWithTooltip story so the urgency tooltip design is
  visible (forced open), plus the chipTooltip/chipTooltipOpen controls.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The CriticalWithTooltip story is the only one that mounts the shared Tooltip,
which reads the request protocol via useQueryClient — without a QueryClientProvider
that throws, so the story errored. Add the provider decorator (matching
Tooltip.stories).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Swap the slow ease-in-out breathe for a snappy pop: the border strikes up fast
with overshoot (scale 1.25), rebounds slightly past its rest size (the gong
vibration), then settles — ease-out, 0.45s. CELEBRATION_MS 600 -> 550.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The chip now uses the same colour that sits behind the avatar for each state
(opaque, never transparent): calm states keep the rail tint, while freeze /
at-risk / critical / earned use the matching -flat tint (freeze is the exact
same blueCheese-flat as its fill). So the chip reads as part of the coloured
state instead of a separate neutral box. transition-[transform,background-color]
fades the chip colour smoothly on state changes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
At rest the chip stays the neutral rail tint (masking the border); on hover it
shifts to the colour behind the avatar — freeze -> blueCheese-flat, at-risk ->
bun-flat, critical -> ketchup-flat, earned -> bacon-flat (calm states get a
subtle surface-hover). Reverts the always-on colour to a hover-only change.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Shorten the earn-pop strike to 0.3s (was 0.45s) and CELEBRATION_MS 550 -> 400 so
it pops and settles quickly with no hold.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A side-by-side comparison of celebration animations at different durations and
easings (punch+rebound, quick punch, snap, spring/back-out, gong ring) with a
Replay button, to dial in the perfect earn-pop feel. Uses inline keyframes so
the timing is accurate even when the running Storybook serves a stale tailwind
config (tailwind config changes don't hot-reload).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the mixed-easing variations with the same punch+rebound pop at 6
durations (0.15 / 0.3 / 0.45 / 0.6 / 0.8 / 1s) to compare speed only. Drop the
now-unused punch/spring/gong keyframes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Set the earn pop to 0.6s and rework the fill timing: the pink stays clear while
the border strikes up to its peak (0->25%), then washes in from that point to
the end as the border rebounds and settles — instead of flashing in at the
peak. CELEBRATION_MS 400 -> 700 to cover the longer pop. Mirror the timing in
the Storybook earn-pop lab.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Remove popClassByState (an all-empty-string map) and the popClassName return
  field/type — read by no consumer since the earn pop moved to a border-only
  animation.
- Remove the isUrgent return field/type — no consumers.
- Remove weekProgress and the eager 30-day reading-history query that only fed
  it. The query key is shared (ReadingStreakPopup fetches it on demand), so this
  just drops a wasted 30-day fetch that ran for every logged-in user, plus the
  now-unused WEEK_LENGTH/imports.

No behaviour change — all removed symbols were unreferenced.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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