From 76f8de258cbfd1d9afc1d199f3026acc34a52d9e Mon Sep 17 00:00:00 2001 From: npeter83 Date: Tue, 11 Aug 2026 04:04:06 +0200 Subject: [PATCH 1/3] =?UTF-8?q?refactor(ui):=20R10=20=E2=80=94=20.btn-prim?= =?UTF-8?q?ary=20recipe=20for=20the=20primary/action=20buttons?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a paint-only .btn-primary recipe (solid accent bg + accent-fg text + hover:opacity dim) in index.css @layer components, alongside the selection-paint recipes (.pill/.chip/.is-current). Migrate ~38 primary/action buttons across 24 files off the hand-rolled 'bg-accent text-accent-fg hover:opacity-90', keeping each site's geometry (padding/radius/font/gap/disabled/shadow). Deliberately NOT migrated: accent BADGES/labels (spans), the chat message bubble, selection toggles (tableHeader — R8 popover territory), and the round play FAB (hover:scale, not the opacity-dim primary idiom). Three previously hover-less primary buttons (SettingsPanel/SetupWizard/DraftSaveBar) gain the standard hover dim for consistency. Gate green (tsc/eslint-0/prettier/knip/vitest 79); self-E2E confirmed a migrated button computes to accent/accent-fg and both recipe rules load. --- frontend/src/components/About.tsx | 2 +- frontend/src/components/Banner.tsx | 2 +- frontend/src/components/ChannelPage.tsx | 2 +- frontend/src/components/ChatThread.tsx | 2 +- frontend/src/components/ConfirmProvider.tsx | 6 +----- frontend/src/components/DownloadCenter.tsx | 8 ++++---- frontend/src/components/DownloadDialog.tsx | 2 +- frontend/src/components/ErrorDialog.tsx | 2 +- frontend/src/components/Feed.tsx | 6 +++--- frontend/src/components/KeyGate.tsx | 2 +- frontend/src/components/Messages.tsx | 2 +- frontend/src/components/OnboardingWizard.tsx | 6 +++--- frontend/src/components/PlayerModal.tsx | 4 ++-- frontend/src/components/Playlists.tsx | 2 +- frontend/src/components/PlexBrowse.tsx | 2 +- frontend/src/components/ProfileEditor.tsx | 4 ++-- frontend/src/components/SettingsPanel.tsx | 2 +- frontend/src/components/SetupWizard.tsx | 2 +- frontend/src/components/ShareDialog.tsx | 4 ++-- frontend/src/components/VideoCard.tsx | 2 +- frontend/src/components/VideoEditor.tsx | 5 +---- frontend/src/components/WatchPage.tsx | 2 +- frontend/src/components/Welcome.tsx | 2 +- frontend/src/components/ui/DraftSaveBar.tsx | 2 +- frontend/src/index.css | 12 ++++++++++++ 25 files changed, 46 insertions(+), 41 deletions(-) diff --git a/frontend/src/components/About.tsx b/frontend/src/components/About.tsx index 25c1c3dc..46d2888c 100644 --- a/frontend/src/components/About.tsx +++ b/frontend/src/components/About.tsx @@ -72,7 +72,7 @@ export default function About({ diff --git a/frontend/src/components/Banner.tsx b/frontend/src/components/Banner.tsx index 888b8f7d..e0fae05f 100644 --- a/frontend/src/components/Banner.tsx +++ b/frontend/src/components/Banner.tsx @@ -8,7 +8,7 @@ const TONES: Record = accent: { bar: "bg-accent/15 border-accent/30", icon: "text-accent", - action: "bg-accent text-accent-fg hover:opacity-90", + action: "btn-primary", }, warning: { bar: "bg-warn/15 border-warn/30", diff --git a/frontend/src/components/ChannelPage.tsx b/frontend/src/components/ChannelPage.tsx index 34b6682a..318518b7 100644 --- a/frontend/src/components/ChannelPage.tsx +++ b/frontend/src/components/ChannelPage.tsx @@ -460,7 +460,7 @@ export default function ChannelPage({ diff --git a/frontend/src/components/DownloadCenter.tsx b/frontend/src/components/DownloadCenter.tsx index ed0cbefe..cf4a61a9 100644 --- a/frontend/src/components/DownloadCenter.tsx +++ b/frontend/src/components/DownloadCenter.tsx @@ -390,7 +390,7 @@ function MetaEditModal({ job, onClose }: { job: DownloadJob; onClose: () => void @@ -448,7 +448,7 @@ function ErrorModal({ onRetry(); onClose(); }} - className="px-3 py-1.5 rounded-lg text-sm font-medium bg-accent text-accent-fg hover:opacity-90 transition" + className="px-3 py-1.5 rounded-lg text-sm font-medium btn-primary transition" > {t("downloads.actions.retry")} @@ -607,7 +607,7 @@ function QuotaModal({ @@ -894,7 +894,7 @@ export default function DownloadCenter() { diff --git a/frontend/src/components/DownloadDialog.tsx b/frontend/src/components/DownloadDialog.tsx index 36caadd7..0fc3f72e 100644 --- a/frontend/src/components/DownloadDialog.tsx +++ b/frontend/src/components/DownloadDialog.tsx @@ -98,7 +98,7 @@ export default function DownloadDialog({ diff --git a/frontend/src/components/ErrorDialog.tsx b/frontend/src/components/ErrorDialog.tsx index c212cfa7..6ba217a6 100644 --- a/frontend/src/components/ErrorDialog.tsx +++ b/frontend/src/components/ErrorDialog.tsx @@ -14,7 +14,7 @@ export default function ErrorDialog() {
diff --git a/frontend/src/components/Feed.tsx b/frontend/src/components/Feed.tsx index 5d49bcf6..f932e961 100644 --- a/frontend/src/components/Feed.tsx +++ b/frontend/src/components/Feed.tsx @@ -522,7 +522,7 @@ export default function Feed({ {isDemo ? ( @@ -530,7 +530,7 @@ export default function Feed({ <> @@ -711,7 +711,7 @@ export default function Feed({ {filters.q.trim() && !isDemo && ( @@ -221,7 +221,7 @@ export default function OnboardingWizard({ me, onClose }: { me: Me; onClose: ()
@@ -252,7 +252,7 @@ export default function OnboardingWizard({ me, onClose }: { me: Me; onClose: () endOnboarding(false); onClose(); }} - className="inline-flex items-center justify-center gap-2 px-5 py-3 rounded-xl font-semibold bg-accent text-accent-fg hover:opacity-90 transition w-full" + className="inline-flex items-center justify-center gap-2 px-5 py-3 rounded-xl font-semibold btn-primary transition w-full" > {t("onboarding.done.done")} diff --git a/frontend/src/components/PlayerModal.tsx b/frontend/src/components/PlayerModal.tsx index bb403baf..0b22c319 100644 --- a/frontend/src/components/PlayerModal.tsx +++ b/frontend/src/components/PlayerModal.tsx @@ -1088,7 +1088,7 @@ export default function PlayerModal({ href={`https://www.youtube.com/watch?v=${currentVideoId}`} target="_blank" rel="noreferrer" - className="inline-flex items-center justify-center gap-2 px-4 py-2 rounded-xl font-semibold bg-accent text-accent-fg hover:opacity-90 transition" + className="inline-flex items-center justify-center gap-2 px-4 py-2 rounded-xl font-semibold btn-primary transition" > {t("player.openOnYoutube")} @@ -1314,7 +1314,7 @@ export default function PlayerModal({ className={ "shrink-0 inline-flex items-center gap-1.5 text-sm px-3 py-1.5 rounded-lg transition " + (watched - ? "bg-accent text-accent-fg shadow-xs hover:opacity-90" + ? "btn-primary shadow-xs" : "text-muted hover:text-fg hover:bg-surface border border-border") } > diff --git a/frontend/src/components/Playlists.tsx b/frontend/src/components/Playlists.tsx index 95e1fe29..a1bb8774 100644 --- a/frontend/src/components/Playlists.tsx +++ b/frontend/src/components/Playlists.tsx @@ -630,7 +630,7 @@ export default function Playlists() { diff --git a/frontend/src/components/PlexBrowse.tsx b/frontend/src/components/PlexBrowse.tsx index 02892b03..4a871832 100644 --- a/frontend/src/components/PlexBrowse.tsx +++ b/frontend/src/components/PlexBrowse.tsx @@ -673,7 +673,7 @@ function ActionBtn({ onClick={onClick} disabled={disabled} className={`inline-flex items-center gap-2 rounded-xl px-3 py-2 text-sm font-medium transition disabled:opacity-50 ${ - primary ? "bg-accent text-accent-fg shadow-xs hover:opacity-90" : "glass-card glass-hover" + primary ? "btn-primary shadow-xs" : "glass-card glass-hover" }`} > diff --git a/frontend/src/components/ProfileEditor.tsx b/frontend/src/components/ProfileEditor.tsx index 685436eb..8822ca19 100644 --- a/frontend/src/components/ProfileEditor.tsx +++ b/frontend/src/components/ProfileEditor.tsx @@ -147,7 +147,7 @@ export default function ProfileEditor({ onClose }: { onClose: () => void }) { {!formOpen ? ( @@ -272,7 +272,7 @@ export default function ProfileEditor({ onClose }: { onClose: () => void }) { diff --git a/frontend/src/components/SettingsPanel.tsx b/frontend/src/components/SettingsPanel.tsx index 8b88677c..eab9b47b 100644 --- a/frontend/src/components/SettingsPanel.tsx +++ b/frontend/src/components/SettingsPanel.tsx @@ -480,7 +480,7 @@ function SignInMethods({ me }: { me: Me }) { diff --git a/frontend/src/components/SetupWizard.tsx b/frontend/src/components/SetupWizard.tsx index 7c863756..9b53654d 100644 --- a/frontend/src/components/SetupWizard.tsx +++ b/frontend/src/components/SetupWizard.tsx @@ -278,7 +278,7 @@ export default function SetupWizard() { @@ -352,7 +352,7 @@ function LinkShare({ job }: { job: DownloadJob }) { diff --git a/frontend/src/components/VideoCard.tsx b/frontend/src/components/VideoCard.tsx index 749045c5..b2a1f77b 100644 --- a/frontend/src/components/VideoCard.tsx +++ b/frontend/src/components/VideoCard.tsx @@ -277,7 +277,7 @@ function Thumb({ title={t("card.continueTitle")} aria-label={t("card.continue")} className={clsx( - "inline-flex items-center justify-center gap-1.5 font-semibold bg-accent text-accent-fg shadow-lg hover:opacity-90 transition", + "inline-flex items-center justify-center gap-1.5 font-semibold btn-primary shadow-lg transition", small ? "w-9 h-9 rounded-full" : "px-3 py-1.5 rounded-lg text-sm", )} > diff --git a/frontend/src/components/VideoEditor.tsx b/frontend/src/components/VideoEditor.tsx index a2070ab8..b23de353 100644 --- a/frontend/src/components/VideoEditor.tsx +++ b/frontend/src/components/VideoEditor.tsx @@ -630,10 +630,7 @@ export default function VideoEditor({ job, onClose }: { job: DownloadJob; onClos diff --git a/frontend/src/components/Welcome.tsx b/frontend/src/components/Welcome.tsx index 0671e38d..5e2dd325 100644 --- a/frontend/src/components/Welcome.tsx +++ b/frontend/src/components/Welcome.tsx @@ -501,7 +501,7 @@ function AuthCard() { diff --git a/frontend/src/components/ui/DraftSaveBar.tsx b/frontend/src/components/ui/DraftSaveBar.tsx index 9c15fd20..2dd3dd5a 100644 --- a/frontend/src/components/ui/DraftSaveBar.tsx +++ b/frontend/src/components/ui/DraftSaveBar.tsx @@ -71,7 +71,7 @@ export function DraftSaveBar({ data-testid="draft-save" onClick={onSave} disabled={saving || !dirty} - className="flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-sm bg-accent text-accent-fg font-medium shadow-md shadow-accent/20 disabled:opacity-40 transition" + className="flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-sm btn-primary font-medium shadow-md shadow-accent/20 disabled:opacity-40 transition" > {saving ? labels.saving : labels.save} diff --git a/frontend/src/index.css b/frontend/src/index.css index 3ae68bfb..9fce8e50 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -496,6 +496,18 @@ body { color: var(--accent-fg); border-color: var(--accent); } + /* .btn-primary — the primary/ACTION button paint (solid accent). Distinct from the selection + recipes above: this is "the primary action", not "selected among options". PAINT-ONLY like + .chip-solid — a site keeps its own geometry (padding/radius/font/gap/disabled:opacity/transition). + Migrated sites MUST drop `bg-accent text-accent-fg hover:opacity-90` so this @layer-components + rule isn't beaten by those utilities. No focus-visible — @layer base already rings every button. */ + .btn-primary { + background-color: var(--accent); + color: var(--accent-fg); + } + .btn-primary:hover:not(:disabled) { + opacity: 0.9; + } } .glass-hover:hover { border-color: color-mix(in srgb, var(--accent) 55%, transparent); From fc186e1230b3bc8013ad92cd9a1541fe3fdb9c04 Mon Sep 17 00:00:00 2001 From: npeter83 Date: Tue, 11 Aug 2026 04:20:10 +0200 Subject: [PATCH 2/3] =?UTF-8?q?refactor(ui):=20address=20/code-review=20on?= =?UTF-8?q?=20.btn-primary=20=E2=80=94=20tone=20family=20+=20doc=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Generalize the recipe into a .btn-{tone} family: add .btn-danger / .btn-warn siblings sharing one hover-dim, and migrate the two hand-rolled tone-action buttons (ConfirmProvider danger confirm, Banner warning action) onto them, so primary/danger/warn action paints can't drift. - Reword the recipe comment: it owns paint + one standard hover-dim (not 'PAINT-ONLY' — that mislabel obscured the hover behaviour). - Drop the now-redundant hover:opacity-90 from Banner's action-button base (each tone's recipe supplies the hover). The 3 previously hover-less primary buttons keep the standard hover-dim gained in the prior commit (intentional consistency; now accurately documented). Gate green (tsc/eslint-0/prettier/knip/vitest 79); self-E2E confirmed btn-danger/btn-warn compute to their tone/tone-fg tokens. --- frontend/src/components/Banner.tsx | 4 ++-- frontend/src/components/ConfirmProvider.tsx | 2 +- frontend/src/index.css | 23 +++++++++++++++------ 3 files changed, 20 insertions(+), 9 deletions(-) diff --git a/frontend/src/components/Banner.tsx b/frontend/src/components/Banner.tsx index e0fae05f..ecdc536d 100644 --- a/frontend/src/components/Banner.tsx +++ b/frontend/src/components/Banner.tsx @@ -13,7 +13,7 @@ const TONES: Record = warning: { bar: "bg-warn/15 border-warn/30", icon: "text-warn", - action: "bg-warn text-warn-fg hover:opacity-90", + action: "btn-warn", }, }; @@ -42,7 +42,7 @@ export default function Banner({ {action && ( diff --git a/frontend/src/components/ConfirmProvider.tsx b/frontend/src/components/ConfirmProvider.tsx index 84150ec9..6051ff53 100644 --- a/frontend/src/components/ConfirmProvider.tsx +++ b/frontend/src/components/ConfirmProvider.tsx @@ -116,7 +116,7 @@ export function ConfirmProvider({ children }: { children: ReactNode }) { onClick={() => close(c.id)} className={`px-4 py-2 rounded-lg text-sm font-semibold transition ${ multi ? "w-full" : "" - } ${c.danger ? "bg-danger text-danger-fg hover:opacity-90" : "btn-primary"}`} + } ${c.danger ? "btn-danger" : "btn-primary"}`} > {c.label || t("common.confirm")} diff --git a/frontend/src/index.css b/frontend/src/index.css index 9fce8e50..4f69c0c5 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -496,16 +496,27 @@ body { color: var(--accent-fg); border-color: var(--accent); } - /* .btn-primary — the primary/ACTION button paint (solid accent). Distinct from the selection - recipes above: this is "the primary action", not "selected among options". PAINT-ONLY like - .chip-solid — a site keeps its own geometry (padding/radius/font/gap/disabled:opacity/transition). - Migrated sites MUST drop `bg-accent text-accent-fg hover:opacity-90` so this @layer-components - rule isn't beaten by those utilities. No focus-visible — @layer base already rings every button. */ + /* .btn-{tone} — solid ACTION-button paint (the "primary/danger/warn action", distinct from the + selection recipes above which are "selected among options"). Each sets its tone fill + contrasting + fg; all share ONE standard hover-dim. Like .chip-solid this owns paint (+ that single hover + behaviour), NOT geometry — a site keeps its own padding/radius/font/gap/disabled:opacity/shadow. + Migrated sites MUST drop `bg-{tone} text-{tone}-fg hover:opacity-90` so these @layer-components + rules aren't beaten by those utilities. No focus-visible — @layer base already rings every button. */ .btn-primary { background-color: var(--accent); color: var(--accent-fg); } - .btn-primary:hover:not(:disabled) { + .btn-danger { + background-color: var(--danger); + color: var(--danger-fg); + } + .btn-warn { + background-color: var(--warn); + color: var(--warn-fg); + } + .btn-primary:hover:not(:disabled), + .btn-danger:hover:not(:disabled), + .btn-warn:hover:not(:disabled) { opacity: 0.9; } } From b5cf355ae450847b43b0932fbe0f00c9e9a48095 Mon Sep 17 00:00:00 2001 From: npeter83 Date: Tue, 11 Aug 2026 04:23:26 +0200 Subject: [PATCH 3/3] =?UTF-8?q?chore(release):=200.61.1=20=E2=80=94=20.btn?= =?UTF-8?q?-{tone}=20action-button=20recipe=20family?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit VERSION bump + chores release note for the R10 .btn-primary/.btn-danger/.btn-warn recipe unification (refactor; a few primary buttons gained the standard hover). --- VERSION | 2 +- frontend/src/lib/releaseNotes.ts | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/VERSION b/VERSION index 0b094550..6bcc0367 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.61.0 +0.61.1 diff --git a/frontend/src/lib/releaseNotes.ts b/frontend/src/lib/releaseNotes.ts index dac29f9a..3f45e06e 100644 --- a/frontend/src/lib/releaseNotes.ts +++ b/frontend/src/lib/releaseNotes.ts @@ -14,6 +14,13 @@ export interface ReleaseEntry { } export const RELEASE_NOTES: ReleaseEntry[] = [ + { + version: "0.61.1", + date: "2026-08-11", + chores: [ + "Unified the primary, danger and warning action-button styling onto shared recipes, so the same kind of button looks and behaves the same everywhere (a few primary buttons gained the standard subtle hover; nothing about what the buttons do changed).", + ], + }, { version: "0.61.0", date: "2026-08-11",