Skip to content

release: wire prod Turnstile sitekey + wrangler.jsonc comment audit (post-#115 follow-up)#116

Merged
brettdavies merged 2 commits into
mainfrom
release/2026-05-25-prod-turnstile-wiring
May 25, 2026
Merged

release: wire prod Turnstile sitekey + wrangler.jsonc comment audit (post-#115 follow-up)#116
brettdavies merged 2 commits into
mainfrom
release/2026-05-25-prod-turnstile-wiring

Conversation

@brettdavies
Copy link
Copy Markdown
Owner

@brettdavies brettdavies commented May 25, 2026

Summary

Two related changes in two commits.

Commit 1, enable the production homepage form after fail-closed verification. PR #115 deliberately omitted the production TURNSTILE_SITEKEY value from wrangler.jsonc, with the express purpose of forcing the live-scoring form to fail closed in production immediately after the v3 cut deployed: an empty sitekey causes src/client/live-score.ts to disable the form with a "Live scoring is available on staging only" notice rather than render a non-functional Turnstile widget. The point of that deliberate omission was to verify, on live anc.dev, that the fail-closed path actually works end-to-end before flipping the switch. It did: the form was disabled, the disabled-state message rendered, the surface degraded gracefully without a single 5xx. With fail-closed verified in production, this commit now enables the form by wiring the real production sitekey into the top-level vars block. The matching TURNSTILE_SECRET was set on the production Worker via wrangler secret put TURNSTILE_SECRET separately (encrypted at rest in CF; never committed). The sitekey is public-by-design: Turnstile embeds it in HTML at request time for the client-side widget to render. Anyone viewing the page source already sees it; committing it to wrangler.jsonc is intentional. The secret is what enforces ownership server-side at the siteverify API boundary, and it is not in the repo.

Commit 2, trim wrangler.jsonc comments per /code-comments policy + stash rationale. Eight comment blocks audited against the WHY-only / no-temporal / no-task-flow / no-local-doc-ref / no-instructional policy. Net 65 lines removed from wrangler.jsonc, 41 lines added to RELEASES-RATIONALE.md under a new ## Wrangler env inheritance traps section that consolidates which keys inherit from top-level (routes, triggers, vars), which do not (containers, durable_objects, migrations, etc.), the REPLACE-not-merge semantics on vars, and the 2026-04-30 routing-drift incident as historical context. Each trimmed comment in wrangler.jsonc keeps the WHY that's unique to inline context and points at the consolidated rationale for the rest. Procedural runbook content that was duplicated between wrangler.jsonc and RELEASES.md § Sandbox image releases is dropped from the config in favor of the pointer.

Changelog

Added

  • Enable the live-scoring homepage form on anc.dev by wiring the production TURNSTILE_SITEKEY. PR release: live-scoring v3 + anc 0.4.0 + post-#91 promotion #115's intentional omission served its purpose: the fail-closed path was verified in production (form disabled with the staging-only notice, no 5xx, graceful degradation). With fail-closed proven, the real sitekey is now in place and the form is live.

Changed

  • Consolidate Wrangler env-inheritance rationale (routes / triggers / vars inheritance semantics, the 2026-04-30 routing-drift incident, container app naming quirk) into a new RELEASES-RATIONALE.md § Wrangler env inheritance traps. wrangler.jsonc comments now point at the consolidated rationale.

Documentation

  • Trim eight wrangler.jsonc comment blocks per the /code-comments policy: drop temporal phrasing (first-ever, since 2026-04-30, etc.), drop docs/plans/ and personal-repo references, drop instructional voice (Flip via:, Deploy with:, Removing this line will reintroduce the bug), drop runbook content that duplicates RELEASES.md § Sandbox image releases.

Type of Change

  • feat: New feature (non-breaking change which adds functionality)

PR #115 shipped working live-scoring infrastructure behind a deliberate fail-closed gate. Commit 1 flips the gate open after that fail-closed behavior was verified in production. Commit 2 is documentation hygiene that surfaced during a /code-comments audit of the config file we were already editing.

Related Issues/Stories

Testing

  • Unit tests added/updated
  • All tests passing

Test Summary:

  • bun test: 737 pass / 0 fail across 28 files.
  • bun x wrangler deploy --dry-run: clean. Lists env.TURNSTILE_SITEKEY ("ff0x4AAAAAADQFMBoVm56-OPuQ") (the real production sitekey).
  • bun x wrangler deploy --dry-run --env staging: clean. Lists env.TURNSTILE_SITEKEY ("1x00000000000000000000AA") (Cloudflare's always-pass test sitekey, unchanged). Confirms env.staging.vars REPLACE semantics correctly isolates staging from the new top-level value.
  • Pre-push gate: pass (lint, build, test, both wrangler dry-runs, pack-README, banned-fonts, prose-check 0 blocking).
  • /code-comments policy pattern scan against wrangler.jsonc: zero temporal / zero local-doc / zero instructional findings post-trim.
  • wrangler secret list against the production Worker confirms TURNSTILE_SECRET is set (re-set from 1Password's agentnative-site Cloudflare Turnstile / prod.secret_key field via wrangler secret put before this PR opened).
  • Fail-closed verification (post-release: live-scoring v3 + anc 0.4.0 + post-#91 promotion #115, pre-release: wire prod Turnstile sitekey + wrangler.jsonc comment audit (post-#115 follow-up) #116): anc.dev homepage form rendered the disabled state with the "Live scoring is available on staging only" notice; no Turnstile widget rendered; no /api/score POST was dispatched; no 5xx in the request log. The deliberate omission worked exactly as designed.

Post-merge verification plan (after the production deploy on this PR's merge):

  • Visit anc.dev. Confirm the homepage form is now enabled and the Turnstile widget renders (invisible-mode, so it may not be visually obvious; the form's submit button should be clickable rather than greyed out with the staging-only notice).
  • Submit a real input through the homepage form. Confirm round-trip: Turnstile widget executes, POST to /api/score returns a scorecard, browser redirects to /live-score/<binary> with the inline scorecard.
  • curl https://anc.dev/api/score -H 'Content-Type: application/json' -d '{}' still returns 400 unrecognized_input (sanity: handler unchanged by this PR).

Files Modified

Modified:

  • wrangler.jsonc: enabled the top-level vars block and populated TURNSTILE_SITEKEY with the production sitekey from 1Password (agentnative-site Cloudflare Turnstile / prod.site_key). Trimmed eight comment blocks per the /code-comments policy (see ## Summary commit 2 for the per-block summary). Net 65 lines removed.
  • RELEASES-RATIONALE.md: new ## Wrangler env inheritance traps section consolidates inheritance semantics, override patterns, and the historical context that the config-file comments previously held inline.

Created:

  • None.

Renamed:

  • None.

Deleted:

  • None.

Adds the real production Turnstile sitekey to the top-level `vars`
block in wrangler.jsonc. Without it, the homepage form reads an empty
TURNSTILE_SITEKEY meta tag at request time and disables itself with a
"Live scoring is available on staging only" message: the exact UX
production showed immediately after PR #115 deployed live scoring.

The sitekey is public-by-design (Turnstile embeds it in HTML for
client-side widget rendering; anyone viewing page source sees it).
The matching TURNSTILE_SECRET was set on the production Worker via
`wrangler secret put TURNSTILE_SECRET` separately and is not committed.

Comment cleanup: drops the stale "deferred until production promotion"
framing on both the top-level and env.staging `vars` blocks, and adds
an explicit note about `vars` REPLACE-not-merge inheritance semantics
so a future contributor who adds a top-level var knows env.staging
will not pick it up automatically.

No env.staging change to the actual sitekey value: env.staging.vars
already explicitly defined the Cloudflare always-pass test sitekey
(1x...AA), which still wins via REPLACE semantics.
…nale

Audit of wrangler.jsonc against the WHY-only / no-temporal /
no-task-flow / no-local-doc-ref / no-instructional policy. Trimmed
eight comment blocks; net 65 lines removed from wrangler.jsonc and
41 lines of consolidated rationale added to RELEASES-RATIONALE.md.

Moves to RELEASES-RATIONALE.md (new "Wrangler env inheritance traps"
section):

- Which keys inherit from top-level (routes, triggers, vars) and
  which do not (containers, durable_objects, migrations, ratelimits,
  r2_buckets, kv_namespaces, analytics_engine_datasets).
- REPLACE-not-merge semantics on `vars`, and the future-additions
  mirroring requirement.
- The 2026-04-30 routing-drift incident as the historical context
  for the explicit `routes: []` override.
- Container app naming quirk (no automatic env-suffix).

Trims in wrangler.jsonc (each block now WHY-only with a pointer):

- Dropped the `dotfiles config/shell/telemetry.sh` reference on
  `send_metrics` (personal-repo local-doc ref).
- Dropped "first-ever" temporal on the live-scoring binding block.
- Dropped the duplicated promotion runbook on both prod and staging
  `containers[0]` blocks (lives in RELEASES.md § Sandbox image
  releases); kept the account-id-committed-deliberately WHY inline.
- Dropped `docs/plans/` references on SCORE_LIMITER_IP and SCORE_KV.
- Dropped the kill-switch flip command on SCORE_KV (lives in
  RELEASES.md § Cost guardrails).
- Replaced the routes-override block's temporal + local-doc +
  instructional content with a pointer to RATIONALE.
- Trimmed the staging triggers prophylactic to a pointer.

No behavior change. All gates pass: 737 tests, lint clean, wrangler
dry-runs on both envs clean (TURNSTILE_SITEKEY values unchanged).
@brettdavies brettdavies changed the title release: wire production Turnstile sitekey (post-#115 follow-up) release: wire prod Turnstile sitekey + wrangler.jsonc comment audit (post-#115 follow-up) May 25, 2026
@brettdavies brettdavies merged commit a5a2a1c into main May 25, 2026
5 checks passed
@brettdavies brettdavies deleted the release/2026-05-25-prod-turnstile-wiring branch May 25, 2026 06:12
brettdavies added a commit that referenced this pull request May 25, 2026
…udit from release #116 (#117)

## Summary

Back-port of PR #116 (which shipped to production via main) onto dev so
the next release branch carries the production Turnstile wiring and the
consolidated `wrangler.jsonc` comment hygiene by default, rather than
re-introducing the fail-closed gap or the duplicated runbook prose.

Two commits cherry-picked from the original
`release/2026-05-25-prod-turnstile-wiring` branch (deleted on PR #116
merge), preserving the same diff that already shipped to production:

- **`feat(worker)` c08a0fa, mirror prod TURNSTILE_SITEKEY wiring from
release #116.** Enables the top-level `vars` block in `wrangler.jsonc`
and populates `TURNSTILE_SITEKEY` with the production `sitekey` from
1Password (`agentnative-site Cloudflare Turnstile / prod.site_key`). The
matching `TURNSTILE_SECRET` lives in CF-encrypted wrangler secrets, not
committed. env.staging.vars is unchanged: it carries Cloudflare's
always-pass test `sitekey` (`1x...AA`), which under wrangler `vars`
REPLACE semantics correctly isolates staging from the new top-level
value. Without this back-port, the next release branch snapshot of dev
would re-introduce the empty `TURNSTILE_SITEKEY` and re-create the
fail-closed gap on production.
- **`docs(wrangler)` bac003d, trim comments per `/code-comments` policy
+ stash rationale.** Eight comment blocks audited against the WHY-only /
no-temporal / no-task-flow / no-local-doc-ref / no-instructional policy.
Net 65 lines removed from `wrangler.jsonc`, 41 lines added to
`RELEASES-RATIONALE.md` under a new `## Wrangler env inheritance traps`
section that consolidates which keys inherit from top-level (`routes`,
`triggers`, `vars`), which do not, the REPLACE-not-merge semantics on
`vars`, and the 2026-04-30 routing-drift incident as historical context.
Each trimmed comment in `wrangler.jsonc` keeps the WHY that's unique to
inline context and points at the consolidated rationale for the rest.

## Changelog

### Added

- Mirror the production `TURNSTILE_SITEKEY` wiring from release #116
onto dev so the next release-branch snapshot carries it. No behavior
change on dev (env.staging keeps the test `sitekey` for CLI verification
flows under `vars` REPLACE semantics).

### Changed

- Consolidate Wrangler env-inheritance rationale (`routes` / `triggers`
/ `vars` inheritance semantics, the 2026-04-30 routing-drift incident,
container app naming quirk) into `RELEASES-RATIONALE.md` § Wrangler env
inheritance traps. `wrangler.jsonc` comments now point at the
consolidated rationale.

### Documentation

- Trim eight `wrangler.jsonc` comment blocks per the `/code-comments`
policy: drop temporal phrasing (`first-ever`, `since 2026-04-30`, etc.),
drop `docs/plans/` and personal-repo references, drop instructional
voice (`Flip via:`, `Deploy with:`, `Removing this line will reintroduce
the bug`), drop runbook content that duplicates `RELEASES.md` § Sandbox
image releases.

## Type of Change

- [x] `feat`: New feature (non-breaking change which adds functionality)

The `feat` headlines because the next release-branch cut now ships the
working homepage form on production by default. Documentation cleanup
rides along.

## Related Issues/Stories

- Story: Back-port of PR #116 onto dev to prevent the next
release-branch snapshot from re-introducing the empty-`sitekey`
fail-closed state on production. PR #116 already shipped to anc.dev via
squash-merge to main; this PR aligns dev with main on the relevant
files.
- Issue: n/a
- Architecture: n/a
- Related PRs: #116 (the production release this back-ports), #115 (the
live-scoring v3 launch that ran behind the deliberate fail-closed gate),
#114 (sandbox image bump to anc 0.4.0).

## Testing

- [x] Unit tests added/updated
- [x] All tests passing

**Test Summary:**

- `bun test`: 737 pass / 0 fail across 28 files.
- `bun x wrangler deploy --dry-run`: clean. Lists `env.TURNSTILE_SITEKEY
("ff0x4AAAAAADQFMBoVm56-OPuQ")` (the real production `sitekey`).
- `bun x wrangler deploy --dry-run --env staging`: clean. Lists
`env.TURNSTILE_SITEKEY ("1x00000000000000000000AA")` (Cloudflare's
always-pass test `sitekey`, unchanged). Confirms env.staging.vars
REPLACE semantics correctly isolates staging from the new top-level
value.
- Pre-push gate: pass (lint, build, test, both wrangler dry-runs,
pack-README, banned-fonts, prose-check).

**Post-merge verification plan** (after the staging deploy on this PR's
merge):

- The staging Worker on
`agentnative-site-staging.<subdomain>.workers.dev` continues to render
its homepage form via the test `sitekey`; nothing on staging changes
because env.staging.vars REPLACE wins.
- The next release-branch cut from this dev tip carries
`TURNSTILE_SITEKEY: ff0x...` in `wrangler.jsonc` top-level by default.
No further hot-fix needed to keep the production form enabled across
future cuts.

## Files Modified

**Modified:**

- `wrangler.jsonc`: enabled the top-level `vars` block with the
production `sitekey`; trimmed eight comment blocks per `/code-comments`
policy. Net 65 lines removed.
- `RELEASES-RATIONALE.md`: new `## Wrangler env inheritance traps`
section consolidates inheritance semantics, override patterns, and the
historical context that the config-file comments previously held inline.

**Created:**

- None.

**Renamed:**

- None.

**Deleted:**

- None.
brettdavies added a commit that referenced this pull request May 25, 2026
…Turnstile 400020) (#118)

## Summary

Fast follow to PR #116. The production `TURNSTILE_SITEKEY` value pinned
in `wrangler.jsonc` was `ff0x4AAAAAADQFMBoVm56-OPuQ` (26 chars), which
the Cloudflare Turnstile widget rejects with error 400020 (Invalid
`sitekey`) on every request. Cloudflare API confirmed the dashboard's
actual `sitekey` for this account is `0x4AAAAAADQFMBoVm56-OPuQ` (24
chars, starting with `0x` per Turnstile's `sitekey` convention). The
1Password item `agentnative-site Cloudflare Turnstile / prod.site_key`
had a stray `ff` prefix from a paste error when the credential was first
saved; PR #116 dutifully read that wrong value into `wrangler.jsonc`.
The 1Password field has been corrected at source and this PR ships the
corrected pin to production.

User-visible state after PR #116 deployed:

- Homepage form was enabled (no longer in the fail-closed "Live scoring
is available on staging only" state).
- Turnstile widget tried to challenge
`https://challenges.cloudflare.com/...<key-in-path>...` with the pinned
`ff0x4...` value, got HTTP 400 back, console showed Turnstile error
400020.
- Browser console additionally showed "Call to execute() on a widget
that is already executing" because the client retries the challenge on
its own (separate client-side bug; will be filed as its own PR).
- No `/api/score` POST ever reached the Worker because the widget never
produced a token; no rate-limit or kill-switch impact; no telemetry
events written.

env.staging is unchanged: it carries Cloudflare's always-pass test
`sitekey` (`1x00000000000000000000AA`), independent of the 1Password
value.

## Changelog

### Fixed

- Correct the production `TURNSTILE_SITEKEY` value in `wrangler.jsonc`
from `ff0x4AAAAAADQFMBoVm56-OPuQ` (paste-error value carried into PR
#116 from a stale 1Password entry) to `0x4AAAAAADQFMBoVm56-OPuQ` (the
actual dashboard `sitekey` for this account). Cloudflare Turnstile error
400020 on the anc.dev homepage form clears with this deploy.

## Type of Change

- [x] `fix`: Bug fix (non-breaking change which fixes an issue)

## Related Issues/Stories

- Story: PR #116 enabled the homepage form on production after
fail-closed verification, but the pinned `TURNSTILE_SITEKEY` value had a
stray `ff` prefix from a 1Password paste error. This corrects the value
at source (1Password) and ships the corrected pin.
- Issue: n/a
- Architecture: n/a
- Related PRs: #116 (the enable-the-form release whose 1Password value
was wrong), #115 (the live-scoring v3 launch this restores to working
order).

## Testing

- [x] Unit tests added/updated
- [x] All tests passing

**Test Summary:**

- `bun test`: 737 pass / 0 fail across 28 files.
- `bun x wrangler deploy --dry-run`: clean. Lists `env.TURNSTILE_SITEKEY
("0x4AAAAAADQFMBoVm56-OPuQ")` (the corrected production `sitekey`).
- `bun x wrangler deploy --dry-run --env staging`: clean. Lists
`env.TURNSTILE_SITEKEY ("1x00000000000000000000AA")` (Cloudflare's
always-pass test `sitekey`, unchanged).
- Pre-push gate: pass (lint, build, test, both wrangler dry-runs,
pack-README, banned-fonts, prose-check).
- Cloudflare API: `GET /accounts/<id>/challenges/widgets` failed with
auth-scope error on the existing `CF_API_TOKEN`, so the dashboard
`sitekey` was retrieved manually and re-staged into 1Password via the
no-echo `stage_secret.sh` pipeline. 1Password's `prod.site_key` field is
now 24 chars matching the dashboard value.

**Post-merge verification plan** (after the production deploy on this
PR's merge):

- View source on anc.dev and confirm the meta tag `turnstile-sitekey`
carries `content="0x4AAAAAADQFMBoVm56-OPuQ"` (with `0x4` prefix, no
`ff`).
- Open anc.dev in a browser. Submit a real input (e.g., `ripgrep`).
Confirm the Turnstile challenge succeeds (invisible-mode, no widget
popup), POST to `/api/score` returns a scorecard, browser redirects to
`/live-score/<binary>` with the inline scorecard.
- Confirm browser console no longer shows Turnstile error 400020.
- Separately track: the "Call to execute() on a widget that is already
executing" warning is a client-side bug in
`src/client/live-score.ts:acquireTurnstileToken` (always-render +
always-execute pattern, needs reset()-before-execute) and the
`static.cloudflareinsights.com/beacon.min.js` CSP violation is the CF
Web Analytics auto-injected beacon hitting a CSP that doesn't list
`static.cloudflareinsights.com`. Both are out of scope here.

## Files Modified

**Modified:**

- `wrangler.jsonc`: top-level `vars.TURNSTILE_SITEKEY` changed from
`ff0x4AAAAAADQFMBoVm56-OPuQ` to `0x4AAAAAADQFMBoVm56-OPuQ` (drop stray
`ff` prefix).

**Created:**

- None.

**Renamed:**

- None.

**Deleted:**

- None.
brettdavies added a commit that referenced this pull request May 25, 2026
…119)

## Summary

`acquireTurnstileToken` called `api.render()` on every acquire, so the
second form submit produced "Call to execute() on a widget that is
already executing" in the console plus Turnstile error 400020 from
`challenges.cloudflare.com`. Render exactly once per page session, then
`reset()` + `execute()` on the existing widget id for retries. Surfaced
after #118 corrected the production `sitekey` and the form started
producing real challenges.

## Changelog

### Fixed

- `/api/score` form on anc.dev succeeds on retry submits. Pre-fix, only
the first submit per page could produce a Turnstile token; subsequent
submits silently failed with Turnstile error 400020.

## Type of Change

- [x] `fix`: Bug fix (non-breaking change which fixes an issue)

## Related Issues/Stories

- Story: Surfaced during browser-side QA after PR #118 corrected the
production `TURNSTILE_SITEKEY`.
- Issue: n/a
- Architecture: n/a
- Related PRs: #118, #116, #115.

## Testing

- [x] Unit tests added/updated
- [x] All tests passing

**Test Summary:**

- `bun test`: 737 pass / 0 fail. No client-side unit test added: no DOM
harness in Bun test today; regression test pinned to follow-up
`feat(test-infra)` PR (plan in flight).
- Manual: on staging after merge, submit twice in the same page session;
both should round-trip with zero Turnstile console warnings.

## Files Modified

**Modified:**

- `src/client/live-score.ts`: render Turnstile once; reset+execute the
existing widget id on subsequent acquires. Module-scope
`turnstileWidget` + `pendingTurnstile` slot with a `settleTurnstile`
helper rotating the resolver per acquire.

**Created:**

- None.

**Renamed:**

- None.

**Deleted:**

- None.
brettdavies added a commit that referenced this pull request May 25, 2026
…ide widget teardown (#120)

## Summary

Three fixes that surfaced sequentially while validating PR #119's
reset+execute fix on staging. Validated end-to-end at staging Worker
version `c6ab5306-b238-4e07-b41b-472858261c15`.

**Commit 1 (`3ff7b7a`), drop invalid `size: 'invisible'` + add
`execution: 'execute'`.** `acquireTurnstileToken` passed `size:
'invisible'` to `api.render()`. Per CF docs, `size` accepts `compact |
flexible | normal` only; `invisible` throws `Uncaught TurnstileError`
and puts the widget in a stuck "executing" state that masks the #119
reset+execute fix. Invisible behavior is `sitekey`-mode (set in CF
dashboard), not a render-time argument. Drop the invalid value; the
off-screen container CSS keeps the widget visually hidden. Add
`execution: 'execute'` so the challenge defers to our explicit
`api.execute()` instead of starting on render.

**Commit 2 (`1ff0b01`), allow Lato in CSP + tear down widget on
`pagehide`.** Even with the `sitekey` configured as `Invisible` mode,
Turnstile's bootstrap injects `<link rel=stylesheet
href="https://fonts.googleapis.com/css?family=Lato...">` into the host
document (defensive UI prep). Our CSP blocks it. Allowlist
`https://fonts.googleapis.com` on `style-src` and
`https://fonts.gstatic.com` on `font-src` in both `CSP_HTML`
(`src/worker/headers.ts`) and `LIVE_SCORE_CSP`
(`src/worker/score/summary-render.ts`). Separately, on `pagehide`, call
`api.remove(widgetId)` and clear module-scope state so a `bfcache`
restore can't re-bootstrap a half-dead widget and re-inject the Lato
stylesheet.

## Changelog

### Fixed

- `/api/score` homepage form on anc.dev now executes Turnstile cleanly:
zero `Uncaught TurnstileError`, zero "already executing" warnings, zero
CSP violations on initial load OR `bfcache` restore (back-button to
homepage from a result page).

### Changed

- CSP allowlist for `https://fonts.googleapis.com` on `style-src` and
`https://fonts.gstatic.com` on `font-src` (both `CSP_HTML` and
`LIVE_SCORE_CSP`). Required for Turnstile's defensive Lato bootstrap
even on Invisible-mode `sitekeys`.
- TurnstileApi type tightened to match docs: `size` is now `'compact' |
'flexible' | 'normal'`; new optional `execution` field.

## Type of Change

- [x] `fix`: Bug fix (non-breaking change which fixes an issue)

## Related Issues/Stories

- Story: Surfaced sequentially during browser-side validation of PR
#119. Each fix unmasked the next: #119 fixed the render-twice pattern,
exposing the `size:'invisible'` invalid value; fixing that unmasked the
Lato CSP gap and the `bfcache` stale-widget. All three now clean.
- Issue: n/a
- Architecture: n/a
- Related PRs: #119 (reset+execute, now actually reachable), #118, #116,
#115.

## Testing

- [x] Unit tests added/updated
- [x] All tests passing

**Test Summary:**

- `bun test`: 737 pass / 0 fail.
- `bun run lint`: clean.
- `bun run build`: clean. `dist/js/live-score.js` bundle contains
`execution:'execute'` and the `pagehide` handler; no `size:'invisible'`.
- Manual on staging at Worker version
`c6ab5306-b238-4e07-b41b-472858261c15` (deployed from this branch via
`bun x wrangler deploy --env staging`):
- First submit (`ripgrep`) round-trips, redirects to `/score/ripgrep`.
Zero console warnings.
  - Back-button to homepage. Zero CSP violations on `bfcache` restore.
  - Second submit succeeds the same as the first.

## Files Modified

**Modified:**

- `src/client/live-score.ts`: drop `size: 'invisible'` from
`api.render()`; add `execution: 'execute'`; tighten `TurnstileApi` type;
add `pagehide` listener that removes the widget + clears module-scope
state.
- `src/worker/headers.ts`: extend `CSP_HTML` `style-src` and `font-src`
to allow Google Fonts origins (Turnstile bootstrap requirement).
- `src/worker/score/summary-render.ts`: same CSP extension on
`LIVE_SCORE_CSP` for `/live-score/<binary>` pages.

**Created:**

- None.

**Renamed:**

- None.

**Deleted:**

- None.
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