From 4167fb243c321c78e788e7066bf5e8a7bd11aad4 Mon Sep 17 00:00:00 2001 From: Tsahi Matsliah Date: Wed, 27 May 2026 09:49:03 +0300 Subject: [PATCH 01/14] fix(header): lift avatar out of the stats pill The avatar lived inside the surface-float pill with only the reputation button's right padding between the number and the profile picture. Move it to a sibling of the pill so it gets a proper 8px breathing gap, and revert reputation to the symmetric !px-1.5 the other two stats use. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../src/components/profile/ProfileButton.tsx | 104 +++++++++--------- 1 file changed, 54 insertions(+), 50 deletions(-) diff --git a/packages/shared/src/components/profile/ProfileButton.tsx b/packages/shared/src/components/profile/ProfileButton.tsx index e21c5a2dbf..332c6003f2 100644 --- a/packages/shared/src/components/profile/ProfileButton.tsx +++ b/packages/shared/src/components/profile/ProfileButton.tsx @@ -204,61 +204,65 @@ export default function ProfileButton({ icon={} /> ) : ( -
- {isStreaksEnabled && streak && ( - - )} - {hasCoresAccess && ( - - Wallet -
- {preciseBalance} Cores - - } - > +
+
+ {isStreaksEnabled && streak && ( + + )} + {hasCoresAccess && ( + + Wallet +
+ {preciseBalance} Cores + + } + > +
+ + + +
+
+ )} +
- - - +
- )} - -
- -
-
+
From 60a96f88ec83a4ae51b10ac4833fac6ffaf61f11 Mon Sep 17 00:00:00 2001 From: Tsahi Matsliah Date: Wed, 27 May 2026 10:41:30 +0300 Subject: [PATCH 04/14] fix(header): make stat-button hover state fill the pill Each Small Button shipped at h-8 inside an h-10 pill with px-1, so the hover background had a 4px halo on every side. Switch the pill to items-stretch, drop its horizontal padding, clip with overflow-hidden, and let each Button take !h-full !rounded-none so the hover fills its slot edge to edge. Co-Authored-By: Claude Opus 4.7 (1M context) --- packages/shared/src/components/profile/ProfileButton.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/shared/src/components/profile/ProfileButton.tsx b/packages/shared/src/components/profile/ProfileButton.tsx index 274381579d..af57b51f7a 100644 --- a/packages/shared/src/components/profile/ProfileButton.tsx +++ b/packages/shared/src/components/profile/ProfileButton.tsx @@ -207,12 +207,13 @@ export default function ProfileButton({ /> ) : (
-
+
{isStreaksEnabled && streak && ( )} {hasCoresAccess && ( @@ -236,7 +237,7 @@ export default function ProfileButton({ tag="a" variant={ButtonVariant.Tertiary} size={ButtonSize.Small} - className="!px-1.5" + className="!h-full !rounded-none !px-1.5" > {largeNumberFormat(displayedBalance)} @@ -260,7 +261,7 @@ export default function ProfileButton({ } variant={ButtonVariant.Tertiary} size={ButtonSize.Small} - className="!px-1.5" + className="!h-full !rounded-none !px-1.5" onClick={wrapHandler(() => onUpdate(!isOpen))} > {largeNumberFormat(displayedReputation ?? 0)} From f315ad6256879d1b0efc0cd842c21d84ff5c7cae Mon Sep 17 00:00:00 2001 From: Tsahi Matsliah Date: Wed, 27 May 2026 10:49:14 +0300 Subject: [PATCH 05/14] fix(header): align inner profile gap with parent header gap The parent HeaderButtons container uses gap-3 (12px) between top-level items, but the pill-to-avatar gap inside ProfileButton was gap-2 (8px). Bump it so every gap in the right header strip is the same 12px. Co-Authored-By: Claude Opus 4.7 (1M context) --- packages/shared/src/components/profile/ProfileButton.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/shared/src/components/profile/ProfileButton.tsx b/packages/shared/src/components/profile/ProfileButton.tsx index af57b51f7a..4a68582481 100644 --- a/packages/shared/src/components/profile/ProfileButton.tsx +++ b/packages/shared/src/components/profile/ProfileButton.tsx @@ -206,7 +206,7 @@ export default function ProfileButton({ icon={} /> ) : ( -
+
{isStreaksEnabled && streak && ( Date: Wed, 27 May 2026 11:01:50 +0300 Subject: [PATCH 06/14] fix(header): bump stat-button padding and rebalance visible inset MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Buttons inside the stats pill were getting cramped on both sides with !px-1.5 (6px), and the reputation icon's built-in transparent inset (~4px when scaled to 28px) made its left side read as larger than its right. - Streak: !pl-2 !pr-2.5 (8/10) so the 2px icon-centering padding lands at a visible 10px on the left, matching pr-2.5. - Cores: !px-2.5 (10/10), symmetric. - Reputation: !pl-1.5 !pr-2.5 (6/10) so the ~4px icon inset lands at a visible ~10px on the left, matching pr-2.5. Also drop the compact-only !px-1.5 hardcoded inside ReadingStreakButton — callers now pass their own padding when they need to override the design-system default. Co-Authored-By: Claude Opus 4.7 (1M context) --- packages/shared/src/components/profile/ProfileButton.tsx | 6 +++--- .../shared/src/components/streak/ReadingStreakButton.tsx | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/shared/src/components/profile/ProfileButton.tsx b/packages/shared/src/components/profile/ProfileButton.tsx index 4a68582481..dd7b287e3a 100644 --- a/packages/shared/src/components/profile/ProfileButton.tsx +++ b/packages/shared/src/components/profile/ProfileButton.tsx @@ -213,7 +213,7 @@ export default function ProfileButton({ streak={streak} isLoading={isLoading} compact - className="!h-full !rounded-none" + className="!h-full !rounded-none !pl-2 !pr-2.5" /> )} {hasCoresAccess && ( @@ -237,7 +237,7 @@ export default function ProfileButton({ tag="a" variant={ButtonVariant.Tertiary} size={ButtonSize.Small} - className="!h-full !rounded-none !px-1.5" + className="!h-full !rounded-none !px-2.5" > {largeNumberFormat(displayedBalance)} @@ -261,7 +261,7 @@ export default function ProfileButton({ } variant={ButtonVariant.Tertiary} size={ButtonSize.Small} - className="!h-full !rounded-none !px-1.5" + className="!h-full !rounded-none !pl-1.5 !pr-2.5" onClick={wrapHandler(() => onUpdate(!isOpen))} > {largeNumberFormat(displayedReputation ?? 0)} diff --git a/packages/shared/src/components/streak/ReadingStreakButton.tsx b/packages/shared/src/components/streak/ReadingStreakButton.tsx index 903bb22e85..30dfe8976b 100644 --- a/packages/shared/src/components/streak/ReadingStreakButton.tsx +++ b/packages/shared/src/components/streak/ReadingStreakButton.tsx @@ -141,7 +141,7 @@ export function ReadingStreakButton({ : ButtonVariant.Float } onClick={handleToggle} - className={classnames('gap-1', compact && '!px-1.5', className)} + className={classnames('gap-1', className)} size={!compact && !isMobile ? ButtonSize.Medium : ButtonSize.Small} > {streak?.current} From 6ea61f206b44472cf85c0ae3dcfa0883f17994eb Mon Sep 17 00:00:00 2001 From: Tsahi Matsliah Date: Wed, 27 May 2026 13:24:23 +0300 Subject: [PATCH 07/14] fix(header): bring the avatar back inside the stats pill MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move the profile picture back into the surface-float pill as the rightmost slot so it lives in the same bordered container as the three stats. Render it through Button (Tertiary, Small) so its hover state fills the slot edge-to-edge like the other stats, and keep the same click handler as the reputation button — both open the profile menu, matching the production behaviour. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../src/components/profile/ProfileButton.tsx | 127 +++++++++--------- 1 file changed, 62 insertions(+), 65 deletions(-) diff --git a/packages/shared/src/components/profile/ProfileButton.tsx b/packages/shared/src/components/profile/ProfileButton.tsx index dd7b287e3a..d3cc954845 100644 --- a/packages/shared/src/components/profile/ProfileButton.tsx +++ b/packages/shared/src/components/profile/ProfileButton.tsx @@ -206,84 +206,81 @@ export default function ProfileButton({ icon={} /> ) : ( -
-
- {isStreaksEnabled && streak && ( - - )} - {hasCoresAccess && ( - - Wallet -
- {preciseBalance} Cores - - } - > -
- - - -
-
- )} - +
+ {isStreaksEnabled && streak && ( + + )} + {hasCoresAccess && ( + + Wallet +
+ {preciseBalance} Cores + + } + >
- + + +
-
+ )} + +
+ +
+
- +
)} From 6d23694d3da67e4fd166cdce4b176dc94ff162f9 Mon Sep 17 00:00:00 2001 From: Tsahi Matsliah Date: Wed, 27 May 2026 13:57:23 +0300 Subject: [PATCH 08/14] fix(header): merge reputation and avatar into one button Wrap the reputation badge and the profile picture in a single Button so they share one hover state instead of looking like two adjacent controls that do the same thing. Use !pr-1 (4px) on the right so the gap between the avatar and the button edge equals the 4px the centered 32px avatar leaves above and below it. Trim the left padding on streak and cores (!pl-1.5 and !pl-2) so the gaps between the four slots tighten consistently. The reputation reward-target span keeps the ref so the rep counter still pulses on impact without scaling the avatar. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../components/profile/ProfileButton.spec.tsx | 8 +++- .../src/components/profile/ProfileButton.tsx | 48 ++++++++----------- 2 files changed, 27 insertions(+), 29 deletions(-) diff --git a/packages/shared/src/components/profile/ProfileButton.spec.tsx b/packages/shared/src/components/profile/ProfileButton.spec.tsx index 2ae80077ab..8bb0049edf 100644 --- a/packages/shared/src/components/profile/ProfileButton.spec.tsx +++ b/packages/shared/src/components/profile/ProfileButton.spec.tsx @@ -54,10 +54,14 @@ it('should show "Profile settings" tooltip on the profile picture', () => { ).toBeInTheDocument(); }); -it('should show "Reputation" tooltip on the reputation badge', () => { +it('should render the reputation reward target inside the profile button', () => { renderComponent(); - expect(screen.getByLabelText('Reputation')).toBeInTheDocument(); + expect( + screen + .getByRole('button', { name: 'Profile settings' }) + .querySelector('[data-reward-target="reputation"]'), + ).not.toBeNull(); }); it('should show settings option that opens modal', async () => { diff --git a/packages/shared/src/components/profile/ProfileButton.tsx b/packages/shared/src/components/profile/ProfileButton.tsx index d3cc954845..2971c28b2f 100644 --- a/packages/shared/src/components/profile/ProfileButton.tsx +++ b/packages/shared/src/components/profile/ProfileButton.tsx @@ -50,7 +50,7 @@ export default function ProfileButton({ Partial> >({}); const coresCounterRef = useRef(null); - const reputationCounterRef = useRef(null); + const reputationCounterRef = useRef(null); const displayedBalance = typeof animatedCores === 'number' ? animatedCores @@ -212,7 +212,7 @@ export default function ProfileButton({ streak={streak} isLoading={isLoading} compact - className="!h-full !rounded-none !pl-2 !pr-2.5" + className="!h-full !rounded-none !pl-1.5 !pr-2" /> )} {hasCoresAccess && ( @@ -236,7 +236,7 @@ export default function ProfileButton({ tag="a" variant={ButtonVariant.Tertiary} size={ButtonSize.Small} - className="!h-full !rounded-none !px-2.5" + className="!h-full !rounded-none !px-2" > {largeNumberFormat(displayedBalance)} @@ -244,41 +244,35 @@ export default function ProfileButton({
)} - -
- -
-
From 447832209258cc38069b917bd6fc80ab42720d09 Mon Sep 17 00:00:00 2001 From: Tsahi Matsliah Date: Wed, 27 May 2026 14:07:41 +0300 Subject: [PATCH 09/14] fix(header): tighten stat-button paddings and grow avatar to fill MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Combined profile button: drop right padding (!pr-0) and override the Button gap to 0 (!gap-0) so the reputation icon hugs its number like the streak does, and the avatar's right edge lands the same distance from the pill as its top edge does. - Grow avatar to ProfileImageSize.Large (40px) so it fills the pill height — top/bottom/right visible spacing all reduce to the 1px pill border. - Cores: !pl-1.5 (was 2) to tighten the gap from the streak. - Combined profile: !pl-0.5 (was 1) to tighten the gap from cores. Co-Authored-By: Claude Opus 4.7 (1M context) --- packages/shared/src/components/profile/ProfileButton.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/shared/src/components/profile/ProfileButton.tsx b/packages/shared/src/components/profile/ProfileButton.tsx index 2971c28b2f..596d930e74 100644 --- a/packages/shared/src/components/profile/ProfileButton.tsx +++ b/packages/shared/src/components/profile/ProfileButton.tsx @@ -236,7 +236,7 @@ export default function ProfileButton({ tag="a" variant={ButtonVariant.Tertiary} size={ButtonSize.Small} - className="!h-full !rounded-none !px-2" + className="!h-full !rounded-none !pl-1.5 !pr-2" > {largeNumberFormat(displayedBalance)} @@ -257,7 +257,7 @@ export default function ProfileButton({ variant={ButtonVariant.Tertiary} size={ButtonSize.Small} className={classNames( - '!h-full !rounded-none !pl-1 !pr-1', + '!h-full !gap-0 !rounded-none !pl-0.5 !pr-0', className, )} onClick={wrapHandler(() => onUpdate(!isOpen))} @@ -271,7 +271,7 @@ export default function ProfileButton({ From c9ffd366142fc483b917d18e74aded7a69728b29 Mon Sep 17 00:00:00 2001 From: Tsahi Matsliah Date: Wed, 27 May 2026 14:18:05 +0300 Subject: [PATCH 10/14] fix(header): shrink avatar to fit a 2px inset and match pill curvature Shrink the avatar from 40px (Large) to 36px (w-9 h-9) so it leaves 2px of breathing room on the top, right, and bottom inside the pill (1px button padding + 1px pill border). Also override the avatar's border-radius to rounded-10 (down from the size's default rounded-12). With the avatar inset 2px from the pill's rounded-12 outer corner, the UX nested-radius rule says the inner shape should be radius - inset = 12 - 2 = 10. Co-Authored-By: Claude Opus 4.7 (1M context) --- packages/shared/src/components/profile/ProfileButton.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/shared/src/components/profile/ProfileButton.tsx b/packages/shared/src/components/profile/ProfileButton.tsx index 596d930e74..c481a0cdc0 100644 --- a/packages/shared/src/components/profile/ProfileButton.tsx +++ b/packages/shared/src/components/profile/ProfileButton.tsx @@ -257,7 +257,7 @@ export default function ProfileButton({ variant={ButtonVariant.Tertiary} size={ButtonSize.Small} className={classNames( - '!h-full !gap-0 !rounded-none !pl-0.5 !pr-0', + '!h-full !gap-0 !rounded-none !pl-0.5 !pr-px', className, )} onClick={wrapHandler(() => onUpdate(!isOpen))} @@ -272,6 +272,7 @@ export default function ProfileButton({ From b9abb70532ac28c3e62b0f3aa293462c2ed2cbee Mon Sep 17 00:00:00 2001 From: Tsahi Matsliah Date: Wed, 27 May 2026 14:32:09 +0300 Subject: [PATCH 11/14] chore: re-trigger CI after flaky webapp test Co-Authored-By: Claude Opus 4.7 (1M context) From e142b2c0bd0ac98005f06b80108a3255fffcf2da Mon Sep 17 00:00:00 2001 From: Tsahi Matsliah Date: Wed, 27 May 2026 14:42:23 +0300 Subject: [PATCH 12/14] fix(header): drop right padding on combined profile button MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Switch the combined profile button's !pr-px (1px) to !pr-0 so the avatar sits flush against the button's right edge — only the pill's 1px border remains between the avatar and the pill outer edge. Co-Authored-By: Claude Opus 4.7 (1M context) --- packages/shared/src/components/profile/ProfileButton.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/shared/src/components/profile/ProfileButton.tsx b/packages/shared/src/components/profile/ProfileButton.tsx index c481a0cdc0..e8b721ad3c 100644 --- a/packages/shared/src/components/profile/ProfileButton.tsx +++ b/packages/shared/src/components/profile/ProfileButton.tsx @@ -257,7 +257,7 @@ export default function ProfileButton({ variant={ButtonVariant.Tertiary} size={ButtonSize.Small} className={classNames( - '!h-full !gap-0 !rounded-none !pl-0.5 !pr-px', + '!h-full !gap-0 !rounded-none !pl-0.5 !pr-0', className, )} onClick={wrapHandler(() => onUpdate(!isOpen))} From d78cb18eed5da7dcee54955f874e43d8fd6ab481 Mon Sep 17 00:00:00 2001 From: Tsahi Matsliah Date: Wed, 27 May 2026 14:52:40 +0300 Subject: [PATCH 13/14] chore: re-trigger CircleCI Co-Authored-By: Claude Opus 4.7 (1M context) From 2f6b64ac0c83260ea308ba0cf8d5d1f16fe4760a Mon Sep 17 00:00:00 2001 From: Tsahi Matsliah Date: Wed, 27 May 2026 15:05:20 +0300 Subject: [PATCH 14/14] chore: document profile pill layout Add a brief explanatory comment above the pill render so future readers know why streak, cores, reputation, and the avatar share the same bordered container. Co-Authored-By: Claude Opus 4.7 (1M context) --- packages/shared/src/components/profile/ProfileButton.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/shared/src/components/profile/ProfileButton.tsx b/packages/shared/src/components/profile/ProfileButton.tsx index e8b721ad3c..f51da8086f 100644 --- a/packages/shared/src/components/profile/ProfileButton.tsx +++ b/packages/shared/src/components/profile/ProfileButton.tsx @@ -197,6 +197,8 @@ export default function ProfileButton({ return <>; } + // The pill groups streak / cores / reputation / avatar into one + // bordered control with edge-to-edge hover slots. return ( <> {settingsIconOnly ? (