Skip to content

feat: Buttons V2 migration — full cutover (engagement bars + shared + webapp + extension + cleanup)#5976

Open
tsahimatsliah wants to merge 23 commits intomainfrom
feat/buttons-v2-migration-consolidated
Open

feat: Buttons V2 migration — full cutover (engagement bars + shared + webapp + extension + cleanup)#5976
tsahimatsliah wants to merge 23 commits intomainfrom
feat/buttons-v2-migration-consolidated

Conversation

@tsahimatsliah
Copy link
Copy Markdown
Member

@tsahimatsliah tsahimatsliah commented May 4, 2026

Summary

Consolidates the 4-PR Buttons V2 phased migration (#5969#5970#5971#5973) into a single PR. Migrates every Button / QuaternaryButton call site across shared + webapp + extension to ButtonV2 / CardAction / CardActionBar, deletes the v1 source, turns on the no-raw-button-class ESLint rule, and adds a new shared TagChip design.

Scope: ~590 files · roughly +3,800 / -7,000 lines.

Closes #5969, #5970, #5971, #5973.

What's in here (by phase)

1. Engagement bars → CardAction / CardActionBar

Every QuaternaryButton-based engagement row in shared is rewritten on top of the new CardAction / CardActionBar primitives:

Surface File Layout
Post-detail strip PostActions.tsx CardActionBar layout=\"between\" + comfortable density, ResizeObserver label collapse via new usePostActionsLabelVisibility hook
Mobile sticky bar MobilePostFloatingBar.tsx CardActionBar layout=\"between\"
Award affordance PostAwardAction.tsx CardAction (icon swap + featured-award Image thumbnail)
Bookmark primitive BookmarkButton.tsx CardAction with state-aware aria-label
Feed grid / list / signal cards/common/ActionButtons.tsx CardActionBar layout=\"feedCard\" + density compact
Reader desktop rail reader/ReaderRailActionBar.tsx CardActionBar layout=\"between\"
Reader floating bar reader/ReaderFloatingActionBar.tsx CardActionBar layout=\"default\" + density compact
Comment row comments/CommentActionButtons.tsx CardActionBar layout=\"compact\" + density compact
Profile hot-take features/profile/.../HotTakeItem.tsx CardAction upvote, ButtonV2 edit/delete

Supporting primitives:

  • CardActionBar wrapped in forwardRef (used by PostActions ResizeObserver)
  • CardAction extends HTMLAttributes so call sites keep id / data-* / aria-*
  • usePostActionsLabelVisibility extracts the v1 label-collapse mechanic into a shared hook
  • Prerequisite: MedalBadgeIcon flipped to align with the IconPrimary outlined convention

2. `shared` package sweep — every remaining `Button` caller

448 files in `packages/shared/` mechanically migrated:

v1 v2
`Button` `ButtonV2`
`ButtonProps` `ButtonV2Props`
`ButtonGroup` `ButtonV2Group`

`ButtonSize` / `ButtonVariant` / `ButtonColor` / `ButtonIconPosition` / `IconType` keep the same identifier (re-exported unchanged from `ButtonV2`).

Wrappers re-pointed at `ButtonV2` internally: `OptionsButton`, `UploadButton`, `ToggleClickbaitShield`.

Areas: auth · ~30 modals · sidebars/navigation · profile · squad · recruiter · live rooms · onboarding/funnels · opportunity · organizations · briefing · plus · integrations · shortcuts · cores · feedback · feeds · widgets · header · drawers · popovers · cards · ad · brand · errors · gear · highlights · history · markdown editor · streak · tags · tooltips.

3. `webapp` + `extension` sweep

Package Files migrated
`packages/webapp/` 83
`packages/extension/` 11

Webapp — Settings, recruiter, jobs, squads, plus / billing / wallet / cores, game-center, standups, briefing, onboarding, welcome, verification, tags, team feedback, posts analytics, search, source pages, devcard.

Extension — Companion popup/menu/toggle/permission, `ShortcutLinks/*` hub, `DndModal`, `HijackingLoginStrip`.

4. Cleanup, delete v1, enable lint rule

Delete v1 source

  • `Button.tsx`, `Button.spec.tsx`, `QuaternaryButton.tsx`
  • `styles/components/buttons.css`, `tailwind/buttons.ts`
  • v1 helpers from `buttons/common.ts` (`SizeToClassName`, `IconOnlySizeToClassName`, `VariantToClassName`, `VariantColorToClassName`, `useGetIconWithSize`)
  • `@import` removed from `styles/components.css`; v1 plugin removed from `tailwind.config.ts`
  • `/dev/buttons` review page deleted (Storybook is now the canonical visual reference)

Storybook — remaining v1-importing stories migrated (`Drawer`, `ListDrawer`, `NavDrawer`, `BookmarkReminderModal`, `DiscountTimer`); `ButtonV2.stories.tsx` rewritten to drop the side-by-side v1 column.

Lint rule — `@dailydotdev/daily-dev-eslint-rules` wired into `eslint-config`; `no-raw-button-class` turned on as `error`. Scoped `overrides` for the v2 source files that legitimately reference the raw classes. eslint-rules plugin loader hardened so the legacy `no-custom-color` rule's broken require can no longer block plugin loading.

Final raw-class sweep — every site flagged once the rule was enabled (`MarkdownInput`, `RichTextInput`, `SignBackButton`, `BuyCore`, `LoginForm`, `KeywordSynonymModal`, `ModalTabs`, `tags/TagElement`, `FeedSettings/TagElement`, `TagSelection`, `BlockedWords`, extension `newtab/App.tsx`).

5. New: unified `TagChip` design

New shared `packages/shared/src/components/tags/TagChip.tsx` implementing the modern chip pattern: `#tag` text + vertical separator + follow (`+`) / unfollow (`×`) action button.

Three sizes follow the v2 button ladder:

  • `sm` (24 px) — article post / modal tag list, in-card chips
  • `md` (32 px) — tags directory page (`/tags`)
  • `lg` (40 px) — reserved for hero placements

Surfaces updated:

  • Article post / modal (`PostTagList`) — followed tags now show an `×` button so the followed/unfollowed states share one consistent design (was previously a plain `#tag` link with no action — read as "old / inconsistent")
  • Tags directory (`/tags` page) — chips upgraded from plain `TagLink` buttons to `TagChip` with the same follow/unfollow affordance, just at the bigger `md` size
  • `useFollowPostTags` hook now exposes `onUnfollowTag` alongside `onFollowTag`

6. New: raw-button audit migration

After the cutover, an audit found 16 raw `` elements that were missed. 9 of them are migrated to `ButtonV2` in this branch:

File What it does
`integrations/UserSourceIntegrationList` Disconnect-source close button (also fixes invalid nested-interactive HTML by splitting the nested `` out of the row CTA)
`profile/UserExperienceItem` "Verify company" chip + "+N more skills" expander
`post/NewComment` Composer trigger card
`help/HelpWidget` Sidebar Help-guide popover trigger
`squads/SharePostBar` Mobile "Choose from reading history"
`auth/RegistrationForm` Inline "Log in." link in the email-taken alert
`webapp/quiz/ai-fluency` Quiz answer options (Quiz variant + `pressed`)
`webapp/SettingsLayout` Redirect-banner CTA

The 7 audit candidates that are intentionally left as raw `

`:

  • `OnboardingV2.tsx` and `OnboardingChooserGrid.tsx` (6 buttons) — bespoke marketing styling (custom gradients, glow, shine animations) that would only need `!important` overrides if forced into `ButtonV2`
  • `RecruiterSetupChecklist.tsx` (1 button) — pure card-wrapper disclosure where all styling lives on the inner card div

Review-feedback fixes shipped on this branch

Captured during review of the stacked PRs and folded into the same migration so they land together:

  • Pressed/hover background sticking — `tertiary` `pressed` state in `tailwind/buttons-v2.ts` no longer paints a background; matches v1's text-only pressed cue and lets `mouseleave` clear the hover background after a click.
  • Icon-size regression on icon-only buttons — `buttonSizeToIconSizeV2` restored to v1's mapping (`XLarge → IconSize.XLarge`, `Small → IconSize.Small`, etc.), fixing the profile-page edit pen and other shrunk icons.
  • Icon-to-label gap regression on toolbar/header strips — `SizeToGapV2` tightened to v1's flat 4 px (`gap-1`) on Small / Medium / XSmall, with a single-step bump on Large / XLarge for hero CTAs.
  • `InteractionCounter` vertical alignment — switched the static counter span to `inline-flex items-center justify-start leading-none` so feed-card upvote / comment numbers center vertically.
  • `PostUpvotesCommentsCount` typography — `text-text-secondary typo-footnote font-medium` on the row, `defaultTypo={false} className="!text-text-secondary"` on the inner `ClickableText` so the post-page stats no longer render in the v1 grey/large form.
  • Post action bar padding — `CardActionBar` `className` on `PostActions` reduced from `py-2 pl-4 pr-6` to `p-2` so buttons spread evenly across the strip.
  • Engagement-bar `aria-label` — kept static `Upvote` / `Downvote` on `PostActions` and `ReaderRailActionBar` so webapp's `tests/PostPage.tsx` cancel-upvote / cancel-downvote assertions stay green.
  • Read post button icon — `OpenLinkIcon` rendered as outlined (removed the `secondary` prop) and pinned to the right side via `iconPosition={ButtonIconPosition.Right}` on `ReadArticleButton`.
  • Sidebar expand toggle — `SidebarArrowLeft` / `SidebarArrowRight` pinned to `IconSize.Size16` so the strokes read as outlined inside the 24 px `XSmall` button.
  • Profile cover edit button — fixed typo (`type={ButtonVariant.Float}` → `variant={ButtonVariant.Float}`) so the Float variant actually applies.
  • Post-detail action bar icon position — reverted the brief change that moved Comment / Bookmark / Copy icons to the right; only `Read post` keeps its right-side icon (matches the original v1 layout).
  • Asymmetric icon-side padding (general guideline) — every `ButtonV2` with both an icon and a label now applies tighter padding on the icon side (one Tailwind step smaller). Pulled `px-X` out of `SizeToClassNameV2` and added `HorizontalPaddingV2` (default symmetric) + `IconSidePaddingV2` (asymmetric for icon+label). Reference: Material 3, Apple HIG, GitHub Primer, Linear, Notion all tighten the icon side by ~25 % to compensate for the icon's optical mass.

Validation

  • `pnpm --filter @dailydotdev/shared lint` ✅ (clean with rule enabled)
  • `pnpm --filter webapp lint` ✅
  • `pnpm --filter extension lint` ✅
  • `pnpm --filter storybook lint` ✅
  • `NODE_ENV=test pnpm --filter @dailydotdev/shared test` — 1498/1498 pass
  • `NODE_ENV=test pnpm --filter webapp test` — 263/263 pass (PostPage 44/44 still green after revert + tag-chip change)
  • `pnpm --filter extension test` — 30/30 pass
  • `pnpm --filter webapp build` — TypeScript / Next compile clean (`/gear` SSG prerender DNS error is the env-only `api.local.fylla.dev` issue from main, unrelated)
  • `pnpm --filter extension build` — clean Chrome zip produced
  • `pnpm --filter storybook build` — clean
  • `node ./scripts/typecheck-strict-changed.js` — green; pre-existing strict violations from main are listed in `scripts/strict-skip.buttons-v2-migration.json` (151 files: original 149 + `useFollowPostTags.ts` + `pages/tags/index.tsx`, both flagged for pre-existing untyped-reduce-accumulator strict errors that exist on main)

Test plan

Use the preview domain (`*.preview.app.daily.dev`).

New / changed-since-last-review surfaces

  • Asymmetric padding — sample buttons with icons (Read post, Save, share menus, dropdown triggers) on `/posts/{id}`, feed cards, sidebar; visually check the icon side does not feel right-/left-heavy
  • `TagChip` — article post (`/posts/{id}`) — followed tags now show an `×` next to `#tag`; clicking unfollows; unfollowed tags still show the `+`
  • `TagChip` — tags directory (`/tags`) — every chip in the A–Z listing has a `+` (or `×` once followed); follow/unfollow round-trip works; chip is bigger than the article-post chip
  • Post-detail action bar — confirm Comment / Bookmark / Copy icons are back on the left of their labels; `Read post` keeps its icon on the right
  • Read post button — icon is outlined (not filled) and on the right side
  • Sidebar expand toggle — outlined arrow at 16 px inside the 24 px XSmall button
  • Profile cover edit pen — Float-variant styling applies (rounded background on hover/active)

Audit-migration regression checks

  • Slack integration → connected sources list → "Disconnect source" `×` works (no nested-button warnings in console)
  • Profile → experience entries → "Verify company" chip opens the verify modal; "+N skills" expander reveals all skills
  • `/posts/{id}` → "Share your thoughts" composer card opens the composer
  • Sidebar → Help-guide trigger toggles the popover
  • Mobile post composer → "Choose from reading history" opens the picker
  • `/register` with a taken email → inline "Log in." link switches to the login form
  • `/quiz/ai-fluency` → answer options highlight when selected
  • Settings page entered with `?redirectTo=...&redirectCopy=...` → banner CTA navigates back

Engagement-bar surfaces (re-verify after revert)

  • `/posts/{id}` post-detail action bar (Upvote / Downvote / Comment / Award / Bookmark / Copy); icons on left of labels; resize the column → label collapse still works
  • `/`, `/upvoted`, `/discussed` — feed-card engagement bar (grid + list)
  • `/bookmarks` — bookmark feed
  • `/squads/{handle}/post/{id}` — comment thread row actions
  • `/{username}/posts`, `/{username}/upvoted`, `/{username}/replies` — profile activity tabs
  • `/{username}/hottakes` — hot-take row
  • Mobile viewport (<420 px) on any post → `MobilePostFloatingBar` + label collapse
  • Reader modal → `ReaderRailActionBar` (desktop) + `ReaderFloatingActionBar` (mobile)

Shared sweep

  • Auth — `/login`, `/register`, `/forgotpassword`
  • Profile — `/{username}` + activity tabs
  • Squads — `/squads/{handle}`, `/squads/discover/my`, moderation, creation
  • Plus — `/plus`, `/plus/success`
  • Modals — report / share / give-award / bookmark-folder / feedback / achievement showcase
  • Notifications — `/notifications` + bell
  • Live rooms — create / join / raise hand / chat
  • Onboarding / funnels — `/onboarding`, `/welcome`, plus pricing
  • Opportunity — `/jobs/{id}`, CV upload, candidate status
  • Recruiter — `/recruiter/dashboard`, recruiter intro modal
  • Boost — boost-post / boost-source / boost-squad modals
  • Briefing — brief banner, plus upgrade CTA
  • Settings/feeds — feed settings drawer, AI section, my feed heading

Webapp

  • Settings — `/account/profile`, `/account/security`, `/account/api`, `/account/notifications`, `/account/subscription`, `/account/invite`, `/account/feedback`
  • DevCard — `/devcard` editor + share dialog
  • Recruiter — `/recruiter/dashboard`, `/recruiter/review/*`, `/recruiter/[opportunityId]/payment`, opportunity create/edit
  • Jobs — `/jobs`, `/jobs/{id}`, `/jobs/how-it-works`, `/jobs/{id}/done`
  • Posts analytics — `/posts/{id}/analytics`
  • Plus / billing — `/plus`, `/plus/success`, `/cores`, `/wallet`
  • Game center — `/game-center`
  • Standups — `/standups`
  • Tags — `/tags`, `/tags/{tag}`
  • Team feedback — `/team/feedback`

Extension (Chrome)

  • Companion — open on any article: bookmark, more menu, toggle, permission prompts
  • New tab — DnD modal, hijacking login strip, error fallback
  • Shortcut links — empty state + add via hub menu, manual editor, import flow

Lint-rule sweep surfaces

  • `/login` — forgot password link + sign-back row
  • `/cores` and any wallet entry → `BuyCore`
  • Modals using `ModalTabs` (account settings tabs, feed settings tabs)
  • `/feeds` settings → tag picker
  • `/onboarding` → tag picker
  • `/feeds//edit` → blocked words → confirm hover turns the icon red
  • Bookmark reminder dropdown
  • Storybook — `Atoms / ButtonV2`, `Drawer / *`, `Modals / BookmarkReminderModal`, `Onboarding / DiscountTimer`

Cross-cutting

  • Focus rings + hover tints unchanged across surfaces
  • Sticky CTAs + modals render at correct height
  • Sample mobile viewport — Small / Medium / Large size tokens render unchanged

Notes

Made with Cursor

Preview domain

https://feat-buttons-v2-migration-consol.preview.app.daily.dev

tsahimatsliah and others added 11 commits May 4, 2026 00:02
Flips MedalBadgeIcon to follow the project-wide convention every other
icon ships with (IconPrimary={Outlined}, IconSecondary={Filled}). Was
the only icon in the codebase with the props reversed, which made the
toggle pattern (`secondary={isActive}` and ButtonV2's `iconPressed`)
behave inverted against the surrounding icon family.

Updates every explicit caller to preserve the existing rendered visual:
- Strips `secondary` from sites that were rendering the outlined art
  (NotificationItemAvatar top-reader badge, LiveRoomTileActions award,
  ProfileActions award button, AwardButton, PostAnalytics + SquadAnalytics
  Awards stat tile, CommentAwardActions count badge, UserEngagementSections
  badge chip, Leaderboard RankBadge / TopRankBadge).
- Adds `secondary` where the site rendered the filled art and depended on
  the previous default (OpportunityBenefits "Private until you say yes",
  ProfileAchievements + AchievementsWidget header glyph,
  AchievementTrackerButton fallback, game-center DataTile awards).
- Inverts `secondary={!awarded}` to `secondary={!!awarded}` on
  ReaderRailActionBar so its outline-when-idle / filled-when-awarded
  behaviour stays identical post-flip.
- Updates /dev/buttons OLD vignette to invert its boolean and rewrites
  the NEW CardAction vignette to the cleaner `icon` + `iconPressed`
  pattern (outlined idle, filled pressed) made possible by the flip.

Function-ref usages in the profile/account menus (`icon: MedalBadgeIcon`)
intentionally aren't touched — they pass `secondary={isActive}` through
ProfileSectionItem and now match the rest of the menu's icon family
(outlined when inactive, filled when active).

Prerequisite for the v2 button system migration so consumers can use
`<MedalBadgeIcon />` for the idle/outline state and
`<MedalBadgeIcon secondary />` for the pressed/filled state without
re-fixing the inversion at every CardAction call site.

Made-with: Cursor
Replaces every v1 QuaternaryButton + v1 Button engagement-row caller
in the shared package with the v2 button system:

- packages/shared/src/components/buttons/BookmarkButton.tsx
  rewritten to compose CardAction. New engagement-bar API
  (post + density + pressed + onClick + label + count) replaces the
  v1 buttonProps bag; reminder dropdown still wraps the trigger as
  before. All previous QuaternaryButton call-site noise (icon swap,
  pressed-color tinting, counter slot) now lives inside CardAction.

- packages/shared/src/components/post/PostActions.tsx
  Post-detail action strip now renders CardActionBar layout="between"
  with one CardAction per affordance (Upvote / Downvote / Comment /
  Award / Bookmark / Copy). Adds a new shared
  usePostActionsLabelVisibility hook (extracted from /dev/buttons)
  that mirrors the v1 ResizeObserver-driven label-collapse mechanic
  exactly: every CardAction renders with labelVisible always-on and
  the hook toggles the .card-action-content wrapper hidden class so
  the row collapses to icon-only when it would overflow.

- packages/shared/src/components/post/MobilePostFloatingBar.tsx
  Mobile sticky bar migrated to CardActionBar layout="between"
  inside the existing surface-float blurred container.

- packages/shared/src/components/post/PostAwardAction.tsx
  Award affordance migrated to CardAction with iconPressed swap
  (outline -> filled medal). Featured-award thumbnail still uses
  iconSizeToClassName lock so the asset doesn't get rescaled.

- packages/shared/src/components/cards/common/ActionButtons.tsx
  Feed grid + list + signal cards migrated to CardActionBar
  layout="feedCard" + density="compact" per the CardAction width
  contract (5 actions inside the production 272-340 px feed-card
  clamp).

- packages/shared/src/components/post/reader/ReaderRailActionBar.tsx
  Desktop reader rail migrated to CardActionBar layout="between"
  with full action set (U / D / Comment / Award / Bookmark / Copy).

- packages/shared/src/components/post/reader/ReaderFloatingActionBar.tsx
  Floating reader bar migrated to CardActionBar default + density
  compact, dropping the v1 !h-8 !w-8 !rounded-10 size hacks.

- packages/shared/src/features/profile/components/hotTakes/HotTakeItem.tsx
  Owner edit/delete buttons -> ButtonV2; upvote affordance ->
  CardAction with density compact + iconPressed.

- packages/shared/src/components/comments/CommentActionButtons.tsx
  Comment row engagement (Upvote / Downvote / Reply / Award /
  Share) migrated to CardActionBar layout="compact" + CardAction
  density compact. Options menu trigger upgraded to ButtonV2.

Supporting changes:
- packages/shared/src/components/buttons/CardActionBar.tsx
  Wrapped in forwardRef so PostActions can attach the
  ResizeObserver ref directly to the bar element.
- packages/shared/src/components/buttons/CardAction.tsx
  Extends HTMLAttributes (id, role, aria-*, data-*, type, etc.)
  so call sites can keep the same hooks/IDs they had on v1.

CommentActionButtons.spec.tsx passes (16/16). Existing strict type
errors in CommentAwardActions / PostAwardAction / ProfileActions /
analytics pages are pre-existing on origin/main (verified with a
stash + re-run) — no new strict errors introduced.

Made-with: Cursor
Co-authored-by: Cursor <cursoragent@cursor.com>
… actions

PR 1 made the upvote/downvote `aria-label` on `PostActions` and
`ReaderRailActionBar` state-aware ("Remove upvote" / "More like this") to
mirror the v1 Tooltip *content*. That conflated two layers — v1 also set
an explicit `aria-label="Upvote"` / `aria-label="Downvote"` on the
button, which won over the Tooltip-derived aria-label and is what the
webapp `__tests__/PostPage.tsx` cancel-upvote / cancel-downvote
assertions match against (`findByLabelText('Upvote')`).

Restore the static labels so the accessible name is stable across vote
state, matching v1 + the PostPage tests. Feed-card `ActionButtons`,
`MobilePostFloatingBar`, and `BookmarkButton` keep their state-aware
labels (they always were that way in v1 — Tooltip-as-aria-label with no
explicit override).

Co-authored-by: Cursor <cursoragent@cursor.com>
Three review-feedback fixes for PR 1:

- InteractionCounter centred its number against the icon by accident
  only — the static branch was a `flex flex-col` with the text glued
  to the top of its 20 px box, so the digit visibly floated above the
  icon's optical centre. Switch the static render to
  `inline-flex items-center` (with `leading-none`) so the digit sits
  on the icon's mid-line; the animated render keeps `flex-col` for
  the slide transition.
- PostActions container had asymmetric horizontal padding
  (`py-2 pl-4 pr-6`) that left a visible empty band on the right of
  the action strip. Drop to `p-2` so left/right matches top/bottom and
  the `between` layout actually distributes the actions across the
  full width of the bordered box.
- PostUpvotesCommentsCount stats line above the action bar was still
  rendering at v1's `text-text-tertiary typo-callout` (grey, 15 px),
  which read as "the old one" next to the new v2 action chrome. Move
  to `text-text-secondary typo-footnote font-medium` and override the
  `ClickableText` defaults the same way so the upvote / repost / award
  links inherit the new contrast and size.

Co-authored-by: Cursor <cursoragent@cursor.com>
The strict-changed CI runs `tsc --strict` against every file the PR
modified, regardless of who introduced the violations. The button
migration touched 30+ files purely to swap `Button -> ButtonV2`,
`QuaternaryButton -> CardAction`, or to add `secondary` on
`MedalBadgeIcon`, but several of those files carry pre-existing
strict violations unrelated to the migration:

- `comments/CommentAwardActions.tsx` — AwardEntity receiver narrowing
- `post/PostAwardAction.tsx` — same
- `profile/ProfileActions.tsx` — query param string narrowing
- `webapp/pages/posts/[id]/analytics/index.tsx` — null/undefined
  narrowing across 9 sites in the analytics page
- `webapp/pages/squads/[handle]/analytics.tsx` — Squad narrowing

All five files were already failing strict on origin/main, verified
against a clean `4204abdf9` worktree. Add them to the existing
`strictSkipList` per the same convention used for the customize-new-tab
and micro-interactions-ads branches; the underlying bugs are tracked
to a dedicated cleanup PR.

Also fix `InteractionCounter`'s `value` prop type — the only consumer
(`CardAction`) already passes `count ?? 0`, so the historical
`number | null` was overly broad and caused 7 strict errors that the
recent polish commit dragged into scope by editing the file.

Co-authored-by: Cursor <cursoragent@cursor.com>
PR2/3/4 of the migration touch 100+ files purely for mechanical
Button -> ButtonV2 import/symbol swaps. Each one already had pre-
existing strict-mode violations on origin/main unrelated to the
migration (modal close-handler null types, AwardEntity narrowing,
organization manageSeats null narrowing, dynamic import any-types,
etc.).

Move the long list of skipped paths to a sibling JSON file so the
main script stays readable, and keep the inline 5-file list (PR1
scope) for context. The skip is tracked in a dedicated cleanup PR.

Co-authored-by: Cursor <cursoragent@cursor.com>
Two regressions surfaced after the migration started rolling across
real surfaces:

1. Pressed bg "stuck" on engagement-bar Tertiary actions.
   V2's `tertiary` `pressed` state shipped a 12% brand-alpha bg —
   identical to `hover`. After clicking Upvote / Bookmark / Award and
   moving the cursor away, `:hover` released but the matching pressed
   bg kept showing, so users perceived "the hover background never
   disappears". V1's `tertiary` `pressed` only changed the icon /
   text colour and relied on the icon swap (outline → secondary) for
   the toggled-on cue. Restore that contract — pressed sets `color`
   and explicitly clears `background` to `none`. Float / Subtle /
   Option inherit the same fix via `variations.tertiary(color)`.

2. Icons shrunk by one full step everywhere.
   V2's first-pass `buttonSizeToIconSizeV2` chased an "industry-
   standard 50% ratio" by mapping each ButtonSize to the next-name-
   down IconSize (Medium 40 px button → 20 px icon). Two visible
   regressions on migration:
   - Profile-page edit pens (XSmall, 24 px button) dropped from 20 →
     16 px icon, leaving the affordance nearly invisible inside an
     already-tiny target — the user-reported "almost impossible to
     click" case.
   - Toolbar Small buttons lost a third of their icon footprint
     (24 → 16 px).
   Restore v1's same-name mapping (XSmall button → XSmall icon, etc.)
   so every migrated call site keeps its v1 visual scale. CardAction
   bypasses this map (sets `size` per density on the icon prop) so
   engagement-bar icon sizes are unaffected.

Co-authored-by: Cursor <cursoragent@cursor.com>
v2 first-pass scaled `gap` with button size (gap-1 → gap-2.5),
combined with the removal of v1's `-ml-2 mr-1` negative-margin
trick this made every icon+label button 4–22 px wider than its
v1 sibling. On toolbars and header strips that pack 3-5 buttons
in a row (header rail, MyFeedHeading, FeedSettings strip) the
group visibly spread out vs the v1 surfaces all the call sites
were originally tuned for.

Restore v1's flat 4 px gap (gap-1) on the most-common sizes
(XSmall / Small / Medium) and a single-step bump on Large /
XLarge so hero CTAs keep a touch of breathing room.

Result: header / feed-settings / toolbar groups render at the
same width as v1 with no consumer-side spacing changes needed.

Co-authored-by: Cursor <cursoragent@cursor.com>
…ttonV2

Sweep of every remaining v1 `Button` import across `packages/shared/`
(auth forms, modals, sidebars, profile/squad/recruiter surfaces, live
rooms, onboarding, opportunity, organizations, briefing, integrations,
shortcuts, etc.) to `ButtonV2`. Identifier swaps:

- Button       -> ButtonV2
- ButtonProps  -> ButtonV2Props
- ButtonGroup  -> ButtonV2Group

ButtonSize / ButtonVariant / ButtonColor / ButtonIconPosition / IconType
keep the same identifier (re-exported unchanged from ButtonV2).

Wrapper components OptionsButton, UploadButton, and ToggleClickbaitShield
now compose ButtonV2 internally. The 6 PR-1 files that import
`ButtonColor` from the v1 path are re-pointed at ButtonV2 for consistency.

v1 `Button.tsx`, `Button.spec.tsx`, and `QuaternaryButton.tsx` are kept
untouched on this branch — they remain marked `@deprecated` and are still
imported by webapp/extension call sites that PR 3 will migrate. PR 4
deletes the v1 source.

Co-authored-by: Cursor <cursoragent@cursor.com>
…tension to ButtonV2

Sweep of every remaining v1 \`Button\` import outside packages/shared/.

- 83 webapp files (settings, recruiter, jobs, squads, plus, billing,
  wallet, cores, game-center, standups, briefing, onboarding, welcome,
  verification, tags, team feedback, etc.)
- 11 extension files (companion popup/menu/toggle/permission,
  ShortcutLinks/* hub, DndModal, HijackingLoginStrip)

Identifier swaps (word-boundary safe):

- Button       -> ButtonV2
- ButtonProps  -> ButtonV2Props
- ButtonGroup  -> ButtonV2Group

ButtonSize / ButtonVariant / ButtonColor / ButtonIconPosition / IconType
keep the same identifier (re-exported unchanged from ButtonV2).

After this PR, the only remaining v1 \`Button\` callers are
\`QuaternaryButton.tsx\` and \`Button.spec.tsx\` — both kept
intentionally as the safety net until PR 4 deletes the v1 source.

Co-authored-by: Cursor <cursoragent@cursor.com>
- Delete v1 source: Button.tsx, Button.spec.tsx, QuaternaryButton.tsx,
  the buttons.css stylesheet and the Tailwind buttons plugin
- Trim packages/shared/src/components/buttons/common.ts to only the v2
  helpers (drop SizeToClassName, IconOnlySizeToClassName,
  VariantToClassName, VariantColorToClassName, useGetIconWithSize)
- Drop the v1 import from styles/components.css and the buttons plugin
  from tailwind.config.ts so the deleted assets are no longer wired up
- Migrate the remaining Storybook stories that still imported v1
  (Drawer / ListDrawer / NavDrawer / BookmarkReminderModal /
  DiscountTimer) and rewrite ButtonV2.stories to drop the v1 column
- Delete the /dev/buttons review page; Storybook is now the canonical
  visual reference
- Wire the @dailydotdev/daily-dev-eslint-rules plugin into
  eslint-config and enable no-raw-button-class as an error, with
  scoped overrides for ButtonV2.tsx, CardAction.tsx, CardActionBar.tsx,
  buttons/common.ts, Buttons.mdx and the buttons-v2 preset
- Make the eslint-rules plugin loader resilient: the legacy
  no-custom-color rule used to crash module load by trying to require
  the shared Tailwind config as JS, which now lives as TS and broke
  ESLint plugin discovery; lazy-resolve it
- Sweep the last raw v1 class usages flagged by the new rule:
  - Loader inside MarkdownInput / RichTextInput drops btn-loader
  - SignBackButton and BuyCore inline the visual contract with
    design-system tokens instead of relying on btn-* CSS
  - LoginForm forgot-password ClickableText drops btn-primary
  - KeywordSynonymModal Create button uses ButtonVariant.Primary
  - ModalTabs tab pills drop the btn class
  - tags/TagElement, FeedSettings/TagElement and TagSelection drop
    btn / btn-tag
  - BlockedWords replaces btn-tagBlocked with explicit hover/active
    text + status-error icon utilities
  - extension newtab fallback uses ButtonV2 instead of a raw button
- Verified: shared lint, webapp lint, extension lint, storybook lint,
  shared tests (1498 passing), webapp tests (263 passing), webapp
  build (compile + tsc), chrome extension build, storybook build

Co-authored-by: Cursor <cursoragent@cursor.com>
@vercel
Copy link
Copy Markdown

vercel Bot commented May 4, 2026

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

Project Deployment Actions Updated (UTC)
daily-webapp Ready Ready Preview May 4, 2026 8:44am
storybook Ready Ready Preview May 4, 2026 8:44am

Request Review

Both ReadArticleButton's default icon and getReadPostButtonIcon's
non-Twitter branch were passing `<OpenLinkIcon secondary />` which
renders the FILLED variant. Every other OpenLinkIcon caller across
the codebase (boost campaigns, write/preview, brief, post analytics,
feedback card, NewSourceModal, profile experience, …) uses the
outlined variant via plain `<OpenLinkIcon />`.

This pre-existed the v2 migration but became visible during review
of the new Read article button. Drop the `secondary` flag in both
spots so the post page Read article button matches the outlined
icon style used everywhere else.

Co-authored-by: Cursor <cursoragent@cursor.com>
The sidebar expand/collapse toggle uses an XSmall ButtonV2 (24x24)
with the default icon size of IconSize.XSmall (20px). At 83% icon-
to-button ratio the outlined sidebar arrow's strokes scale up so
heavily that the whole icon visually reads as the FILLED variant,
even though the code is correctly passing the outlined SVG with no
`secondary` prop.

This was also the case on v1 (same XSmall→XSmall icon mapping) but
became visible during the v2 review. Pinning the icon to
IconSize.Size16 (16px) gives the icon comfortable padding inside
the 24px button so the outline strokes render at their intended
weight and the icon clearly reads as outlined.

Other XSmall buttons keep the default 20px icon — the dense look is
only a problem here because the outlined sidebar shape is composed
of thin strokes that get visually amplified at near-fill scale.

Co-authored-by: Cursor <cursoragent@cursor.com>
The post-detail engagement bar (PostActions) now renders every
button with `iconPosition={ButtonIconPosition.Right}` so the
hierarchy reads `[label] [icon]` — matching the Read article button
on feed cards (`Read post →`) instead of the v1 / first-pass v2
`[icon] [label]` order.

CardAction gains an opt-in `iconPosition` prop (defaults to
`Left`) so every other engagement-bar surface — feed grid card,
list card, signal card, comment row, mobile sticky bar, reader
rail / floating bar — keeps its current icon-on-left layout
unchanged. Only PostActions opts into the right-side icon.

BookmarkButton forwards the `iconPosition` prop through to
CardAction so the post-detail bookmark button (which uses the
BookmarkButton wrapper) flips along with the rest of the row.

Co-authored-by: Cursor <cursoragent@cursor.com>
The Edit profile pen-icon button below the cover image on the
public profile page was using `type={ButtonVariant.Float}`. The
HTML `type` attribute is for `<button>` semantics ("button" /
"submit" / "reset") — passing a v2 variant token there left the
underlying `<a>` with `type="tertiaryFloat"` (harmless on anchors)
but more importantly meant the button rendered with the default
v2 variant instead of the intended Float pill.

Rename to `variant={ButtonVariant.Float}` so the button picks up
the correct Float variant — surface-float background, rounded-12,
hover tint — matching the rest of the profile header CTAs.

Co-authored-by: Cursor <cursoragent@cursor.com>
tsahimatsliah and others added 4 commits May 4, 2026 10:15
Pulls horizontal padding out of `SizeToClassNameV2` and applies an
asymmetric scheme when an icon is paired with a label: the side that
hosts the icon gets one Tailwind step tighter padding so the icon's
visual weight no longer pushes the content off-center.

Reference platforms (Material 3, Apple HIG, GitHub Primer, Linear,
Notion) all tighten the icon side by ~25 % to compensate for the
icon's optical mass. Label-only and icon-only buttons are unchanged.

Co-authored-by: Cursor <cursoragent@cursor.com>
Adds a shared `TagChip` component (`packages/shared/src/components/
tags/TagChip.tsx`) implementing the modern chip pattern: text +
vertical separator + follow (`+`) / unfollow (`×`) action button.
Sizes follow the v2 ladder: sm (24 px) for in-card chips, md (32 px)
for the tags directory, lg (40 px) for hero placements.

- Article post / modal tag list now uses TagChip at "sm" and the
  followed state shows an `×` button (previously was just a plain
  text link, which read as an "old" inconsistent design).
- Tags directory page (`/tags`) uses TagChip at "md" with the same
  follow/unfollow affordance for consistency.
- `useFollowPostTags` hook now exposes `onUnfollowTag` alongside
  `onFollowTag` so the followed-state action can dispatch.

Co-authored-by: Cursor <cursoragent@cursor.com>
Migrates the audit-flagged raw `<button>` elements that should have
been part of the v2 cutover but were missed:

- shared/integrations/UserSourceIntegrationList — splits the nested
  close button out of the row CTA (was invalid nested-interactive
  HTML), replaces both with ButtonV2.
- shared/profile/UserExperienceItem — Verify-company chip and the
  "+N more skills" expander.
- shared/post/NewComment — composer trigger card.
- shared/help/HelpWidget — sidebar Help-guide popover trigger.
- shared/squads/SharePostBar — mobile "Choose from reading history".
- shared/auth/RegistrationForm — inline "Log in." CTA in the
  email-taken alert.
- webapp/quiz/ai-fluency — quiz answer options (Quiz variant).
- webapp/SettingsLayout — redirectTo banner CTA.

The bespoke marketing buttons in OnboardingV2 / OnboardingChooserGrid
(custom gradients, glow, shine animations) and pure card-wrapper
disclosure buttons (RecruiterSetupChecklist) are intentionally left
as-is — forcing them into ButtonV2 would only require `!important`
overrides without any visual or behavioral benefit.

Adds `useFollowPostTags` and `pages/tags/index` to the
buttons-v2-migration strict-skip list — both have pre-existing
unrelated `reduce` accumulator strict violations on origin/main.

Co-authored-by: Cursor <cursoragent@cursor.com>
…tion-consolidated

Co-authored-by: Cursor <cursoragent@cursor.com>

# Conflicts:
#	packages/shared/src/components/banners/ShortcutsExtensionPromo.tsx
tsahimatsliah and others added 2 commits May 4, 2026 11:31
The Read post button on shared-post bodies in the post page / modal
was inheriting `iconPosition={Right}` from `ReadArticleButton`'s
default (set up so feed-card Read post buttons have the icon trailing
the label). On the post page / modal the visual hierarchy is
different — the icon should sit before the label so it reads as a
"Read" affordance rather than a "go elsewhere" affordance.

Pins both `ReadArticleButton` callsites in `SharePostContent` to
`iconPosition={ButtonIconPosition.Left}`. Feed-card Read post buttons
keep the existing right-side icon.

Co-authored-by: Cursor <cursoragent@cursor.com>
The previous asymmetric-padding step (one Tailwind step tighter on
the icon side) was too gentle — buttons still read as right- /
left-heavy depending on icon position.

Drops the icon-side padding by another step so the icon : label
padding ratio sits around **1 : 2**, matching Material 3, Apple HIG,
GitHub Primer, Linear, and Notion:

  XSmall  px-2 → pl-1   / pr-2     (4  / 8  px)
  Small   px-3 → pl-1.5 / pr-3     (6  / 12 px)
  Medium  px-4 → pl-2   / pr-4     (8  / 16 px)
  Large   px-6 → pl-4   / pr-6     (16 / 24 px)
  XLarge  px-7 → pl-5   / pr-7     (20 / 28 px)

Mirror values applied for icon-on-right callsites.

Co-authored-by: Cursor <cursoragent@cursor.com>
The unified TagChip rendered the followed and unfollowed states with
the same chip + separator + action layout (only the icon swapped from
+ to ×). Visually they read as "two follow controls" rather than
"followed vs not followed".

Reverts the followed state to the stable bordered-link pill (no
separator, no action button) so:

  - **Followed**     → passive label, plain bordered chip with hover
  - **Not followed** → filled chip + separator + + action

Unfollow lives on the dedicated tag page, not inline. Removes the
now-unused `onUnfollow` prop from `TagChip`, the `onUnfollowTag`
plumbing from `useFollowPostTags`, and the `onUnfollowTags` callsite
in the tags directory page.

Co-authored-by: Cursor <cursoragent@cursor.com>
Copy link
Copy Markdown
Contributor

@rebelchris rebelchris left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe rather just call ButtonV2 Button that would mean 500 less file changes?

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