From a86fb8891bde5845e8d41f05315b35d9d37d8067 Mon Sep 17 00:00:00 2001 From: lawrencecchen <54008264+lawrencecchen@users.noreply.github.com> Date: Fri, 3 Jul 2026 02:38:02 -0700 Subject: [PATCH 01/12] Tighten Download for Mac divider padding Pull the split-button divider closer to the caret across both sizes. Default: caret zone px-2.5 -> px-2, download zone pr-3 -> pr-[11px]. Small: caret zone px-2 -> pl-1.5 pr-2, download zone pr-2.5 -> pr-2. Layout only; no copy or behavior change. Co-Authored-By: Claude Opus 4.8 (1M context) --- web/app/[locale]/components/download-button.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/web/app/[locale]/components/download-button.tsx b/web/app/[locale]/components/download-button.tsx index 885ab9b0ea62..025b4cf746ad 100644 --- a/web/app/[locale]/components/download-button.tsx +++ b/web/app/[locale]/components/download-button.tsx @@ -59,11 +59,11 @@ export function DownloadButton({ // padding, so the divider sits a touch closer to the caret than to "Mac". const downloadZone = `flex items-center transition-colors hover:bg-background/[0.04] dark:hover:bg-background/[0.03] ${ isSmall - ? "gap-2 pl-4 pr-2.5 py-1.5 text-xs" - : "gap-2.5 pl-5 pr-3 py-2.5 text-[15px]" + ? "gap-2 pl-4 pr-2 py-1.5 text-xs" + : "gap-2.5 pl-5 pr-[11px] py-2.5 text-[15px]" }`; const caretZone = `group flex items-center justify-center transition-colors hover:bg-background/[0.04] dark:hover:bg-background/[0.03] data-[popup-open]:bg-background/[0.04] dark:data-[popup-open]:bg-background/[0.03] ${ - isSmall ? "px-2" : "px-2.5" + isSmall ? "pl-1.5 pr-2" : "px-2" }`; const captureMac = () => From 79cdb6b11abdd9534f69ea7e0e27778daa1ab932 Mon Sep 17 00:00:00 2001 From: lawrencecchen <54008264+lawrencecchen@users.noreply.github.com> Date: Fri, 3 Jul 2026 16:18:50 -0700 Subject: [PATCH 02/12] Fix Safari hover tint escaping DownloadButton's rounded corners Give the rounded pill its own backing layer via [transform:translateZ(0)]. Safari does not clip the zones' `transition-colors` hover tint to the `rounded-full` corners once that tint animates into its own compositing layer, so on hover the tint leaked past the corners as square edges (Chrome clips it correctly, so it only reproduced in Safari). Promoting the clip container makes the zones composite inside its clip. Verified in Safari with a static repro forcing the composited-child state: square corner leak without the transform, clean rounded corners with it. Co-Authored-By: Claude Opus 4.8 (1M context) --- web/app/[locale]/components/download-button.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/web/app/[locale]/components/download-button.tsx b/web/app/[locale]/components/download-button.tsx index 025b4cf746ad..1d76705f9bbd 100644 --- a/web/app/[locale]/components/download-button.tsx +++ b/web/app/[locale]/components/download-button.tsx @@ -110,7 +110,13 @@ export function DownloadButton({ return ( <>
Date: Fri, 3 Jul 2026 16:56:07 -0700 Subject: [PATCH 03/12] Trim divider padding and stop the Safari hover jump Padding: shave 1px from each side of the divider per size (default label->divider 11->10px, divider->caret 8->7px; small 8->7px, 6->5px). Hover jump: drop the CSS transitions on the hover tint (zones) and the caret opacity. Animating them promoted the sub-pixel-positioned zone/icon into a WebKit compositing layer that snapped to the device-pixel grid, so the label/caret visibly jumped on hover (Safari only, worst at the small size). Instant hover states never promote, so the pill stays stable. The pill keeps its translateZ(0) backing layer to guard the rounded-corner clip of the tint. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../[locale]/components/download-button.tsx | 26 +++++++++++-------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/web/app/[locale]/components/download-button.tsx b/web/app/[locale]/components/download-button.tsx index 1d76705f9bbd..727b34d2aef5 100644 --- a/web/app/[locale]/components/download-button.tsx +++ b/web/app/[locale]/components/download-button.tsx @@ -57,13 +57,18 @@ export function DownloadButton({ // under `dark:` to keep the split affordance equally quiet. // Slightly more breathing room after the label than the caret zone's // padding, so the divider sits a touch closer to the caret than to "Mac". - const downloadZone = `flex items-center transition-colors hover:bg-background/[0.04] dark:hover:bg-background/[0.03] ${ + // The hover tint is applied without a CSS transition on purpose: animating + // background-color/opacity here promotes the sub-pixel-positioned zone into + // its own WebKit compositing layer, which snaps to the device-pixel grid and + // makes the label/caret visibly jump on hover (Safari only, worst at the + // small size). Instant tint avoids the promotion, so nothing shifts. + const downloadZone = `flex items-center hover:bg-background/[0.04] dark:hover:bg-background/[0.03] ${ isSmall - ? "gap-2 pl-4 pr-2 py-1.5 text-xs" - : "gap-2.5 pl-5 pr-[11px] py-2.5 text-[15px]" + ? "gap-2 pl-4 pr-[7px] py-1.5 text-xs" + : "gap-2.5 pl-5 pr-2.5 py-2.5 text-[15px]" }`; - const caretZone = `group flex items-center justify-center transition-colors hover:bg-background/[0.04] dark:hover:bg-background/[0.03] data-[popup-open]:bg-background/[0.04] dark:data-[popup-open]:bg-background/[0.03] ${ - isSmall ? "pl-1.5 pr-2" : "px-2" + const caretZone = `group flex items-center justify-center hover:bg-background/[0.04] dark:hover:bg-background/[0.03] data-[popup-open]:bg-background/[0.04] dark:data-[popup-open]:bg-background/[0.03] ${ + isSmall ? "pl-[5px] pr-2" : "pl-[7px] pr-2" }`; const captureMac = () => @@ -100,7 +105,7 @@ export function DownloadButton({ strokeWidth="2.25" strokeLinecap="round" strokeLinejoin="round" - className="opacity-40 transition-opacity group-hover:opacity-70 group-data-[popup-open]:opacity-70 dark:opacity-30 dark:group-hover:opacity-55 dark:group-data-[popup-open]:opacity-55" + className="opacity-40 group-hover:opacity-70 group-data-[popup-open]:opacity-70 dark:opacity-30 dark:group-hover:opacity-55 dark:group-data-[popup-open]:opacity-55" aria-hidden="true" > @@ -111,11 +116,10 @@ export function DownloadButton({ <>
Date: Fri, 3 Jul 2026 17:20:30 -0700 Subject: [PATCH 04/12] Drop pill translateZ layer and add /debug tuner for both sizes Remove the `[transform:translateZ(0)]` from the pill. Forcing the pill onto a GPU layer makes WebKit re-rasterize the sub-pixel-positioned zones on every hover repaint and snap them to the device-pixel grid, which is the label/caret "jump" on hover (Safari only, worst small). The layer was added for the corner-tint clip, but that clip only broke while a CSS transition promoted the child; with the transitions already removed, the child never gets its own layer, so overflow-hidden clips correctly without the forced layer. Verified a promoted zone snaps its pixels (compare AE>0) vs an unpromoted one. Add an opt-in `padOverride` prop (inline padding, undefined in prod so rendering is unchanged) and a /debug page that renders both sizes live, drives their divider padding with sliders, and has diagnostic toggles (re-add transition / force translateZ) that should reintroduce the jump, to confirm the cause with a real pointer. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../[locale]/components/download-button.tsx | 50 ++++- web/app/[locale]/debug/page.tsx | 195 ++++++++++++++++++ 2 files changed, 237 insertions(+), 8 deletions(-) create mode 100644 web/app/[locale]/debug/page.tsx diff --git a/web/app/[locale]/components/download-button.tsx b/web/app/[locale]/components/download-button.tsx index 727b34d2aef5..e9f3b2f534a6 100644 --- a/web/app/[locale]/components/download-button.tsx +++ b/web/app/[locale]/components/download-button.tsx @@ -16,14 +16,27 @@ import { ctaButtonStyle } from "./cta-styles"; import { PlatformIcon } from "./platform-icons"; import { WaitlistDialog } from "./waitlist-dialog"; +// Optional per-instance padding override in px, used only by the /debug tuner +// to fine-tune both sizes live. When absent (every production call site), the +// Tailwind classes below are the single source of truth and rendering is +// unchanged. downloadRight = gap LEFT of divider; caretLeft = gap RIGHT of it. +export type PadOverride = { + downloadLeft?: number; + downloadRight?: number; + caretLeft?: number; + caretRight?: number; +}; + export function DownloadButton({ size = "default", location = "hero", className, + padOverride, }: { size?: "default" | "sm"; location?: string; className?: string; + padOverride?: PadOverride; }) { const t = useTranslations("common"); const tp = useTranslations("platforms"); @@ -48,6 +61,14 @@ export function DownloadButton({ const onConfirmationPage = pathname === DOWNLOAD_CONFIRMATION_PATH; const macHref = onConfirmationPage ? DOWNLOAD_URL : DOWNLOAD_CONFIRMATION_HREF; + // Inline padding overrides win over the Tailwind classes; undefined in prod. + const downloadStyle = padOverride + ? { paddingLeft: padOverride.downloadLeft, paddingRight: padOverride.downloadRight } + : undefined; + const caretStyle = padOverride + ? { paddingLeft: padOverride.caretLeft, paddingRight: padOverride.caretRight } + : undefined; + // The split button is one pill with two zones (Mac download + platform caret) // that tint independently on hover. `overflow-hidden` clips the hover tint to // the rounded corners; the divider and caret are kept barely-there so the @@ -115,23 +136,35 @@ export function DownloadButton({ return ( <>
{onConfirmationPage ? ( - + {macIcon} {t("downloadForMac")} ) : ( - + {macIcon} {t("downloadForMac")} @@ -142,6 +175,7 @@ export function DownloadButton({ {caretIcon} diff --git a/web/app/[locale]/debug/page.tsx b/web/app/[locale]/debug/page.tsx new file mode 100644 index 000000000000..d63756e88f9a --- /dev/null +++ b/web/app/[locale]/debug/page.tsx @@ -0,0 +1,195 @@ +"use client"; + +// Live tuner for the real DownloadButton. Renders both sizes at once and drives +// each one's padding via the component's `padOverride` prop, so the divider +// spacing can be dialed in per size and copied back into download-button.tsx. +// Diagnostic toggles (re-add transition, drop translateZ, magnify) exist to +// pin down the Safari hover jump. Reach it at /debug. Not linked from anywhere. + +import { useState } from "react"; +import { DownloadButton } from "../components/download-button"; + +type Pad = { + downloadLeft: number; + downloadRight: number; + caretLeft: number; + caretRight: number; +}; +type Size = "default" | "sm"; + +const INITIAL: Record = { + default: { downloadLeft: 20, downloadRight: 10, caretLeft: 7, caretRight: 8 }, + sm: { downloadLeft: 16, downloadRight: 7, caretLeft: 5, caretRight: 8 }, +}; + +const FIELDS: { key: keyof Pad; label: string; accent: string }[] = [ + { key: "downloadRight", label: "Left of divider (download pr)", accent: "#3b82f6" }, + { key: "caretLeft", label: "Right of divider (caret pl)", accent: "#f97316" }, + { key: "downloadLeft", label: "Outer left (before icon)", accent: "#9ca3af" }, + { key: "caretRight", label: "Outer right (after caret)", accent: "#9ca3af" }, +]; + +// px -> Tailwind spacing token (v4: 1 unit = 4px; .5 steps ok; else arbitrary) +function tw(prefix: string, px: number) { + if (px % 4 === 0) return `${prefix}-${px / 4}`; + if (px % 2 === 0) return `${prefix}-${px / 4}`; // e.g. 10 -> 2.5, 6 -> 1.5 + return `${prefix}-[${px}px]`; +} + +export default function DownloadButtonDebug() { + const [pad, setPad] = useState>(() => structuredClone(INITIAL)); + const [dark, setDark] = useState(false); + const [zoom, setZoom] = useState(3); + const [reTransition, setReTransition] = useState(false); + const [forceTranslateZ, setForceTranslateZ] = useState(false); + const [copied, setCopied] = useState(false); + + const set = (size: Size, key: keyof Pad, v: number) => + setPad((p) => ({ ...p, [size]: { ...p[size], [key]: v } })); + + const classesFor = (size: Size) => { + const p = pad[size]; + return { + download: `${tw("pl", p.downloadLeft)} ${tw("pr", p.downloadRight)}`, + caret: + p.caretLeft === p.caretRight + ? tw("px", p.caretLeft) + : `${tw("pl", p.caretLeft)} ${tw("pr", p.caretRight)}`, + }; + }; + + const snippet = () => { + const d = classesFor("default"); + const s = classesFor("sm"); + return [ + "// download zone padding", + `default download: ${d.download} caret: ${d.caret}`, + `sm download: ${s.download} caret: ${s.caret}`, + "", + "// raw px", + `default: ${JSON.stringify(pad.default)}`, + `sm: ${JSON.stringify(pad.sm)}`, + ].join("\n"); + }; + + const copy = async () => { + await navigator.clipboard.writeText(snippet()); + setCopied(true); + window.setTimeout(() => setCopied(false), 1200); + }; + + const wrapClass = [ + reTransition ? "dbg-retrans" : "", + forceTranslateZ ? "dbg-tz" : "", + ] + .filter(Boolean) + .join(" "); + + return ( +
+ {/* Diagnostic CSS scoped to the previews */} + + +

DownloadButton debug

+

+ Both sizes render the real component; sliders drive its{" "} + padOverride. Hover each + button to check the jump. Copy classes into{" "} + + web/app/[locale]/components/download-button.tsx + + . +

+ + {/* toolbar */} +
+ + + + +
+ + {/* previews + sliders, one column per size */} +
+ {(["default", "sm"] as const).map((size) => ( +
+
+ {size} +
+ +
+
+ +
+
+ +
+ {FIELDS.map((f) => ( +
+
+ + + {f.label} + + set(size, f.key, Number(e.target.value))} + className="w-14 rounded border border-border bg-transparent px-2 py-0.5 text-right text-sm" /> +
+ set(size, f.key, Number(e.target.value))} + className="w-full" /> +
+ ))} +
+ download {classesFor(size).download} · caret {classesFor(size).caret} +
+
+
+ ))} +
+ + {/* output */} +
+ +
+          {snippet()}
+        
+
+
+ ); +} From 2aad3099838a447e35171099170149f438f7f6a9 Mon Sep 17 00:00:00 2001 From: lawrencecchen <54008264+lawrencecchen@users.noreply.github.com> Date: Fri, 3 Jul 2026 17:22:37 -0700 Subject: [PATCH 05/12] Pin theme tokens in /debug preview so the pill has correct contrast Co-Authored-By: Claude Opus 4.8 (1M context) --- web/app/[locale]/debug/page.tsx | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/web/app/[locale]/debug/page.tsx b/web/app/[locale]/debug/page.tsx index d63756e88f9a..fdde775a19b6 100644 --- a/web/app/[locale]/debug/page.tsx +++ b/web/app/[locale]/debug/page.tsx @@ -6,7 +6,7 @@ // Diagnostic toggles (re-add transition, drop translateZ, magnify) exist to // pin down the Safari hover jump. Reach it at /debug. Not linked from anywhere. -import { useState } from "react"; +import { type CSSProperties, useState } from "react"; import { DownloadButton } from "../components/download-button"; type Pad = { @@ -139,14 +139,20 @@ export default function DownloadButtonDebug() {
From eae114b7df3be486c502d91308c21db931fb2977 Mon Sep 17 00:00:00 2001 From: lawrencecchen <54008264+lawrencecchen@users.noreply.github.com> Date: Fri, 3 Jul 2026 17:51:52 -0700 Subject: [PATCH 06/12] Apply tuned divider padding (both sizes) default: pl-5 pr-2 / caret pl-1.5 pr-[7px] (20/8, 6/7) sm: pl-3 pr-[7px] / caret pl-[5px] pr-[7px] (12/7, 5/7) Sync /debug initial values to match. Co-Authored-By: Claude Opus 4.8 (1M context) --- web/app/[locale]/components/download-button.tsx | 6 +++--- web/app/[locale]/debug/page.tsx | 6 ++++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/web/app/[locale]/components/download-button.tsx b/web/app/[locale]/components/download-button.tsx index e9f3b2f534a6..6e61239c6aae 100644 --- a/web/app/[locale]/components/download-button.tsx +++ b/web/app/[locale]/components/download-button.tsx @@ -85,11 +85,11 @@ export function DownloadButton({ // small size). Instant tint avoids the promotion, so nothing shifts. const downloadZone = `flex items-center hover:bg-background/[0.04] dark:hover:bg-background/[0.03] ${ isSmall - ? "gap-2 pl-4 pr-[7px] py-1.5 text-xs" - : "gap-2.5 pl-5 pr-2.5 py-2.5 text-[15px]" + ? "gap-2 pl-3 pr-[7px] py-1.5 text-xs" + : "gap-2.5 pl-5 pr-2 py-2.5 text-[15px]" }`; const caretZone = `group flex items-center justify-center hover:bg-background/[0.04] dark:hover:bg-background/[0.03] data-[popup-open]:bg-background/[0.04] dark:data-[popup-open]:bg-background/[0.03] ${ - isSmall ? "pl-[5px] pr-2" : "pl-[7px] pr-2" + isSmall ? "pl-[5px] pr-[7px]" : "pl-1.5 pr-[7px]" }`; const captureMac = () => diff --git a/web/app/[locale]/debug/page.tsx b/web/app/[locale]/debug/page.tsx index fdde775a19b6..1f6b7a580ede 100644 --- a/web/app/[locale]/debug/page.tsx +++ b/web/app/[locale]/debug/page.tsx @@ -17,9 +17,11 @@ type Pad = { }; type Size = "default" | "sm"; +// Mirrors the values baked into download-button.tsx so /debug opens on the +// currently-shipped spacing. const INITIAL: Record = { - default: { downloadLeft: 20, downloadRight: 10, caretLeft: 7, caretRight: 8 }, - sm: { downloadLeft: 16, downloadRight: 7, caretLeft: 5, caretRight: 8 }, + default: { downloadLeft: 20, downloadRight: 8, caretLeft: 6, caretRight: 7 }, + sm: { downloadLeft: 12, downloadRight: 7, caretLeft: 5, caretRight: 7 }, }; const FIELDS: { key: keyof Pad; label: string; accent: string }[] = [ From a4135816782ecd7b84927cb03cf4ba0bdd6537a0 Mon Sep 17 00:00:00 2001 From: lawrencecchen <54008264+lawrencecchen@users.noreply.github.com> Date: Fri, 3 Jul 2026 17:59:56 -0700 Subject: [PATCH 07/12] Render pill padding as inline px from a config The tuned values include odd px (5,7) that map to arbitrary Tailwind classes like pr-[7px]. That class silently did not resolve on the base-ui Menu.Trigger button, so the caret rendered padding-right:0 and the live button did not match the /debug tuner (which uses inline px via padOverride). Move padding into a per-size PILL_PADDING config applied as inline styles on both zones, so the exact values render identically in dev, prod, and /debug. padOverride now merges over the config. default: download 20/8, caret 6/7 sm: download 12/7, caret 5/7 Co-Authored-By: Claude Opus 4.8 (1M context) --- .../[locale]/components/download-button.tsx | 41 +++++++++++-------- 1 file changed, 24 insertions(+), 17 deletions(-) diff --git a/web/app/[locale]/components/download-button.tsx b/web/app/[locale]/components/download-button.tsx index 6e61239c6aae..69d26224b5d4 100644 --- a/web/app/[locale]/components/download-button.tsx +++ b/web/app/[locale]/components/download-button.tsx @@ -16,16 +16,22 @@ import { ctaButtonStyle } from "./cta-styles"; import { PlatformIcon } from "./platform-icons"; import { WaitlistDialog } from "./waitlist-dialog"; -// Optional per-instance padding override in px, used only by the /debug tuner -// to fine-tune both sizes live. When absent (every production call site), the -// Tailwind classes below are the single source of truth and rendering is -// unchanged. downloadRight = gap LEFT of divider; caretLeft = gap RIGHT of it. +// Per-size pill padding in px. downloadRight = gap LEFT of the divider, +// caretLeft = gap RIGHT of it. Applied as inline styles (not Tailwind classes) +// because the tuned values include odd px like 7 that have no spacing token, +// and because arbitrary `pr-[7px]` did not reliably resolve on the base-ui +// Menu.Trigger button; inline px renders the exact value in dev and prod and +// matches the /debug tuner one-to-one. export type PadOverride = { downloadLeft?: number; downloadRight?: number; caretLeft?: number; caretRight?: number; }; +const PILL_PADDING = { + default: { downloadLeft: 20, downloadRight: 8, caretLeft: 6, caretRight: 7 }, + sm: { downloadLeft: 12, downloadRight: 7, caretLeft: 5, caretRight: 7 }, +} as const; export function DownloadButton({ size = "default", @@ -61,13 +67,17 @@ export function DownloadButton({ const onConfirmationPage = pathname === DOWNLOAD_CONFIRMATION_PATH; const macHref = onConfirmationPage ? DOWNLOAD_URL : DOWNLOAD_CONFIRMATION_HREF; - // Inline padding overrides win over the Tailwind classes; undefined in prod. - const downloadStyle = padOverride - ? { paddingLeft: padOverride.downloadLeft, paddingRight: padOverride.downloadRight } - : undefined; - const caretStyle = padOverride - ? { paddingLeft: padOverride.caretLeft, paddingRight: padOverride.caretRight } - : undefined; + // Resolve padding from the per-size config, with the optional /debug override + // merged in. Applied inline below so odd px render exactly. + const pad = { ...PILL_PADDING[isSmall ? "sm" : "default"], ...padOverride }; + const downloadStyle = { + paddingLeft: pad.downloadLeft, + paddingRight: pad.downloadRight, + }; + const caretStyle = { + paddingLeft: pad.caretLeft, + paddingRight: pad.caretRight, + }; // The split button is one pill with two zones (Mac download + platform caret) // that tint independently on hover. `overflow-hidden` clips the hover tint to @@ -84,13 +94,10 @@ export function DownloadButton({ // makes the label/caret visibly jump on hover (Safari only, worst at the // small size). Instant tint avoids the promotion, so nothing shifts. const downloadZone = `flex items-center hover:bg-background/[0.04] dark:hover:bg-background/[0.03] ${ - isSmall - ? "gap-2 pl-3 pr-[7px] py-1.5 text-xs" - : "gap-2.5 pl-5 pr-2 py-2.5 text-[15px]" - }`; - const caretZone = `group flex items-center justify-center hover:bg-background/[0.04] dark:hover:bg-background/[0.03] data-[popup-open]:bg-background/[0.04] dark:data-[popup-open]:bg-background/[0.03] ${ - isSmall ? "pl-[5px] pr-[7px]" : "pl-1.5 pr-[7px]" + isSmall ? "gap-2 py-1.5 text-xs" : "gap-2.5 py-2.5 text-[15px]" }`; + const caretZone = + "group flex items-center justify-center hover:bg-background/[0.04] dark:hover:bg-background/[0.03] data-[popup-open]:bg-background/[0.04] dark:data-[popup-open]:bg-background/[0.03]"; const captureMac = () => posthog.capture("cmuxterm_download_clicked", { location, platform: "mac" }); From d38c658ffa5a0853ae39a29d912d0296ccacce79 Mon Sep 17 00:00:00 2001 From: lawrencecchen <54008264+lawrencecchen@users.noreply.github.com> Date: Fri, 3 Jul 2026 18:02:49 -0700 Subject: [PATCH 08/12] Show /debug buttons at actual size in front-screen context Default to 1x zoom and dark bg, and render each size next to its real neighbors (View on GitHub for default, Docs/Blog nav for sm) so padding is tuned at true on-screen scale. Co-Authored-By: Claude Opus 4.8 (1M context) --- web/app/[locale]/debug/page.tsx | 38 +++++++++++++++++++++++++++++---- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/web/app/[locale]/debug/page.tsx b/web/app/[locale]/debug/page.tsx index 1f6b7a580ede..7f8d5087bde3 100644 --- a/web/app/[locale]/debug/page.tsx +++ b/web/app/[locale]/debug/page.tsx @@ -40,8 +40,8 @@ function tw(prefix: string, px: number) { export default function DownloadButtonDebug() { const [pad, setPad] = useState>(() => structuredClone(INITIAL)); - const [dark, setDark] = useState(false); - const [zoom, setZoom] = useState(3); + const [dark, setDark] = useState(true); + const [zoom, setZoom] = useState(1); const [reTransition, setReTransition] = useState(false); const [forceTranslateZ, setForceTranslateZ] = useState(false); const [copied, setCopied] = useState(false); @@ -156,9 +156,39 @@ export default function DownloadButtonDebug() { } as CSSProperties } > -
+
+ {size === "sm" && ( + <> + Docs + Blog + + )} + {size === "default" && ( + + View on GitHub + + )}
From 9f39a9f0b45d62b100be7b4f307c17a6e00984bd Mon Sep 17 00:00:00 2001 From: lawrencecchen <54008264+lawrencecchen@users.noreply.github.com> Date: Sat, 4 Jul 2026 02:35:11 -0700 Subject: [PATCH 09/12] Add per-size reset buttons to /debug tuner Co-Authored-By: Claude Opus 4.8 (1M context) --- web/app/[locale]/debug/page.tsx | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/web/app/[locale]/debug/page.tsx b/web/app/[locale]/debug/page.tsx index 7f8d5087bde3..2ebefe7de711 100644 --- a/web/app/[locale]/debug/page.tsx +++ b/web/app/[locale]/debug/page.tsx @@ -49,6 +49,9 @@ export default function DownloadButtonDebug() { const set = (size: Size, key: keyof Pad, v: number) => setPad((p) => ({ ...p, [size]: { ...p[size], [key]: v } })); + const resetSize = (size: Size) => + setPad((p) => ({ ...p, [size]: { ...INITIAL[size] } })); + const classesFor = (size: Size) => { const p = pad[size]; return { @@ -135,8 +138,16 @@ export default function DownloadButtonDebug() {
{(["default", "sm"] as const).map((size) => (
-
- {size} +
+ + {size} + +
Date: Sat, 4 Jul 2026 02:37:01 -0700 Subject: [PATCH 10/12] Add per-value reset to each /debug slider Co-Authored-By: Claude Opus 4.8 (1M context) --- web/app/[locale]/debug/page.tsx | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/web/app/[locale]/debug/page.tsx b/web/app/[locale]/debug/page.tsx index 2ebefe7de711..9de2e463964a 100644 --- a/web/app/[locale]/debug/page.tsx +++ b/web/app/[locale]/debug/page.tsx @@ -212,9 +212,20 @@ export default function DownloadButtonDebug() { style={{ background: f.accent }} /> {f.label} - set(size, f.key, Number(e.target.value))} - className="w-14 rounded border border-border bg-transparent px-2 py-0.5 text-right text-sm" /> + + + set(size, f.key, Number(e.target.value))} + className="w-14 rounded border border-border bg-transparent px-2 py-0.5 text-right text-sm" /> +
set(size, f.key, Number(e.target.value))} From fe8d7cdcf83ac00650b93d92491074fd732b99d0 Mon Sep 17 00:00:00 2001 From: lawrencecchen <54008264+lawrencecchen@users.noreply.github.com> Date: Sat, 4 Jul 2026 02:39:48 -0700 Subject: [PATCH 11/12] Update tuned divider padding default: download 20/9, caret 7/11 sm: download 12/7, caret 5/9 Co-Authored-By: Claude Opus 4.8 (1M context) --- web/app/[locale]/components/download-button.tsx | 4 ++-- web/app/[locale]/debug/page.tsx | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/web/app/[locale]/components/download-button.tsx b/web/app/[locale]/components/download-button.tsx index 69d26224b5d4..8a376470281f 100644 --- a/web/app/[locale]/components/download-button.tsx +++ b/web/app/[locale]/components/download-button.tsx @@ -29,8 +29,8 @@ export type PadOverride = { caretRight?: number; }; const PILL_PADDING = { - default: { downloadLeft: 20, downloadRight: 8, caretLeft: 6, caretRight: 7 }, - sm: { downloadLeft: 12, downloadRight: 7, caretLeft: 5, caretRight: 7 }, + default: { downloadLeft: 20, downloadRight: 9, caretLeft: 7, caretRight: 11 }, + sm: { downloadLeft: 12, downloadRight: 7, caretLeft: 5, caretRight: 9 }, } as const; export function DownloadButton({ diff --git a/web/app/[locale]/debug/page.tsx b/web/app/[locale]/debug/page.tsx index 9de2e463964a..10b60312109f 100644 --- a/web/app/[locale]/debug/page.tsx +++ b/web/app/[locale]/debug/page.tsx @@ -20,8 +20,8 @@ type Size = "default" | "sm"; // Mirrors the values baked into download-button.tsx so /debug opens on the // currently-shipped spacing. const INITIAL: Record = { - default: { downloadLeft: 20, downloadRight: 8, caretLeft: 6, caretRight: 7 }, - sm: { downloadLeft: 12, downloadRight: 7, caretLeft: 5, caretRight: 7 }, + default: { downloadLeft: 20, downloadRight: 9, caretLeft: 7, caretRight: 11 }, + sm: { downloadLeft: 12, downloadRight: 7, caretLeft: 5, caretRight: 9 }, }; const FIELDS: { key: keyof Pad; label: string; accent: string }[] = [ From c464b6d825a10f1488f8a08b5b54872f8fafa9ba Mon Sep 17 00:00:00 2001 From: lawrencecchen <54008264+lawrencecchen@users.noreply.github.com> Date: Sat, 4 Jul 2026 03:16:56 -0700 Subject: [PATCH 12/12] Remove /debug tuner and padOverride prop; ship only the button Tuning is done and the values live in PILL_PADDING. Drop the dev-only /debug page and the padOverride prop so production ships just the download button changes (padding config + hover-jump fix), no public /debug route. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../[locale]/components/download-button.tsx | 21 +- web/app/[locale]/debug/page.tsx | 255 ------------------ 2 files changed, 6 insertions(+), 270 deletions(-) delete mode 100644 web/app/[locale]/debug/page.tsx diff --git a/web/app/[locale]/components/download-button.tsx b/web/app/[locale]/components/download-button.tsx index 8a376470281f..518e0f889327 100644 --- a/web/app/[locale]/components/download-button.tsx +++ b/web/app/[locale]/components/download-button.tsx @@ -18,16 +18,9 @@ import { WaitlistDialog } from "./waitlist-dialog"; // Per-size pill padding in px. downloadRight = gap LEFT of the divider, // caretLeft = gap RIGHT of it. Applied as inline styles (not Tailwind classes) -// because the tuned values include odd px like 7 that have no spacing token, -// and because arbitrary `pr-[7px]` did not reliably resolve on the base-ui -// Menu.Trigger button; inline px renders the exact value in dev and prod and -// matches the /debug tuner one-to-one. -export type PadOverride = { - downloadLeft?: number; - downloadRight?: number; - caretLeft?: number; - caretRight?: number; -}; +// because the tuned values include odd px like 9/11 that have no spacing token, +// and because arbitrary values like `pr-[9px]` did not reliably resolve on the +// base-ui Menu.Trigger button; inline px renders the exact value in dev + prod. const PILL_PADDING = { default: { downloadLeft: 20, downloadRight: 9, caretLeft: 7, caretRight: 11 }, sm: { downloadLeft: 12, downloadRight: 7, caretLeft: 5, caretRight: 9 }, @@ -37,12 +30,10 @@ export function DownloadButton({ size = "default", location = "hero", className, - padOverride, }: { size?: "default" | "sm"; location?: string; className?: string; - padOverride?: PadOverride; }) { const t = useTranslations("common"); const tp = useTranslations("platforms"); @@ -67,9 +58,9 @@ export function DownloadButton({ const onConfirmationPage = pathname === DOWNLOAD_CONFIRMATION_PATH; const macHref = onConfirmationPage ? DOWNLOAD_URL : DOWNLOAD_CONFIRMATION_HREF; - // Resolve padding from the per-size config, with the optional /debug override - // merged in. Applied inline below so odd px render exactly. - const pad = { ...PILL_PADDING[isSmall ? "sm" : "default"], ...padOverride }; + // Resolve padding from the per-size config; applied inline so odd px render + // exactly. + const pad = PILL_PADDING[isSmall ? "sm" : "default"]; const downloadStyle = { paddingLeft: pad.downloadLeft, paddingRight: pad.downloadRight, diff --git a/web/app/[locale]/debug/page.tsx b/web/app/[locale]/debug/page.tsx deleted file mode 100644 index 10b60312109f..000000000000 --- a/web/app/[locale]/debug/page.tsx +++ /dev/null @@ -1,255 +0,0 @@ -"use client"; - -// Live tuner for the real DownloadButton. Renders both sizes at once and drives -// each one's padding via the component's `padOverride` prop, so the divider -// spacing can be dialed in per size and copied back into download-button.tsx. -// Diagnostic toggles (re-add transition, drop translateZ, magnify) exist to -// pin down the Safari hover jump. Reach it at /debug. Not linked from anywhere. - -import { type CSSProperties, useState } from "react"; -import { DownloadButton } from "../components/download-button"; - -type Pad = { - downloadLeft: number; - downloadRight: number; - caretLeft: number; - caretRight: number; -}; -type Size = "default" | "sm"; - -// Mirrors the values baked into download-button.tsx so /debug opens on the -// currently-shipped spacing. -const INITIAL: Record = { - default: { downloadLeft: 20, downloadRight: 9, caretLeft: 7, caretRight: 11 }, - sm: { downloadLeft: 12, downloadRight: 7, caretLeft: 5, caretRight: 9 }, -}; - -const FIELDS: { key: keyof Pad; label: string; accent: string }[] = [ - { key: "downloadRight", label: "Left of divider (download pr)", accent: "#3b82f6" }, - { key: "caretLeft", label: "Right of divider (caret pl)", accent: "#f97316" }, - { key: "downloadLeft", label: "Outer left (before icon)", accent: "#9ca3af" }, - { key: "caretRight", label: "Outer right (after caret)", accent: "#9ca3af" }, -]; - -// px -> Tailwind spacing token (v4: 1 unit = 4px; .5 steps ok; else arbitrary) -function tw(prefix: string, px: number) { - if (px % 4 === 0) return `${prefix}-${px / 4}`; - if (px % 2 === 0) return `${prefix}-${px / 4}`; // e.g. 10 -> 2.5, 6 -> 1.5 - return `${prefix}-[${px}px]`; -} - -export default function DownloadButtonDebug() { - const [pad, setPad] = useState>(() => structuredClone(INITIAL)); - const [dark, setDark] = useState(true); - const [zoom, setZoom] = useState(1); - const [reTransition, setReTransition] = useState(false); - const [forceTranslateZ, setForceTranslateZ] = useState(false); - const [copied, setCopied] = useState(false); - - const set = (size: Size, key: keyof Pad, v: number) => - setPad((p) => ({ ...p, [size]: { ...p[size], [key]: v } })); - - const resetSize = (size: Size) => - setPad((p) => ({ ...p, [size]: { ...INITIAL[size] } })); - - const classesFor = (size: Size) => { - const p = pad[size]; - return { - download: `${tw("pl", p.downloadLeft)} ${tw("pr", p.downloadRight)}`, - caret: - p.caretLeft === p.caretRight - ? tw("px", p.caretLeft) - : `${tw("pl", p.caretLeft)} ${tw("pr", p.caretRight)}`, - }; - }; - - const snippet = () => { - const d = classesFor("default"); - const s = classesFor("sm"); - return [ - "// download zone padding", - `default download: ${d.download} caret: ${d.caret}`, - `sm download: ${s.download} caret: ${s.caret}`, - "", - "// raw px", - `default: ${JSON.stringify(pad.default)}`, - `sm: ${JSON.stringify(pad.sm)}`, - ].join("\n"); - }; - - const copy = async () => { - await navigator.clipboard.writeText(snippet()); - setCopied(true); - window.setTimeout(() => setCopied(false), 1200); - }; - - const wrapClass = [ - reTransition ? "dbg-retrans" : "", - forceTranslateZ ? "dbg-tz" : "", - ] - .filter(Boolean) - .join(" "); - - return ( -
- {/* Diagnostic CSS scoped to the previews */} - - -

DownloadButton debug

-

- Both sizes render the real component; sliders drive its{" "} - padOverride. Hover each - button to check the jump. Copy classes into{" "} - - web/app/[locale]/components/download-button.tsx - - . -

- - {/* toolbar */} -
- - - - -
- - {/* previews + sliders, one column per size */} -
- {(["default", "sm"] as const).map((size) => ( -
-
- - {size} - - -
- -
-
- {size === "sm" && ( - <> - Docs - Blog - - )} - - {size === "default" && ( - - View on GitHub - - )} -
-
- -
- {FIELDS.map((f) => ( -
-
- - - {f.label} - - - - set(size, f.key, Number(e.target.value))} - className="w-14 rounded border border-border bg-transparent px-2 py-0.5 text-right text-sm" /> - -
- set(size, f.key, Number(e.target.value))} - className="w-full" /> -
- ))} -
- download {classesFor(size).download} · caret {classesFor(size).caret} -
-
-
- ))} -
- - {/* output */} -
- -
-          {snippet()}
-        
-
-
- ); -}