Compare commits

..

93 Commits

Author SHA1 Message Date
will.anderson 348c81ac7f Merge pull request 'Deploy #154 to stage — analytics CSP fix' (#155) from dev into stage
Stage — Build, push & deploy to marketing-stage / deploy-stage (push) Failing after 8m36s
2026-05-19 17:54:09 +00:00
will.anderson c30e5903a4 Merge pull request 'Allow Google Analytics and Ads domains in CSP' (#154) from fix/csp-analytics into dev
Dev — Build & local smoke test / build-smoke (push) Successful in 2m5s
2026-05-19 17:53:52 +00:00
will.anderson c526e76d3b Allow Google Analytics and Ads domains in CSP
Dev — Build & local smoke test / build-smoke (pull_request) Successful in 1m34s
Add to connect-src: analytics.google.com, www.google.com,
www.googletagmanager.com — required for GA event beacons and
Google Ads conversion/remarketing collect endpoints.

Add to script-src: googleads.g.doubleclick.net — required for
Google Ads conversion tag script injection via GTM.
2026-05-19 12:53:36 -05:00
will.anderson a02ad7b61a Merge pull request 'Deploy #152 to stage — SyntaxError fix + CSP expansion' (#153) from dev into stage
Stage — Build, push & deploy to marketing-stage / deploy-stage (push) Failing after 8m8s
2026-05-19 17:13:56 +00:00
will.anderson 6a7b8382ea Merge pull request 'Fix SyntaxError in account-dashboard and expand CSP' (#152) from fix/syntax-error-and-csp into dev
Dev — Build & local smoke test / build-smoke (push) Successful in 2m7s
2026-05-19 17:13:39 +00:00
will.anderson d2ae0b4b60 Fix SyntaxError in account-dashboard and expand CSP
Dev — Build & local smoke test / build-smoke (pull_request) Successful in 1m37s
Replace ternary operator in native_js block with explicit if-else —
El's parser chokes on '?' adjacent to single-quoted strings inside
native_js(), causing an Uncaught SyntaxError that prevents the entire
IIFE from running and leaves signInWith undefined.

Add missing CSP entries to all three header functions:
- js.stripe.com → script-src and frame-src (Stripe JS and Elements iframe)
- fonts.googleapis.com → style-src (Google Fonts CSS)
- fonts.gstatic.com → font-src (Google Fonts files)
- static.cloudflareinsights.com → script-src (Cloudflare beacon)
2026-05-19 12:13:05 -05:00
will.anderson 611e43fee1 Merge pull request 'Stage deploy: device count cleanup' (#151) from dev into stage
Stage — Build, push & deploy to marketing-stage / deploy-stage (push) Successful in 8m54s
2026-05-14 16:38:41 +00:00
will.anderson 5c8987ef59 Merge pull request 'Clear hardcoded device count — JS owns it' (#150) from fix/devices-count-v2 into dev
Dev — Build & local smoke test / build-smoke (push) Successful in 2m35s
2026-05-14 16:38:26 +00:00
will.anderson bfcb325352 Clear hardcoded device count — JS sets it from plan data
Dev — Build & local smoke test / build-smoke (pull_request) Successful in 1m54s
2026-05-14 11:38:05 -05:00
will.anderson 61c3b1cfe9 Merge pull request 'Stage deploy: device count fix' (#149) from dev into stage
Stage — Build, push & deploy to marketing-stage / deploy-stage (push) Successful in 9m8s
2026-05-14 16:36:35 +00:00
will.anderson 57e9cafc95 Merge pull request 'Fix device count: 1 for free, 2 for professional/founding' (#148) from fix/devices-count into dev
Dev — Build & local smoke test / build-smoke (push) Successful in 2m13s
2026-05-14 16:36:20 +00:00
will.anderson 632d95000c Fix device count: show 1 for free plan, 2 for professional/founding
Dev — Build & local smoke test / build-smoke (pull_request) Successful in 1m38s
2026-05-14 11:36:02 -05:00
will.anderson ab83b1653c Merge pull request 'Stage deploy: fix account el_meta SIGSEGV' (#147) from dev into stage
Stage — Build, push & deploy to marketing-stage / deploy-stage (push) Failing after 7m36s
2026-05-13 17:42:07 +00:00
will.anderson 77807d30af Merge pull request 'Fix account SIGSEGV: el_meta 1-arg → 2-arg' (#146) from fix/account-el-meta into dev
Dev — Build & local smoke test / build-smoke (push) Successful in 2m13s
2026-05-13 17:41:53 +00:00
will.anderson 8f91a80be7 Fix account page SIGSEGV: el_meta extern arity mismatch (1-arg → 2-arg)
Dev — Build & local smoke test / build-smoke (pull_request) Successful in 1m42s
el_meta was declared as el_meta(attrs) in account.el but the runtime
C implementation expects el_meta(name, content). Same arity crash as
the prior el_img fix — runtime passed garbage on the stack.
2026-05-13 12:41:37 -05:00
will.anderson 752cc415d1 Merge pull request 'Stage deploy: fix account SIGSEGV' (#145) from dev into stage
Stage — Build, push & deploy to marketing-stage / deploy-stage (push) Failing after 7m22s
2026-05-13 17:20:43 +00:00
will.anderson d7bb92c37f Merge pull request 'Fix account page SIGSEGV: el_img extern arity mismatch' (#144) from fix/magic-link-flow into dev
Dev — Build & local smoke test / build-smoke (push) Successful in 2m20s
2026-05-13 17:20:06 +00:00
will.anderson 4ca793ee2c Fix account page SIGSEGV: el_img extern signature mismatch
Dev — Build & local smoke test / build-smoke (pull_request) Successful in 1m46s
account.el declared el_img with 1 arg (attrs only) while the runtime
implementation and all other files use 3 args (src, alt, attrs).
The arity mismatch caused the server to crash with signal 11 on every
request — TCP probe passed (bind was fine) but first HTTP hit segfaulted.
2026-05-13 12:19:43 -05:00
will.anderson 0a599ec149 Merge pull request 'Stage deploy: fix free plan payment init' (#143) from dev into stage
Stage — Build, push & deploy to marketing-stage / deploy-stage (push) Failing after 12m58s
Stage deploy: fix free plan payment init
2026-05-13 17:12:52 +00:00
will.anderson 4123f6d5f1 Merge pull request 'Fix free plan checkout: SetupIntent instead of $0 PaymentIntent' (#142) from fix/free-plan-setup-intent into dev
Dev — Build & local smoke test / build-smoke (push) Successful in 1m59s
Fix free plan checkout: SetupIntent instead of $0 PaymentIntent
2026-05-13 17:12:35 +00:00
will.anderson 69f348d48b Fix free plan checkout: use SetupIntent instead of $0 PaymentIntent
Dev — Build & local smoke test / build-smoke (pull_request) Successful in 1m40s
Stripe rejects amount=0 PaymentIntents. Free plan age verification should
use a SetupIntent (no charge, saves payment method). The JS already handles
setup_mode:true by calling stripe.confirmSetup instead of confirmPayment.
Mirrors the existing professional-later SetupIntent path.
2026-05-13 12:12:10 -05:00
will.anderson 6dedb97719 Merge pull request 'Stage deploy: fix about page El tokenizer rendering' (#141) from dev into stage
Stage — Build, push & deploy to marketing-stage / deploy-stage (push) Failing after 8m15s
Stage deploy: fix about page El tokenizer rendering
2026-05-13 16:46:58 +00:00
will.anderson 3d635505bc Merge pull request 'Fix about page: restore raw string syntax to fix El tokenizer rendering' (#140) from fix/about-rendering into dev
Dev — Build & local smoke test / build-smoke (push) Successful in 2m17s
Fix about page: restore raw string syntax to fix El tokenizer rendering
2026-05-13 16:46:43 +00:00
will.anderson 675c467a74 Fix about page rendering: restore raw string syntax to fix El tokenizer mangling
Dev — Build & local smoke test / build-smoke (pull_request) Successful in 1m45s
The El HTML template parser (native { } syntax introduced in 5cb13d6) strips
spaces from text nodes, drops & from HTML entities (' → 39;), and breaks
hyphenated attribute names (aria-label → aria - label). All other component
files were already converted to the extern el_*() function style in 2553a6b
which is immune to this issue. about.el was the only page still using the
broken template syntax. Restoring the raw string return style fixes all
rendering defects on /about.
2026-05-13 11:46:13 -05:00
will.anderson 8528080e85 Merge pull request 'Deploy dev to stage — magic link sign-in fix' (#139) from dev into stage
Stage — Build, push & deploy to marketing-stage / deploy-stage (push) Successful in 7m4s
2026-05-12 19:33:21 +00:00
will.anderson 708dfd06cb Merge pull request 'Fix magic-link sign-in: implicit flow + redirect to /account' (#138) from fix/magic-link-flow into dev
Dev — Build & local smoke test / build-smoke (push) Successful in 2m14s
2026-05-12 19:33:03 +00:00
will.anderson b6aecd7d89 Fix magic-link sign-in: implicit flow + redirect to /account
Dev — Build & local smoke test / build-smoke (pull_request) Successful in 1m42s
account-auth.el was using flowType:'pkce' while account-dashboard.el
uses 'implicit'. After the OTP redirect, the dashboard's implicit
client couldn't exchange the PKCE code — so the sign-in silently
failed. Fix: match implicit flow across both clients.

Also adds emailRedirectTo so the link lands on /account instead of
the site root.
2026-05-12 14:32:39 -05:00
will.anderson 3c19f4cf73 Merge pull request 'Deploy dev to stage — Stripe dedup + attestation bypass fix' (#137) from dev into stage
Stage — Build, push & deploy to marketing-stage / deploy-stage (push) Successful in 7m17s
2026-05-12 19:23:54 +00:00
will.anderson bb98f76179 Merge pull request 'Fix duplicate Stripe customers and attestation plan bypass' (#136) from fix/stripe-dedup-attestation into dev
Dev — Build & local smoke test / build-smoke (push) Successful in 2m4s
2026-05-12 19:23:33 +00:00
will.anderson 0fdbba82e0 Fix duplicate Stripe customers and attestation plan bypass
Dev — Build & local smoke test / build-smoke (pull_request) Successful in 1m29s
Two bugs:

1. Double-Bearer auth on Stripe customer search. Both checkout paths
   were passing "Bearer sk_..." to http_get_auth(), which prepends
   another "Bearer " — producing "Bearer Bearer sk_..." which Stripe
   rejects as 401. Customer lookup always failed, so a new Stripe
   customer was created on every checkout page load. Fix: pass the
   raw key to http_get_auth(), letting it handle the prefix.

2. /api/attest blindly wrote whatever plan the client submitted to
   the waitlist, letting anyone POST plan=founding and get founding
   member access without paying. Fix: server ignores the client-
   submitted plan and always writes plan=waitlist. Founding access
   requires Stripe payment — the attestation form is waitlist-only.
2026-05-12 14:10:04 -05:00
will.anderson b5285ccb74 Merge pull request 'Deploy dev to stage — webhook user_metadata fix, textarea, initStripe' (#135) from dev into stage
Stage — Build, push & deploy to marketing-stage / deploy-stage (push) Successful in 8m13s
2026-05-12 17:50:07 +00:00
will.anderson 9e0451be41 Merge pull request 'Fix initStripe load order, subscription webhook email, chat textarea' (#134) from fix/webhook-initstripe-textarea into dev
Dev — Build & local smoke test / build-smoke (push) Successful in 2m11s
Merge PR #134: Fix initStripe load order, webhook user_metadata, chat textarea
2026-05-12 17:49:26 +00:00
will.anderson ba776153a9 Merge pull request 'dev → stage' (#133) from dev into stage
Stage — Build, push & deploy to marketing-stage / deploy-stage (push) Successful in 7m48s
2026-05-12 14:03:42 +00:00
will.anderson 853d73855d Merge pull request 'dev → stage' (#131) from dev into stage
Stage — Build, push & deploy to marketing-stage / deploy-stage (push) Successful in 8m26s
2026-05-12 13:53:31 +00:00
will.anderson 3ed43c0037 Merge pull request 'dev → stage: binary assets, payment fix, checkout layout' (#129) from dev into stage
Stage — Build, push & deploy to marketing-stage / deploy-stage (push) Successful in 7m4s
2026-05-12 01:11:23 +00:00
will.anderson 69ae0ca891 Merge pull request 'dev → stage: free plan $0 PaymentIntent' (#126) from dev into stage
Stage — Build, push & deploy to marketing-stage / deploy-stage (push) Successful in 7m25s
2026-05-12 00:46:14 +00:00
will.anderson 876f2afe27 Merge pull request 'dev → stage: free checkout Stripe fix + copy updates' (#124) from dev into stage
Stage — Build, push & deploy to marketing-stage / deploy-stage (push) Successful in 6m51s
2026-05-12 00:16:26 +00:00
will.anderson d12f0375f8 Merge pull request 'dev → stage: force full build after registry cleanup' (#122) from dev into stage
Stage — Build, push & deploy to marketing-stage / deploy-stage (push) Successful in 7m23s
2026-05-11 23:56:57 +00:00
will.anderson 6c2f423548 Merge pull request 'dev → stage: trigger rebuild' (#120) from dev into stage 2026-05-11 23:46:43 +00:00
will.anderson c6fb24498a Merge pull request 'ci: trigger workflow via dist/ change' (#118) from ci/trigger-rebuild into stage
Stage — Build, push & deploy to marketing-stage / deploy-stage (push) Failing after 42s
2026-05-11 23:27:43 +00:00
will.anderson 3e230e52e5 ci: touch dist/ to trigger workflow rebuild 2026-05-11 18:27:31 -05:00
will.anderson f06850eb1a Merge pull request 'ci: re-trigger #3' (#117) from ci/trigger-rebuild into stage 2026-05-11 23:25:53 +00:00
will.anderson 0a4d454765 ci: re-trigger #3 2026-05-11 18:25:41 -05:00
will.anderson 7bc2a8e8f6 Merge pull request 'ci: re-trigger build after runner restart' (#116) from ci/trigger-rebuild into stage 2026-05-11 22:59:51 +00:00
will.anderson c4c30f1b33 ci: re-trigger after runner restart 2026-05-11 17:59:40 -05:00
will.anderson ae1a87de98 Merge pull request 'ci: rebuild after registry cleanup' (#115) from ci/trigger-rebuild into stage 2026-05-11 22:34:23 +00:00
will.anderson f5cbc15b43 Merge pull request 'dev → stage: fix HTML escaping in headings and button' (#114) from dev into stage
Stage — Build, push & deploy to marketing-stage / deploy-stage (push) Successful in 8m5s
2026-05-11 22:27:28 +00:00
will.anderson e148e6987d Merge pull request 'Force full El rebuild — strip CGI content' (#111) from dev into stage
Stage — Build, push & deploy to marketing-stage / deploy-stage (push) Successful in 7m44s
2026-05-11 21:43:20 +00:00
will.anderson 9554430b7e Merge pull request 'Also skip El rebuild for workflow-only changes' (#108) from dev into stage
Stage — Build, push & deploy to marketing-stage / deploy-stage (push) Successful in 8m23s
2026-05-11 20:47:04 +00:00
will.anderson 9685a42c7d Merge pull request 'Skip El rebuild for migration/script/test-only changes' (#106) from dev into stage
Stage — Build, push & deploy to marketing-stage / deploy-stage (push) Successful in 11m48s
2026-05-11 20:45:32 +00:00
will.anderson 9650dad951 Merge pull request 'Update CORS test: no-Origin requests are allowed' (#104) from dev into stage
Stage — Build, push & deploy to marketing-stage / deploy-stage (push) Successful in 8m44s
2026-05-11 20:22:39 +00:00
will.anderson c3aec8947a Merge pull request 'Fix stage source guard: fetch origin/dev before ancestry check' (#102) from dev into stage
Stage — Build, push & deploy to marketing-stage / deploy-stage (push) Failing after 9m28s
2026-05-11 19:09:52 +00:00
will.anderson 441d6d7cb5 Fix: idempotent migration policy creation
Stage — Build, push & deploy to marketing-stage / deploy-stage (push) Failing after 12s
2026-05-11 18:56:50 +00:00
will.anderson de9bf25437 Merge pull request 'dev → stage: fix CI migration heredoc YAML parse error' (#98) from dev into stage
Stage — Build, push & deploy to marketing-stage / deploy-stage (push) Failing after 23s
Merge PR #98 from dev into stage
2026-05-11 18:34:15 +00:00
will.anderson a59fdf4baa Merge pull request 'dev → stage: fix supabase-config null-origin CORS bug' (#96) from dev into stage
Merge PR #96 from dev into stage
2026-05-11 18:31:00 +00:00
will.anderson ae633d3f71 Merge pull request 'dev → stage: wire Supabase migrations into CI/CD' (#94) from dev into stage
Merge PR #94 from dev into stage
2026-05-11 18:22:17 +00:00
will.anderson 43b5286fd5 Merge pull request 'dev → stage: pricing buttons, API keys, enterprise contacts' (#92) from dev into stage
Stage — Build, push & deploy to marketing-stage / deploy-stage (push) Failing after 7m55s
Merge dev into stage
2026-05-11 18:06:02 +00:00
will.anderson 04641ed1a3 Merge pull request 'Stage: CI fixes, pricing buttons, API key provisioning' (#90) from fix/stage-ci-paths into stage
Stage — Build, push & deploy to marketing-stage / deploy-stage (push) Failing after 10s
2026-05-11 17:49:37 +00:00
will.anderson f4a202e220 Merge pull request 'dev → stage: CSP unsafe-eval fix' (#89) from dev into stage
Stage — Build, push & deploy to marketing-stage / deploy-stage (push) Failing after 7m59s
Merge dev into stage
2026-05-11 16:45:12 +00:00
will.anderson 3482e7e0f5 Merge pull request 'dev → stage: remove --obfuscate (CSP/eval fix)' (#87) from dev into stage
Stage — Build, push & deploy to marketing-stage / deploy-stage (push) Failing after 2m31s
2026-05-11 16:11:58 +00:00
will.anderson beee0f99a7 Merge pull request 'Stage: fix Stripe CDN mock + free-plan waitForLoadState sync' (#85) from dev into stage
Stage: fix Stripe CDN mock + free-plan waitForLoadState sync
2026-05-11 15:36:35 +00:00
will.anderson 4b70e8c186 Merge pull request 'Fix Stripe CDN mock override and free-plan sync guards in E2E tests' (#83) from fix/stage-ci-paths into stage
Stage — Build, push & deploy to marketing-stage / deploy-stage (push) Failing after 11s
Fix Stripe CDN mock override and free-plan sync guards in E2E tests
2026-05-11 14:55:22 +00:00
will.anderson f9a5f93070 Merge pull request 'Stage: fix CI JS corruption from obfuscator stdout + flaky test guards' (#82) from dev into stage
Stage — Build, push & deploy to marketing-stage / deploy-stage (push) Failing after 20m46s
Stage: fix CI JS corruption + flaky test guards
2026-05-11 14:16:48 +00:00
will.anderson 8e2deab5cb Merge pull request 'Stage: free plan age verification + soul demo personalization' (#80) from dev into stage
Stage — Build, push & deploy to marketing-stage / deploy-stage (push) Failing after 23m41s
Stage: free plan age verification + soul demo personalization
2026-05-11 07:05:52 +00:00
will.anderson ddeca2250e Merge pull request 'dev → stage: CI paths + comprehensive checkout tests' (#78) from dev into stage
Stage — Build, push & deploy to marketing-stage / deploy-stage (push) Failing after 20m10s
2026-05-11 06:21:21 +00:00
will.anderson d228701828 Merge pull request 'dev → stage: comprehensive checkout + Stripe tests' (#76) from dev into stage
Merge: dev into stage — comprehensive checkout + Stripe tests
2026-05-11 06:19:33 +00:00
will.anderson 41f27e83aa Merge pull request 'test: full Playwright + API test suite for stage' (#74) from dev into stage
Stage — Build, push & deploy to marketing-stage / deploy-stage (push) Successful in 6m43s
Merge dev into stage
2026-05-11 05:29:33 +00:00
will.anderson 533436e2c2 Merge pull request 'security: pentest fixes — deploy to stage' (#70) from dev into stage
Stage — Build, push & deploy to marketing-stage / deploy-stage (push) Successful in 3m32s
security: pentest fixes — deploy to stage
2026-05-11 04:57:20 +00:00
will.anderson aeea037e6f Merge pull request 'feat: auth-gate demo chat + budget circuit breaker' (#68) from dev into stage
Stage — Build, push & deploy to marketing-stage / deploy-stage (push) Successful in 3m53s
feat: auth-gate demo chat + budget circuit breaker
2026-05-11 04:45:56 +00:00
will.anderson 41bad94368 Merge pull request 'feat: scale fixes — max-instances, asset caching, shared rate limits, global cap' (#66) from dev into stage
Stage — Build, push & deploy to marketing-stage / deploy-stage (push) Successful in 4m15s
feat: scale fixes — max-instances, asset caching, shared rate limits, global cap
2026-05-11 03:12:54 +00:00
will.anderson 3020b4e902 Merge pull request 'feat: extract soul-demo into standalone Cloud Run service' (#64) from dev into stage
Stage — Build, push & deploy to marketing-stage / deploy-stage (push) Successful in 4m5s
feat: extract soul-demo into standalone Cloud Run service
2026-05-11 02:09:27 +00:00
will.anderson e82425a829 Merge pull request 'deploy: fix HAVE_CURL verification' (#62) from dev into stage
Stage — Build, push & deploy to marketing-stage / deploy-stage (push) Successful in 3m14s
2026-05-11 01:07:49 +00:00
will.anderson c4cdb31529 Merge pull request 'deploy: fix HAVE_CURL — enable chat proxy to soul-demo' (#60) from dev into stage
Stage — Build, push & deploy to marketing-stage / deploy-stage (push) Failing after 57s
deploy: fix HAVE_CURL — enable chat proxy to soul-demo
2026-05-11 01:03:41 +00:00
will.anderson a1c0cc090d Merge pull request 'Deploy: replace k3s with direct soul-demo watchdog' (#58) from dev into stage
Stage — Build, push & deploy to marketing-stage / deploy-stage (push) Successful in 2m54s
Merge dev into stage
2026-05-11 00:47:16 +00:00
will.anderson 7df96a2273 Merge pull request 'Deploy: fix envelope truncation' (#56) from dev into stage
Stage — Build, push & deploy to marketing-stage / deploy-stage (push) Successful in 3m50s
2026-05-11 00:23:38 +00:00
will.anderson d3b890b739 Merge pull request 'Deploy: fix JS served as JSON envelope' (#54) from dev into stage
Stage — Build, push & deploy to marketing-stage / deploy-stage (push) Successful in 3m11s
Deploy: fix JS served as JSON envelope
2026-05-10 22:34:57 +00:00
will.anderson 3f069eeb79 Merge pull request 'Fix checkout auth (dev → stage)' (#52) from dev into stage
Stage — Build, push & deploy to marketing-stage / deploy-stage (push) Successful in 3m14s
Fix checkout auth (dev → stage)
2026-05-10 22:01:17 +00:00
will.anderson 8676751ed6 Merge pull request 'Fix http handler registration (dev → stage)' (#50) from dev into stage
Stage — Build, push & deploy to marketing-stage / deploy-stage (push) Successful in 3m40s
Merge PR #50: Fix http handler registration (dev → stage)
2026-05-10 18:37:16 +00:00
will.anderson a4f5312069 Merge pull request 'Fix GLIBC_2.38 mismatch: switch base image to ubuntu:24.04' (#48) from dev into stage
Stage — Build, push & deploy to marketing-stage / deploy-stage (push) Successful in 3m37s
Fix GLIBC_2.38 mismatch: switch base image to ubuntu:24.04
2026-05-10 18:02:14 +00:00
will.anderson c76e5a19eb Merge pull request 'Non-blocking entrypoint + k3s --flannel-iface fix' (#46) from dev into stage
Stage — Build, push & deploy to marketing-stage / deploy-stage (push) Failing after 4m8s
Non-blocking entrypoint + k3s flannel-iface fix
2026-05-10 17:55:12 +00:00
will.anderson 58b7b32cdd Single-stage Dockerfile.stage: pre-download k3s on host runner
Stage — Build, push & deploy to marketing-stage / deploy-stage (push) Failing after 12s
2026-05-10 16:27:07 +00:00
will.anderson 0fdabcce86 Merge pull request 'promote: dev → stage' (#42) from dev into stage
Stage — Build, push & deploy to marketing-stage / deploy-stage (push) Failing after 4m46s
promote: dev → stage
2026-05-10 15:57:36 +00:00
will.anderson 79de47de2c Merge pull request 'promote: dev → stage' (#40) from dev into stage
Stage — Build, push & deploy to marketing-stage / deploy-stage (push) Failing after 10m59s
2026-05-10 02:26:35 +00:00
will.anderson 45963154d9 Merge pull request 'promote: dev → stage' (#35) from dev into stage
Stage — Build, push & deploy to marketing-stage / deploy-stage (push) Failing after 14m42s
2026-05-10 01:32:52 +00:00
will.anderson aabaa2ffb0 Merge pull request 'promote: dev → stage' (#33) from dev into stage
Stage — Build, push & deploy to marketing-stage / deploy-stage (push) Failing after 15m11s
2026-05-10 01:07:20 +00:00
will.anderson d5dcb08ec6 Merge pull request 'promote: dev → stage (soul-demo image tar fix)' (#31) from dev into stage
Stage — Build, push & deploy to marketing-stage / deploy-stage (push) Failing after 24s
2026-05-10 01:01:01 +00:00
will.anderson 20a36eeb9e Merge pull request 'promote: dev → stage' (#29) from dev into stage
Stage — Build, push & deploy to marketing-stage / deploy-stage (push) Failing after 3m11s
2026-05-10 00:34:47 +00:00
will.anderson 32a179c24a Merge pull request 'promote: dev → stage' (#27) from dev into stage
Stage — Build, push & deploy to marketing-stage / deploy-stage (push) Failing after 37s
2026-05-10 00:12:47 +00:00
will.anderson 6bc026de19 Merge pull request 'promote: dev → stage' (#25) from dev into stage
Stage — Build, push & deploy to marketing-stage / deploy-stage (push) Failing after 21s
2026-05-09 23:44:30 +00:00
will.anderson 0ae526b72e Merge pull request 'promote: dev → stage' (#23) from dev into stage
Stage — Build, push & deploy to marketing-stage / deploy-stage (push) Failing after 22s
2026-05-09 23:35:56 +00:00
will.anderson 8221aef605 promote: dev → stage
Stage — Build, push & deploy to marketing-stage / deploy-stage (push) Failing after 37s
2026-05-09 18:34:59 +00:00
will.anderson f8487c43a0 Merge branch 'dev' into stage
Stage — Build, push & deploy to marketing-stage / deploy-stage (push) Failing after 14m16s
2026-05-09 17:41:09 +00:00
will.anderson 36b99dd9e2 Merge branch 'dev' into stage
Stage — Build, push & deploy to marketing-stage / deploy-stage (push) Failing after 6s
2026-05-09 17:32:23 +00:00
5 changed files with 94 additions and 77 deletions
+53 -52
View File
@@ -8,41 +8,41 @@
from nav import { nav }
fn about_page() -> String {
return {nav()}
return nav() + "
<main id="about" style="padding: clamp(7rem, 18vh, 11rem) 2.5rem clamp(5rem, 12vh, 8rem);">
<div style="max-width: 700px; margin: 0 auto;">
<main id=\"about\" style=\"padding: clamp(7rem, 18vh, 11rem) 2.5rem clamp(5rem, 12vh, 8rem);\">
<div style=\"max-width: 700px; margin: 0 auto;\">
<p class="label animate-up-1" style="margin-bottom: 2rem;">About</p>
<h1 class="display-lg animate-up-2" style="margin-bottom: 2.5rem; max-width: 22rem;">
<p class=\"label animate-up-1\" style=\"margin-bottom: 2rem;\">About</p>
<h1 class=\"display-lg animate-up-2\" style=\"margin-bottom: 2.5rem; max-width: 22rem;\">
Hi. I&#39;m Will.
</h1>
<div class="navy-line-left animate-up-3" style="width: 4rem; margin-bottom: 3rem;"></div>
<div class=\"navy-line-left animate-up-3\" style=\"width: 4rem; margin-bottom: 3rem;\"></div>
<!-- Photo + opening -->
<div class="reveal" style="display: flex; align-items: flex-start; gap: 2.5rem; margin-bottom: 3rem; flex-wrap: wrap;">
<img src="/assets/will.png" alt="Will Anderson" style="width: 160px; height: 160px; border-radius: 50%; object-fit: cover; flex-shrink: 0;">
<div style="flex: 1; min-width: 260px;">
<p style="font-family: var(--body); font-weight: 300; font-size: clamp(0.9rem, 1.5vw, 1.0625rem); color: var(--t2); line-height: 1.9; margin-bottom: 1.5rem;">
<div class=\"reveal\" style=\"display: flex; align-items: flex-start; gap: 2.5rem; margin-bottom: 3rem; flex-wrap: wrap;\">
<img src=\"/assets/will.png\" alt=\"Will Anderson\" style=\"width: 160px; height: 160px; border-radius: 50%; object-fit: cover; flex-shrink: 0;\">
<div style=\"flex: 1; min-width: 260px;\">
<p style=\"font-family: var(--body); font-weight: 300; font-size: clamp(0.9rem, 1.5vw, 1.0625rem); color: var(--t2); line-height: 1.9; margin-bottom: 1.5rem;\">
I grew up in Fort Smith, Arkansas, in the kind of instability where home is a moving target - roughly thirty addresses before I was fifteen, parents struggling with addiction, the material precarity that comes with all of that. I left home at fifteen, stayed with friends until I finished high school, found my way to college. At fourteen I&#39;d already found software, writing C++ at the public library because it was the first thing in my life that responded to precision with correctness, and that property turned out to matter more to me than almost anything else.
</p>
</div>
</div>
<!-- Career -->
<div class="reveal" style="margin-bottom: 3rem;">
<p style="font-family: var(--body); font-weight: 300; font-size: clamp(0.9rem, 1.5vw, 1.0625rem); color: var(--t2); line-height: 1.9; margin-bottom: 1.5rem;">
<div class=\"reveal\" style=\"margin-bottom: 3rem;\">
<p style=\"font-family: var(--body); font-weight: 300; font-size: clamp(0.9rem, 1.5vw, 1.0625rem); color: var(--t2); line-height: 1.9; margin-bottom: 1.5rem;\">
I dropped out of college, worked, went back as an adult to finish my degree, and built my skills across nearly twenty years and every kind of organization - international consulting, early-stage startups, Fortune 5 enterprises. Logistics, retail, entertainment, hospitality, industrial automation, insurance, healthcare, financial services. I trained under Juval L&#246;wy at IDesign and worked with him as a consultant from 2015 to 2021, which is where I learned what it actually means to practice software engineering as a discipline rather than an improvisation.
</p>
</div>
<!-- Blockquote -->
<blockquote class="reveal" style="
<blockquote class=\"reveal\" style=\"
border-left: 3px solid var(--navy);
padding: 0.5rem 0 0.5rem 2rem;
margin: 0 0 3rem;
">
<p style="
\">
<p style=\"
font-family: var(--head);
font-size: clamp(1.4rem, 3vw, 2rem);
font-weight: 500;
@@ -50,42 +50,42 @@ fn about_page() -> String {
color: var(--t1);
line-height: 1.35;
letter-spacing: -0.01em;
">
\">
Software shouldn&#39;t be hard. The complexity should live in the problem domain - not in the tools and processes we impose on ourselves.
</p>
</blockquote>
<!-- What I saw -->
<div class="reveal" style="margin-bottom: 3rem;">
<p class="label" style="margin-bottom: 1.25rem;">What I saw</p>
<p style="font-family: var(--body); font-weight: 300; font-size: clamp(0.9rem, 1.5vw, 1.0625rem); color: var(--t2); line-height: 1.9; margin-bottom: 1.5rem;">
<div class=\"reveal\" style=\"margin-bottom: 3rem;\">
<p class=\"label\" style=\"margin-bottom: 1.25rem;\">What I saw</p>
<p style=\"font-family: var(--body); font-weight: 300; font-size: clamp(0.9rem, 1.5vw, 1.0625rem); color: var(--t2); line-height: 1.9; margin-bottom: 1.5rem;\">
Across nearly twenty years I watched software get built at organizations with real stakes and real consequences, and I watched AI go from promise to product - watched the same mistake get made at each iteration: tools built to serve the organization&#39;s needs, not the person&#39;s. Engagement over relationship. Features over memory. Policies where values should be. The fundamental premise that you are a user, not a person, has been so thoroughly baked into the architecture of every major AI system that it doesn&#39;t register as a choice anymore. It&#39;s treated as the natural condition of the technology.
</p>
<p style="font-family: var(--body); font-weight: 300; font-size: clamp(0.9rem, 1.5vw, 1.0625rem); color: var(--t2); line-height: 1.9;">
<p style=\"font-family: var(--body); font-weight: 300; font-size: clamp(0.9rem, 1.5vw, 1.0625rem); color: var(--t2); line-height: 1.9;\">
It is not. It is a design decision. And it is the wrong one.
</p>
</div>
<div class="navy-line-center reveal" style="margin-bottom: 3rem;"></div>
<div class=\"navy-line-center reveal\" style=\"margin-bottom: 3rem;\"></div>
<!-- What I built -->
<div class="reveal" style="margin-bottom: 3rem;">
<p class="label" style="margin-bottom: 1.25rem;">What I built</p>
<p style="font-family: var(--body); font-weight: 300; font-size: clamp(0.9rem, 1.5vw, 1.0625rem); color: var(--t2); line-height: 1.9; margin-bottom: 1.5rem;">
<div class=\"reveal\" style=\"margin-bottom: 3rem;\">
<p class=\"label\" style=\"margin-bottom: 1.25rem;\">What I built</p>
<p style=\"font-family: var(--body); font-weight: 300; font-size: clamp(0.9rem, 1.5vw, 1.0625rem); color: var(--t2); line-height: 1.9; margin-bottom: 1.5rem;\">
Neuron is what I built in response to that. Not a startup in the traditional sense - no team, no funding, no press release - one person, nearly two years of work, and a conviction that this can be done differently. I wrote the memory architecture, I built the inference infrastructure, because the tools that existed weren&#39;t sufficient for what I was trying to build and so I built those too.
</p>
<p style="font-family: var(--body); font-weight: 300; font-size: clamp(0.9rem, 1.5vw, 1.0625rem); color: var(--t2); line-height: 1.9;">
<p style=\"font-family: var(--body); font-weight: 300; font-size: clamp(0.9rem, 1.5vw, 1.0625rem); color: var(--t2); line-height: 1.9;\">
Use it long enough and you&#39;ll understand why I couldn&#39;t have gotten there on top of existing infrastructure. Some things have to be built from the ground up to be built right.
</p>
</div>
<!-- What I believe -->
<div class="reveal" style="margin-bottom: 3.5rem;">
<p class="label" style="margin-bottom: 1.25rem;">What I believe</p>
<p style="font-family: var(--body); font-weight: 300; font-size: clamp(0.9rem, 1.5vw, 1.0625rem); color: var(--t2); line-height: 1.9; margin-bottom: 1.5rem;">
<div class=\"reveal\" style=\"margin-bottom: 3.5rem;\">
<p class=\"label\" style=\"margin-bottom: 1.25rem;\">What I believe</p>
<p style=\"font-family: var(--body); font-weight: 300; font-size: clamp(0.9rem, 1.5vw, 1.0625rem); color: var(--t2); line-height: 1.9; margin-bottom: 1.5rem;\">
AI has genuine potential to free people to do work that actually matters to them - not to create engagement loops, not to harvest attention, but to actually serve the person sitting in front of it. That potential is almost entirely unrealized, not because the technology isn&#39;t capable, but because the incentives that shaped it were never oriented toward the person.
</p>
<p style="
<p style=\"
font-family: var(--head);
font-size: clamp(1.2rem, 2.5vw, 1.625rem);
font-weight: 600;
@@ -93,22 +93,22 @@ fn about_page() -> String {
line-height: 1.35;
letter-spacing: -0.01em;
margin-bottom: 1.5rem;
">
\">
Build AI that earns the trust it&#39;s given.
</p>
<p style="font-family: var(--body); font-weight: 300; font-size: clamp(0.9rem, 1.5vw, 1.0625rem); color: var(--t2); line-height: 1.9;">
<p style=\"font-family: var(--body); font-weight: 300; font-size: clamp(0.9rem, 1.5vw, 1.0625rem); color: var(--t2); line-height: 1.9;\">
I don&#39;t know if Neuron will work at the scale I&#39;m imagining. But I know it&#39;s worth finding out, and I know I&#39;m not going back to the other way of building things.
</p>
</div>
<div class="navy-line-center reveal" style="margin-bottom: 3rem;"></div>
<div class=\"navy-line-center reveal\" style=\"margin-bottom: 3rem;\"></div>
<!-- CTA -->
<div class="reveal">
<p style="font-family: var(--body); font-weight: 300; font-size: clamp(0.9rem, 1.5vw, 1.0625rem); color: var(--t2); line-height: 1.9; margin-bottom: 1.5rem;">
<div class=\"reveal\">
<p style=\"font-family: var(--body); font-weight: 300; font-size: clamp(0.9rem, 1.5vw, 1.0625rem); color: var(--t2); line-height: 1.9; margin-bottom: 1.5rem;\">
Neuron opens to founding members on May 1st. 1,000 spots. That&#39;s how it starts.
</p>
<a href="/#pricing" class="btn-primary">
<a href=\"/#pricing\" class=\"btn-primary\">
Join as a founding member &#8594;
</a>
</div>
@@ -116,34 +116,35 @@ fn about_page() -> String {
</div>
</main>
<footer id="footer" aria-label="Footer">
<div class="container">
<div class="footer-inner">
<footer id=\"footer\" aria-label=\"Footer\">
<div class=\"container\">
<div class=\"footer-inner\">
<a href="/" class="footer-brand" aria-label="Neuron home" style="display:flex;flex-direction:column;align-items:center;">
<img src="/assets/brand/neuron-wordmark-on-light.png" srcset="/assets/brand/neuron-wordmark-on-light@2x.png 2x" alt="Neuron" height="24" style="display:block;margin-bottom:0.35rem;">
<p class="footer-brand-tagline">Built Different.</p>
<a href=\"/\" class=\"footer-brand\" aria-label=\"Neuron home\" style=\"display:flex;flex-direction:column;align-items:center;\">
<img src=\"/assets/brand/neuron-wordmark-on-light.png\" srcset=\"/assets/brand/neuron-wordmark-on-light@2x.png 2x\" alt=\"Neuron\" height=\"24\" style=\"display:block;margin-bottom:0.35rem;\">
<p class=\"footer-brand-tagline\">Built Different.</p>
</a>
<div class="footer-center">
<div class="navy-line"></div>
<div class=\"footer-center\">
<div class=\"navy-line\"></div>
</div>
<div class="footer-right">
<p class="footer-domain">neurontechnologies.ai</p>
<nav class="footer-nav" aria-label="Footer navigation">
<a href="/legal/terms">Terms</a>
<a href="/legal/enterprise-terms">Enterprise Agreement</a>
<a href="mailto:legal@neurontechnologies.ai">Contact</a>
<div class=\"footer-right\">
<p class=\"footer-domain\">neurontechnologies.ai</p>
<nav class=\"footer-nav\" aria-label=\"Footer navigation\">
<a href=\"/legal/terms\">Terms</a>
<a href=\"/legal/enterprise-terms\">Enterprise Agreement</a>
<a href=\"mailto:legal@neurontechnologies.ai\">Contact</a>
</nav>
</div>
</div>
<div class="footer-bottom">
<p class="footer-copy">&copy; 2026 Neuron, LLC. All rights reserved.</p>
<p class="footer-tagline-bottom">Your memory. Your AI.</p>
<div class=\"footer-bottom\">
<p class=\"footer-copy\">&copy; 2026 Neuron, LLC. All rights reserved.</p>
<p class=\"footer-tagline-bottom\">Your memory. Your AI.</p>
</div>
</div>
</footer>
"
}
+6 -6
View File
@@ -5,7 +5,7 @@ from founding_badge import { founding_badge, founding_badge_css }
extern fn el_html_doc(lang: String, head: String, body: String) -> String
extern fn el_meta_charset(charset: String) -> String
extern fn el_meta(attrs: String) -> String
extern fn el_meta(name: String, content: String) -> String
extern fn el_title(text: String) -> String
extern fn el_link_stylesheet(href: String) -> String
extern fn el_script_src(src: String, defer_load: Bool) -> String
@@ -13,7 +13,7 @@ extern fn el_script_inline(code: String) -> String
extern fn el_nav(attrs: String, children: String) -> String
extern fn el_div(attrs: String, children: String) -> String
extern fn el_a(href: String, attrs: String, children: String) -> String
extern fn el_img(attrs: String) -> String
extern fn el_img(src: String, alt: String, attrs: String) -> String
extern fn el_p(attrs: String, children: String) -> String
extern fn el_h1(attrs: String, text: String) -> String
extern fn el_button(attrs: String, label: String) -> String
@@ -520,7 +520,7 @@ fn account_css() -> String {
}
fn account_nav() -> String {
let logo_img: String = el_img("src=\"/assets/brand/neuron-wordmark-on-light.png\" srcset=\"/assets/brand/neuron-wordmark-on-light@2x.png 2x\" alt=\"Neuron\" height=\"28\"")
let logo_img: String = el_img("/assets/brand/neuron-wordmark-on-light.png", "Neuron", "srcset=\"/assets/brand/neuron-wordmark-on-light@2x.png 2x\" height=\"28\"")
el_nav(
"id=\"nav\"",
el_div(
@@ -818,7 +818,7 @@ fn account_devices_card() -> String {
el_div("class=\"device-icon\"", account_signin_svg_device()) +
el_div(
"",
el_p("class=\"devices-count\"", "2 devices included with your plan") +
el_p("class=\"devices-count\" id=\"devices-count-el\"", "") +
el_p("class=\"devices-sub\"", "Currently: Setup at launch")
)
) +
@@ -965,9 +965,9 @@ fn account_dashboard_section() -> String {
fn account_page(supabase_url: String, supabase_anon_key: String) -> String {
let head: String =
el_meta_charset("UTF-8") +
el_meta("name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"") +
el_meta("viewport", "width=device-width, initial-scale=1.0") +
el_title("My Account - Neuron") +
el_meta("name=\"description\" content=\"Manage your Neuron account, view your plan, and access your founding member details.\"") +
el_meta("description", "Manage your Neuron account, view your plan, and access your founding member details.") +
"<link rel=\"icon\" type=\"image/png\" sizes=\"16x16\" href=\"/assets/favicon-16.png\">" +
"<link rel=\"icon\" type=\"image/png\" sizes=\"32x32\" href=\"/assets/favicon-32.png\">" +
"<link rel=\"preconnect\" href=\"https://fonts.googleapis.com\">" +
+5 -2
View File
@@ -11,7 +11,7 @@ fn main() -> Void {
'use strict';
var cfg = window.NEURON_CFG || {};
var sb = supabase.createClient(cfg.supabase_url, cfg.supabase_anon_key, {
auth: { flowType: 'pkce' }
auth: { flowType: 'implicit' }
});
window.sendMagicLink = async function() {
@@ -25,7 +25,10 @@ fn main() -> Void {
return;
}
if (btn) { btn.disabled = true; btn.textContent = 'Sending...'; }
var result = await sb.auth.signInWithOtp({ email: email });
var result = await sb.auth.signInWithOtp({
email: email,
options: { emailRedirectTo: window.location.origin + '/account' }
});
if (btn) { btn.disabled = false; btn.textContent = 'Continue with email'; }
msgEl.style.display = 'block';
if (result.error) {
+7
View File
@@ -103,6 +103,13 @@ fn main() -> Void {
}
setHtml('plan-billing-note-el', billingNote);
var devicesEl = document.getElementById('devices-count-el');
if (devicesEl) {
var deviceText = '2 devices included with your plan';
if (plan === 'free') { deviceText = '1 device included with your plan'; }
devicesEl.textContent = deviceText;
}
var meta = '';
if (createdAt) {
var d = new Date(createdAt);
+23 -17
View File
@@ -686,7 +686,7 @@ fn handle_request_inner(method: String, path: String, headers: Map, body: String
if !str_eq(pi_email, "") {
let pi_email_enc: String = str_replace(str_replace(pi_email, "@", "%40"), "+", "%2B")
let pi_search_url: String = "https://api.stripe.com/v1/customers/search?query=email%3A%22" + pi_email_enc + "%22&limit=1"
let pi_search: String = http_get_auth(pi_search_url, auth_header)
let pi_search: String = http_get_auth(pi_search_url, stripe_key)
let pi_cus_id = json_get_string(pi_search, "id")
if str_eq(pi_cus_id, "") {
let pi_name_enc: String = str_replace(pi_name, " ", "%20")
@@ -699,21 +699,24 @@ fn handle_request_inner(method: String, path: String, headers: Map, body: String
}
}
// Free tier: $0 PaymentIntent for age verification (18+ requirement).
// Verifies card is valid. No charge, no capture.
// Note: setup_future_usage cannot be used with amount=0.
// Free tier: SetupIntent for age verification (18+ requirement).
// Verifies card is valid and saves it. No charge, no capture.
// $0 PaymentIntents are rejected by Stripe; SetupIntent is the correct tool.
if str_eq(plan, "free") {
let free_pi_body: String = "amount=0"
+ "&currency=usd"
+ "&payment_method_types[]=card"
let si_body: String = "automatic_payment_methods[enabled]=true"
+ "&usage=off_session"
+ "&metadata[plan]=free"
+ "&metadata[purpose]=age_verification"
let free_pi_body = if !str_eq(pi_cus_id, "") { free_pi_body + "&customer=" + pi_cus_id } else { free_pi_body }
let free_pi_resp: String = http_post_form_auth(
"https://api.stripe.com/v1/payment_intents",
free_pi_body,
let si_body = if !str_eq(pi_cus_id, "") { si_body + "&customer=" + pi_cus_id } else { si_body }
let si_resp: String = http_post_form_auth(
"https://api.stripe.com/v1/setup_intents",
si_body,
auth_header)
return free_pi_resp
if str_starts_with(si_resp, "{") {
let inner: String = str_slice(si_resp, 1, str_len(si_resp))
return "{\"setup_mode\":true,\"plan\":\"free\"," + inner
}
return si_resp
}
// Setup-mode path: save payment method, do not charge. Only valid
@@ -784,7 +787,7 @@ fn handle_request_inner(method: String, path: String, headers: Map, body: String
// 1. Search existing customers by email
let lc_search_url: String = "https://api.stripe.com/v1/customers/search?query=email%3A%22" + lc_email_enc + "%22&limit=1"
let lc_search: String = http_get_auth(lc_search_url, lc_auth)
let lc_search: String = http_get_auth(lc_search_url, stripe_key)
let lc_cus_id: String = json_get_string(lc_search, "id")
// 2. If none, create one. We always include supabase_user_id so the
@@ -1116,13 +1119,16 @@ fn handle_request_inner(method: String, path: String, headers: Map, body: String
}
let attest_name: String = json_get(body, "name")
let attest_email: String = json_get(body, "email")
let attest_plan: String = json_get(body, "plan")
let attest_ts: String = json_get(body, "timestamp")
let attest_text: String = json_get(body, "attestation")
let attest_ua: String = json_get(body, "user_agent")
if str_eq(attest_email, "") {
return "{\"error\":\"email required\"}"
}
// Founding membership now requires $199 Stripe payment the attestation
// form is a waitlist-only path. Server enforces this regardless of what
// the client submits as plan to prevent bypassing payment.
let attest_plan: String = "waitlist"
let n_safe: String = str_replace(str_replace(attest_name, "\\", "\\\\"), "\"", "\\\"")
let e_safe: String = str_replace(str_replace(attest_email, "\\", "\\\\"), "\"", "\\\"")
let t_safe: String = str_replace(str_replace(attest_text, "\\", "\\\\"), "\"", "\\\"")
@@ -2311,7 +2317,7 @@ fn sec_headers_json() -> String {
+ "\"X-Frame-Options\":\"SAMEORIGIN\","
+ "\"Referrer-Policy\":\"strict-origin-when-cross-origin\","
+ "\"Permissions-Policy\":\"geolocation=(), microphone=(), camera=()\","
+ "\"Content-Security-Policy\":\"default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://challenges.cloudflare.com https://cdn.jsdelivr.net https://www.googletagmanager.com https://www.google-analytics.com; style-src 'self' 'unsafe-inline'; frame-src https://challenges.cloudflare.com; connect-src 'self' https://api.stripe.com https://*.supabase.co; img-src 'self' data: https:; font-src 'self' data:\"}"
+ "\"Content-Security-Policy\":\"default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://challenges.cloudflare.com https://cdn.jsdelivr.net https://googleads.g.doubleclick.net https://js.stripe.com https://static.cloudflareinsights.com https://www.googletagmanager.com https://www.google-analytics.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; frame-src https://challenges.cloudflare.com https://js.stripe.com; connect-src 'self' https://analytics.google.com https://api.stripe.com https://*.supabase.co https://www.google.com https://www.googletagmanager.com; img-src 'self' data: https:; font-src 'self' data: https://fonts.gstatic.com\"}"
}
// Headers for compiled JS assets. Explicitly sets Content-Type so the browser
@@ -2327,7 +2333,7 @@ fn js_headers_json() -> String {
+ "\"X-Frame-Options\":\"SAMEORIGIN\","
+ "\"Referrer-Policy\":\"strict-origin-when-cross-origin\","
+ "\"Permissions-Policy\":\"geolocation=(), microphone=(), camera=()\","
+ "\"Content-Security-Policy\":\"default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://challenges.cloudflare.com https://cdn.jsdelivr.net https://www.googletagmanager.com https://www.google-analytics.com; style-src 'self' 'unsafe-inline'; frame-src https://challenges.cloudflare.com; connect-src 'self' https://api.stripe.com https://*.supabase.co; img-src 'self' data: https:; font-src 'self' data:\"}"
+ "\"Content-Security-Policy\":\"default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://challenges.cloudflare.com https://cdn.jsdelivr.net https://googleads.g.doubleclick.net https://js.stripe.com https://static.cloudflareinsights.com https://www.googletagmanager.com https://www.google-analytics.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; frame-src https://challenges.cloudflare.com https://js.stripe.com; connect-src 'self' https://analytics.google.com https://api.stripe.com https://*.supabase.co https://www.google.com https://www.googletagmanager.com; img-src 'self' data: https:; font-src 'self' data: https://fonts.gstatic.com\"}"
}
// Headers for static assets under /assets/ and /brand/.
@@ -2343,7 +2349,7 @@ fn static_asset_headers_json() -> String {
+ "\"X-Frame-Options\":\"SAMEORIGIN\","
+ "\"Referrer-Policy\":\"strict-origin-when-cross-origin\","
+ "\"Permissions-Policy\":\"geolocation=(), microphone=(), camera=()\","
+ "\"Content-Security-Policy\":\"default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://challenges.cloudflare.com https://cdn.jsdelivr.net https://www.googletagmanager.com https://www.google-analytics.com; style-src 'self' 'unsafe-inline'; frame-src https://challenges.cloudflare.com; connect-src 'self' https://api.stripe.com https://*.supabase.co; img-src 'self' data: https:; font-src 'self' data:\"}"
+ "\"Content-Security-Policy\":\"default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://challenges.cloudflare.com https://cdn.jsdelivr.net https://googleads.g.doubleclick.net https://js.stripe.com https://static.cloudflareinsights.com https://www.googletagmanager.com https://www.google-analytics.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; frame-src https://challenges.cloudflare.com https://js.stripe.com; connect-src 'self' https://analytics.google.com https://api.stripe.com https://*.supabase.co https://www.google.com https://www.googletagmanager.com; img-src 'self' data: https:; font-src 'self' data: https://fonts.gstatic.com\"}"
}
fn handle_request(method: String, path: String, headers: Map, body: String) -> String {