Skip to content

fix(website): download gate never enables when the GitHub API lookup fails - #805

Open
felipesalinasr wants to merge 1 commit into
mainfrom
fix/download-gate-fallback
Open

fix(website): download gate never enables when the GitHub API lookup fails#805
felipesalinasr wants to merge 1 commit into
mainfrom
fix/download-gate-fallback

Conversation

@felipesalinasr

Copy link
Copy Markdown
Collaborator

Before merging / deploying

  • No secrets or config needed: the deploy workflow now passes the built-in github.token to the build (no new repo secret).
  • Note: after merge, the website build FAILS CLOSED if the latest-release lookup fails, instead of shipping a page with a dead download button. That is intentional; re-run the deploy if GitHub hiccups.

The bug (user-reported)

Invited users paste the correct code and the download button never enables; clicks do nothing.

The button required both a code match and a successful runtime fetch of api.github.com/.../releases/latest from the visitor's browser:

  • Unauthenticated GitHub API = 60 requests/hour per IP. Shared IPs (offices, VPNs, mobile carrier NAT) get refused -> no installer URL -> button can never enable.
  • The fetch had no .catch; blocked/failed lookups fail silently and permanently.
  • Validation only ran on input events, so a code pasted before the fetch resolved stayed locked even after the URL arrived.
  • UX hid it: the input turns green (code accepted) while the button stays at 35% opacity with pointer-events: none.
  • Analytics hid it too: download_unlocked only fired on the happy path, so affected users were indistinguishable from visitors without a code.

The fix

Plan A (live, upgraded): the runtime lookup stays, now with .catch + re-validation when it lands (fixes the paste-before-fetch race).

Plan B (baked fallback): new src/_data/release.js resolves the latest installer URLs at build time and bakes them into the page. If the live lookup fails, the baked link downloads the app directly. Worst case: the release current at deploy time, and the app self-updates on first launch. Nothing hand-written, nothing version-pinned in source.

Gate rule change: a correct code alone unlocks the button; the href is live-or-baked. Same for the Windows modal and the early-access page (whose fallback previously dumped users on the homepage).

Hardening: case-insensitive code check; autocapitalize="none" autocorrect="off" on both inputs (iOS typed Getsh*tdone and never validated); download_unlocked fires on code match unconditionally; download events carry link_source: live|baked so the fallback path is measurable.

lib/release-assets.js holds the pure asset-picking logic outside _data — Eleventy treats named exports in data files as data keys and silently drops the default export (verified the broken build output before moving it).

Verification

  • pickInstallerUrls unit-checked via node against the real v0.4.27 asset list + empty/undefined inputs.
  • Eleventy build bakes real URLs into /, /early-access/ (en/es/pt).
  • Playwright against the built site with the API endpoint deadened: wrong code stays locked; correct code (mixed case, padded) enables Mac + both Windows buttons with direct installer hrefs; console warning surfaces the lookup failure.
  • Race test: code typed immediately on load unlocks instantly and stays unlocked after the fetch settles.
  • CI fail-closed: CI=1 + failing lookup throws (tested with a 401).
  • Biome clean on both new JS files.

🤖 Generated with Claude Code

…fails

Users reported entering the invite code correctly and the download button
staying dead. Root cause: the button required BOTH a code match AND a
runtime fetch of api.github.com to have succeeded. Unauthenticated GitHub
API calls are limited to 60/hour per IP, so visitors behind shared IPs
(offices, VPNs, mobile carrier NAT) never got an installer URL; the fetch
also had no .catch, and validation only ran on keystrokes so a URL arriving
after the code was pasted never unlocked the button. The failure was
invisible in analytics because download_unlocked only fired on the happy
path.

Fixes:
- New build-time data file (src/_data/release.js) resolves the latest
  installer URLs on every deploy and bakes them into the pages as
  guaranteed direct-download fallbacks. Fails closed in CI so a deploy can
  never ship with empty fallbacks; helper logic lives in lib/release-assets
  because Eleventy treats named exports in data files as data keys.
- Gate unlocks on the code alone; href prefers the live lookup, falls back
  to the baked link. Validation re-runs when the lookup lands (fixes the
  paste-before-fetch race). Same treatment for the Windows modal and the
  early-access page (whose fallback previously sent users to the homepage).
- Runtime lookup now has a .catch (warn + baked fallback per beta policy).
- Code comparison is case-insensitive and both inputs disable mobile
  autocapitalize/autocorrect (iOS typed "Getsh*tdone" and never validated).
- download_unlocked fires on code match regardless of URL state, and
  download events carry link_source (live|baked) so this failure mode is
  measurable in the funnel.
- website-deploy.yml passes the workflow token to the build so the
  build-time lookup is not rate-limited on shared Actions runners.

Verified: Eleventy build bakes real URLs; Playwright run against the built
site with the API deadened (wrong code stays locked; correct code, mixed
case and padded, enables Mac + both Windows buttons with direct installer
hrefs); race test (code typed before fetch resolves) unlocks immediately
and stays unlocked; CI fail-closed path throws on API 401; Biome clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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.

1 participant