Skip to content

fix(marketing): stop inferring Mac CPU arch from the WebGL renderer - #7054

Merged
Kitenite merged 1 commit into
mainfrom
reproduce-issue-7036
Sep 3, 2026
Merged

Kitenite merged 1 commit into
mainfrom
reproduce-issue-7036

Conversation

@Kitenite

@Kitenite Kitenite commented Sep 1, 2026 •

Copy link
Copy Markdown
Member

Fixes #7036

Summary

  • detectMacArch read the WebGL unmasked renderer string and treated any string without apple in it as proof of an Intel Mac. Apple Silicon visitors whose browser masks or software-renders that string were handed Superset-x64.dmg.
  • Replaced with UA Client Hints (navigator.userAgentData). When the browser exposes nothing (Safari, Firefox) the page defaults to Apple Silicon, labels the pill plain "macOS" instead of claiming a detection, and links the Intel build one click below the button.
  • download_started / download_manual_clicked carry archSource (client-hint / default / not-mac), and a new download_arch_switched event fires when someone takes the Intel link, so PostHog can size how often the default is wrong.
  • Added the first tests for this module, including the negative case that pins the regression.

Why / Context

The reporter was on an M5 MacBook Pro and got the Intel build, then had to go to GitHub Releases to fix it by hand.

Inferring a CPU property from a GPU string is wrong regardless of this bug: the two aren't related, and the renderer string is absent under software rendering and forged by fingerprint-resistant browsers and extensions. Every genuine failure path already fell back to Apple Silicon; only a present but wrong renderer string produced Intel.

/download is the only surface that can serve the x64 build. apps/web and apps/docs hardcode arm64.

How It Works

detectMacArch now:

  1. Asks navigator.userAgentData.getHighEntropyValues(["architecture"]), which reports the CPU directly in Chromium and is unaffected by GPU state.
  2. Normalises the answer by prefix (normalizeArch), so x86_64 and arm64 map correctly rather than falling through.
  3. Returns Apple Silicon with archSource: "default" when there is no signal at all.

The interstitial treats archSource: "default" as unconfirmed: the platform pill reads "macOS", and a line under the button says the browser didn't report a chip and links Superset-x64.dmg for Intel Macs. The ReleaseList from #7047 still lists every asset below.

Design Decisions

  • Why default unconfirmed to Apple Silicon, not x64. An intermediate commit on this branch defaulted to x64 because it runs on either chip via Rosetta. PostHog for the last 30 days says that mis-serves more people than it rescues:

    Browser Mac downloads Resolved Intel under the old code
    Chrome + Edge + Opera 4,110 232
    Safari + Firefox 626 4

    Safari and Firefox have no client hint, so an x64 default sends all ~626/month to the Intel build, and nearly all of them are Apple Silicon. Three things make that landing worse than "degraded but runs": a fresh Apple Silicon Mac has no Rosetta installed, so first launch is an install prompt; the auto-updater keeps the user on x64 for every future release; and Apple has said Rosetta narrows after macOS 27. An arm64 build on an Intel Mac fails loudly with a "can't be opened" dialog, and the note on the page already points at the fix.

  • Why not invert the WebGL check to require a positive intel|amd|radeon match: it would fix the reported case but keep a heuristic that misfires on exactly the privacy-configured browsers that cause the problem. Deleting it removes the failure mode instead of narrowing it.

  • Why prefix matching in normalizeArch: exact === "x86" would send an engine reporting x86_64 to the Apple Silicon default, which is this same silent-wrong-build bug pointed the other way. I can't test a real Intel Mac, so the assumption is made cheap to be wrong about.

  • Why no Sec-CH-UA-Arch server tier: it covers no browser navigator.userAgentData doesn't, and it makes the HTML vary on a request header while Next silently drops the Vary that would make that safe to cache.

  • Why not a universal binary: it removes the guess entirely, and Cursor, Slack and Obsidian ship universal. But VS Code's universal build is 1.74x its arm64 build, which would put Superset near 1GB, paid by every Apple Silicon user on every download and auto-update. Worth a deliberate decision, not this PR.

Manual QA Checklist

Reproduced and verified on real Apple Silicon (M5 Max), driving Chrome over CDP against the page with downloads denied.

Before the fix, against production superset.sh/download:

  • Chrome with SwiftShader software rendering requested Superset-x64.dmg
  • Chrome with a masked renderer string (Mesa OffScreen) requested Superset-x64.dmg
  • Stock Chrome requested Superset-arm64.dmg (control)

After the fix, against this branch:

  • SwiftShader: Superset-arm64.dmg, page labels it "macOS · Apple Silicon"

  • Stock Chrome: Superset-arm64.dmg (control)

  • Client hint x86: Superset-x64.dmg, page labels it "macOS · Intel"

  • Client hint x86_64: Superset-x64.dmg

  • Client hint arm64: Superset-arm64.dmg

  • Unrecognised value (wasm): Superset-arm64.dmg (safe default)

  • Real browsers against the final commit (bun drivers over CDP / WebDriver BiDi on throwaway profiles, downloads denied or github.com blocked):

    Browser Arch signal Redirected to Pill Intel note
    Chrome 152 stock client hint arm Superset-arm64.dmg macOS · Apple Silicon no
    Chrome 152 --use-angle=swiftshader (renderer = SwiftShader, the old Intel trigger) client hint arm Superset-arm64.dmg macOS · Apple Silicon no
    Chrome 152 under Rosetta (arch -x86_64, chrome://version says "x86_64 translated") client hint arm Superset-arm64.dmg macOS · Apple Silicon no
    Comet (Chromium 152) client hint arm Superset-arm64.dmg macOS · Apple Silicon no
    Firefox 154 stock none Superset-arm64.dmg macOS yes, links Superset-x64.dmg
    Firefox 154 privacy.resistFingerprinting (renderer = "Mozilla", the old Intel trigger) none Superset-arm64.dmg macOS yes
  • Safari 26.6: not driven. WebDriver needs sudo safaridriver --enable on this machine. Safari has no userAgentData (WebKit standards-positions format 1 #70 is still open), so it takes the same path Firefox exercised above.

Also confirmed the healthy path was never broken: a port of the old detectMacArch resolved correctly in Chrome, Safari 26.6, Firefox 148, Arc and Comet on this machine, which is why this only ever bit a subset of visitors.

Testing

  • bun run --cwd apps/marketing typecheck
  • bun test --cwd apps/marketing (85 pass, 6 files)
  • bunx biome check apps/marketing
  • bun run --cwd packages/i18n check (strict compile 0 missing across 17 locales, stale audit clean)

Confirmed the tests can fail: reintroducing the Intel default turns 4 red, and reverting normalizeArch to exact matching turns 6 red.

Known Limitations

  • Intel Macs on Safari and Firefox get the Apple Silicon default. Those browsers expose no architecture signal. The old WebGL check did not classify them correctly either (Safari masks the renderer as "Apple GPU" on every Mac), so this is not a regression, but the recovery is now explicit: the page says the chip wasn't detected and links the Intel build.
  • The Intel branch is verified with a synthetic client hint, not real Intel hardware. Prefix normalisation is the hedge.
  • This may not be what the reporter hit. Every stock browser on my M5 Max resolves correctly, so I could only reproduce under software rendering or a masked renderer. Their report says app.superset.sh, which hardcodes arm64 and can't produce x64. The bug fixed here is real and proven; whether it's theirs is unconfirmed. archSource in PostHog will size it.
  • The 16 non-English translations of the new note were written by hand in this PR, not by the translate workflow. Reword freely.
  • iPad Safari is classified as a Mac. iPadOS has sent the desktop Macintosh user agent by default since iPadOS 13, so an iPad visitor hits detectMacArch, gets the Apple Silicon default, and auto-downloads a DMG instead of seeing the "open this on your desktop" link. Not a regression (the WebGL path did the same via "Apple GPU"), and PostHog can't size it because it files iPads under Mac OS X too. The standard tell is navigator.platform === "MacIntel" && navigator.maxTouchPoints > 1, which Claude, Cursor, Obsidian and Postman all use. One-line follow-up.

Follow-ups

  • apps/web/src/app/(agents)/agents/page.tsx:73, apps/web/.../settings/billing/page.tsx:63 and apps/docs/.../DownloadButton.tsx hardcode arm64 with no escape hatch. Routing them at superset.sh/download would keep this logic in one place.
  • The desktop app could detect a wrong-arch install at runtime from process.arch plus a Rosetta check, which catches every path including builds grabbed from GitHub.

https://claude.ai/code/session_01YU7cRGNVocDp2PwUCKbzU7
https://claude.ai/code/session_01DyrTQocncE9XV6tHTXe3J8

@coderabbitai

coderabbitai Bot commented Sep 1, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Team

Run ID: 1068e097-f630-4ee5-9d02-1a98fdf4f9cb

📥 Commits

Reviewing files that changed from the base of the PR and between 6fa575b and f29b25e.

📒 Files selected for processing (37)
  • apps/marketing/src/app/[lang]/download/components/DownloadInterstitial/DownloadInterstitial.tsx
  • apps/marketing/src/app/[lang]/hooks/useOS/useOS.test.ts
  • apps/marketing/src/app/[lang]/hooks/useOS/useOS.ts
  • packages/i18n/locales/cs/messages.po
  • packages/i18n/locales/cs/messages.ts
  • packages/i18n/locales/de/messages.po
  • packages/i18n/locales/de/messages.ts
  • packages/i18n/locales/en/messages.po
  • packages/i18n/locales/en/messages.ts
  • packages/i18n/locales/es/messages.po
  • packages/i18n/locales/es/messages.ts
  • packages/i18n/locales/fr/messages.po
  • packages/i18n/locales/fr/messages.ts
  • packages/i18n/locales/id/messages.po
  • packages/i18n/locales/id/messages.ts
  • packages/i18n/locales/it/messages.po
  • packages/i18n/locales/it/messages.ts
  • packages/i18n/locales/ja/messages.po
  • packages/i18n/locales/ja/messages.ts
  • packages/i18n/locales/ko/messages.po
  • packages/i18n/locales/ko/messages.ts
  • packages/i18n/locales/nl/messages.po
  • packages/i18n/locales/nl/messages.ts
  • packages/i18n/locales/pl/messages.po
  • packages/i18n/locales/pl/messages.ts
  • packages/i18n/locales/pt-BR/messages.po
  • packages/i18n/locales/pt-BR/messages.ts
  • packages/i18n/locales/ru/messages.po
  • packages/i18n/locales/ru/messages.ts
  • packages/i18n/locales/tr/messages.po
  • packages/i18n/locales/tr/messages.ts
  • packages/i18n/locales/vi/messages.po
  • packages/i18n/locales/vi/messages.ts
  • packages/i18n/locales/zh-CN/messages.po
  • packages/i18n/locales/zh-CN/messages.ts
  • packages/i18n/locales/zh-TW/messages.po
  • packages/i18n/locales/zh-TW/messages.ts
🚧 Files skipped from review as they are similar to previous changes (16)
  • packages/i18n/locales/es/messages.po
  • packages/i18n/locales/nl/messages.po
  • packages/i18n/locales/ja/messages.po
  • packages/i18n/locales/tr/messages.po
  • packages/i18n/locales/zh-CN/messages.po
  • packages/i18n/locales/ko/messages.po
  • packages/i18n/locales/vi/messages.po
  • packages/i18n/locales/zh-TW/messages.po
  • packages/i18n/locales/pl/messages.po
  • packages/i18n/locales/cs/messages.po
  • packages/i18n/locales/fr/messages.po
  • packages/i18n/locales/it/messages.po
  • packages/i18n/locales/de/messages.po
  • packages/i18n/locales/ru/messages.po
  • packages/i18n/locales/en/messages.po
  • packages/i18n/locales/pt-BR/messages.po

Included review availability: Your plan provides up to 10 included reviews per hour; 5 remain after this review.


📝 Walkthrough

Walkthrough

The useOS hook now detects Mac architecture through UA Client Hints and resolves platform data asynchronously. Unconfirmed Mac downloads default to Apple Silicon and provide an Intel download link. Tests and locale catalogs cover the updated behavior.

Changes

Mac architecture-aware downloads

Layer / File(s) Summary
Architecture detection contracts and resolution
apps/marketing/src/app/[lang]/hooks/useOS/useOS.ts, apps/marketing/src/app/[lang]/hooks/useOS/index.ts
Adds ArchSource, archSource, normalizeArch, and public detection exports. Mac architecture uses asynchronous UA Client Hints and defaults to Apple Silicon when no usable hint is available.
Download selection and fallback messaging
apps/marketing/src/app/[lang]/download/components/DownloadInterstitial/DownloadInterstitial.tsx
Uses archSource for analytics, effect dependencies, platform labeling, and an Apple Silicon fallback notice with an Intel download link.
Detection and platform classification tests
apps/marketing/src/app/[lang]/hooks/useOS/useOS.test.ts
Tests architecture normalization, Client Hint results, fallback behavior, navigator cleanup, platform classification, iPad handling, and isMacPlatform.
Localized fallback notice
packages/i18n/locales/*/messages.po
Updates or adds marketing.download.archUnconfirmed translations for the Apple Silicon default and Intel download link.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Merge Risk: ⚪ Minimal · up to f29b2

The download flow now uses client hints when available, defaults unconfirmed Macs to Apple Silicon, and provides an Intel fallback link. No current merge-blocking risk remains.

Sequence Diagram(s)

sequenceDiagram
  participant DownloadInterstitial
  participant usePlatform
  participant detectPlatform
  participant navigator.userAgentData
  DownloadInterstitial->>usePlatform: request platform information
  usePlatform->>detectPlatform: resolve platform
  detectPlatform->>navigator.userAgentData: request architecture hint
  navigator.userAgentData-->>detectPlatform: architecture value or failure
  detectPlatform-->>usePlatform: PlatformInfo with archSource
  usePlatform-->>DownloadInterstitial: platform and architecture source
  DownloadInterstitial->>DownloadInterstitial: select download and render fallback notice
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 37.50% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 8 functions across 4 files. (17 skipped: … Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The changes address issue #7036 by removing incorrect WebGL inference, using client hints when available, defaulting safely to Apple Silicon, and providing Intel users an alternate download path.
Out of Scope Changes check ✅ Passed The code, tests, analytics, and localization updates directly support the Mac architecture download fix. No unrelated changes are evident.
Title check ✅ Passed The title uses conventional commit format and clearly describes the main change: removing WebGL renderer-based Mac CPU architecture inference.
Description check ✅ Passed The description clearly explains the problem, implementation, design decisions, testing, manual QA, limitations, and follow-ups. It does not reproduce the exact template checklist or confirm the maint…
Full details: Docstring Coverage

Explanation

Docstring coverage is 37.50% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 8 functions across 4 files. (17 skipped: 17 unsupported.)

Full details: Description check

Explanation

The description clearly explains the problem, implementation, design decisions, testing, manual QA, limitations, and follow-ups. It does not reproduce the exact template checklist or confirm the maintainer-edit setting, but the required change and validation details are substantially complete.

✨ Finishing Touches 💡 2
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
⚔️ Resolve merge conflicts 💡
  • Resolve merge conflict in branch reproduce-issue-7036
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch reproduce-issue-7036

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Sep 1, 2026 •

Copy link
Copy Markdown
Contributor

🧹 Preview Cleanup Complete

The following preview resources have been cleaned up:

  • ✅ Neon database branch

Thank you for your contribution! 🎉

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

No issues found across 3 files

Architecture diagram
sequenceDiagram
    participant Client as Marketing Site (Browser)
    participant UA as navigator.userAgentData
    participant UA2 as navigator.userAgent
    participant Hook as usePlatform (React)
    participant Download as Download Logic
    participant Analytics as PostHog Event

    Note over Client,Analytics: Mac Architecture Detection & Download Flow

    Client->>Hook: Mount usePlatform hook
    Hook->>Hook: Initialize with Platform.Unknown

    Hook->>UA2: Read navigator.userAgent
    UA2-->>Hook: User agent string

    alt Mobile device detected
        Hook->>Hook: Classify as Platform.Mobile
        Hook-->>Client: Return Mobile + NotMac source
    else Mac detected (mac os x|macintosh)
        Hook->>UA: Call getHighEntropyValues(["architecture"])
        
        alt Browser exposes client hint
            UA-->>Hook: Return architecture value
            Hook->>Hook: normalizeArch() - prefix matching
            alt Architecture is x86/x86_64/amd64
                Hook->>Hook: Classify as MacIntel + ClientHint source
            else Architecture is arm/arm64
                Hook->>Hook: Classify as MacAppleSilicon + ClientHint source
            else Unrecognized or empty architecture
                Hook->>Hook: Default to MacAppleSilicon + Default source
            end
        else No userAgentData (Safari/Firefox) or error
            UA-->>Hook: Browser exposes no architecture
            Hook->>Hook: Default to MacAppleSilicon + Default source
        end
        Hook-->>Client: Return Mac platform info
    else Windows/Linux/Other OS
        Hook->>Hook: Classify based on UA patterns
        Hook-->>Client: Return OS platform + NotMac source
    end

    Client->>Download: User initiates download
    Download->>Download: Determine DMG filename
    Download-->>Client: Serve arm64 or x64 build

    Client->>Analytics: Track download_started/manual_clicked
    Note over Client,Analytics: Includes archSource field<br/>(client-hint vs default vs not-mac)
Loading

Re-trigger cubic

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

All reported issues were addressed across 37 files (changes from recent commits).

Not reviewed (too large): packages/i18n/locales/cs/messages.ts (~2 lines), packages/i18n/locales/de/messages.ts (~2 lines), packages/i18n/locales/en/messages.ts (~2 lines), packages/i18n/locales/es/messages.ts (~2 lines), packages/i18n/locales/fr/messages.ts (~2 lines), packages/i18n/locales/id/messages.ts (~2 lines), packages/i18n/locales/it/messages.ts (~2 lines), packages/i18n/locales/ja/messages.ts (~2 lines), packages/i18n/locales/ko/messages.ts (~2 lines), packages/i18n/locales/nl/messages.ts (~2 lines), and 7 more - if these are generated or fixture files, add them to ignored paths to exclude them from future reviews.

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

@Kitenite
Kitenite force-pushed the reproduce-issue-7036 branch from 0344776 to f6ffc3d Compare September 3, 2026 06:09
@coderabbitai

coderabbitai Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

@Kitenite

Kitenite commented Sep 3, 2026

Copy link
Copy Markdown
Member Author

Re the two cubic P2/P3 notes: the fallback direction is now Apple Silicon again (f29b25e), with the data behind that choice in the description's Design Decisions. The test comment and PR body are updated to match what ships.

detectMacArch read the WebGL unmasked renderer string and treated any
string without "apple" in it as proof of an Intel Mac. Apple Silicon
visitors whose browser masks or software-renders that string were
handed Superset-x64.dmg (#7036).

Architecture now comes from UA Client Hints, which report the CPU
directly in Chromium and report the hardware even under Rosetta. When
the browser exposes nothing (Safari, Firefox) the page defaults to
Apple Silicon, labels the pill plain "macOS" instead of claiming a
detection, and links the Intel build under the button. PostHog says
that is the right default: Safari and Firefox are ~13% of Mac
downloads and nearly all of them are Apple Silicon, so guessing x64
would mis-serve far more people than it rescues.

download_started and download_manual_clicked carry archSource, and
download_arch_switched fires when someone takes the Intel link, so the
share of guessed visitors is measurable.

Rebuilt on main after #7092 re-keyed the catalogs by English text; the
note's 16 translations are written in by hand.

Fixes #7036

Claude-Session: https://claude.ai/code/session_01DyrTQocncE9XV6tHTXe3J8
@Kitenite
Kitenite merged commit 45c671c into main Sep 3, 2026
20 checks passed
@Kitenite
Kitenite deleted the reproduce-issue-7036 branch September 3, 2026 23:47

This branch was previously deployed

1 inactive deployment
preview — 1e811b37 Deployed Sep 3, 2026 by Kitenite via Deploy Docs #9096
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.

Website downloads x64 by default

1 participant