Test licence code, a working local dev loop, and an RSVP funnel that works end to end - #2
Merged
Merged
Conversation
Every paid feature sits behind a redeemed licence, and there was no way to test any of them: - `astro dev` ran no Pages Functions, so every relative /api/* call 404ed, the session resolved to anonymous and the app stayed on the free tier with the Worker running beside it. /i/<slug> and /join/<token> 404ed too. - The local Worker had SELF_HOSTED=true, so a signed-in local user was already pro and the upgrade path could not be exercised. - Sign in linked to /auth/google, which 500s without a Google client, so a browser could not sign in locally at all (curl's cookie is not the browser's). - Minted codes are single-use, so each test needed a fresh one. Now: - astro.config.mjs proxies /api, /invite and the three /auth routes to the Worker (BACKEND_DEV_URL, default :8787) and rewrites /i/* and /join/* onto their pages, mirroring functions/. /auth/callback is left alone for the same reason functions/auth/ has no catchall. - BC-TEST-TEST-TEST, from TEST_LICENCE_CODE on the local and preview environments, grants pro without a licence_keys row and can be redeemed by any number of accounts. It is refused whenever ENVIRONMENT is production, since the code is in a public repository. - local now runs SELF_HOSTED=false so the paywall is real; .dev.vars can set it back to "true" to see the self-hosted behaviour. - The session reports devSignIn wherever POST /auth/dev is open (local or self-hosted; one shared predicate), and the header then offers an email sign-in instead of the Google link. Self-hosters without a Google client can now sign in from the browser too. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TivKBtoj8GvfK4yeS44Vnr
Driving the funnel in a browser — host publishes, guests open, answer and forward, host watches — found it broken at the first step and in several places after. - Publishing always threw. documentOf() structuredClone'd each field of the store's reactive party, and every object reached through a Vue proxy is a proxy, which structuredClone refuses. No invite link was ever created; co-organiser sync went through the same function. It now reads through toRaw. - Hand-typed guests on a published party appeared twice, permanently. addInvite posted the guest but never gave the local row the server's id, so mergeFunnel kept it as hand-typed and added the server's copy beside it. adoptRemoteGuest stamps the id (or drops the local row if a poll already brought the copy in). - The party then stopped saving: mergeFunnel carried reactive rows into the new list, putting proxies inside the raw party, and persist() failed with DataCloneError. It copies them now. - Share sheet: WhatsApp, Email, Messages, Instagram and QR code only set a "sent" flag. They now open wa.me, mailto: and sms: with the invite text, use the system share sheet (or copy) for Instagram, and render a downloadable QR. Copied links used a hard-coded https://, which broke on http://localhost; both the host's and a guest's forward link now use the page's own origin. - Guests tab: someone who opened the link without answering was a blank row with an empty avatar and an Accept button that would issue a ticket to nobody. They read "Opened the link", show "?", and offer Decline only. The Live badge sits beside Send invite rather than floating mid-header. Verified in Chromium against wrangler dev + local D1: 5 reached, 3 confirmed, 1 maybe, 1 declined; a friend-of-friend at depth 1; disabling forwarding reaches the server; host Reset/Accept survive the 20s poll; a hand-typed guest appears once across polls and reloads. Unit tests cover the three data bugs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TivKBtoj8GvfK4yeS44Vnr
The Pages project could not get bottlecount.pages.dev — the name was taken — and Cloudflare assigned bottlecount-epj.pages.dev. Every place that names the app's origin is updated: the Worker's FRONTEND_URL (production and preview; Google's redirect URI is built from it), the app build's SITE, the docs build's PUBLIC_APP_ORIGIN, the canonical-link check in CI, and the README. Also fills in the production D1 database_id. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TivKBtoj8GvfK4yeS44Vnr
Not a secret — it is sent to the browser in the OAuth redirect. The client secret and JWT secret are Worker secrets set with `wrangler secret put`. The client's authorised redirect URI is https://bottlecount-epj.pages.dev/auth/google. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TivKBtoj8GvfK4yeS44Vnr
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
There was no way to test the paid features, and when I tested them anyway the RSVP funnel was broken at its first step. This PR fixes both.
1. Testing the paid features (
fc38763)Test licence
BC-TEST-TEST-TESTproon thelocalandpreviewWorkers. The code comes from aTEST_LICENCE_CODEvar.licence_keysrow.ENVIRONMENTisproduction, even if the var is set there, because the code is in a public repo.Local dev now reaches the Worker
astro devruns no Pages Functions, so every relative/api/*call returned 404. The app silently stayed on the anonymous free tier.astro.config.mjsnow proxies/api,/inviteand/auth/{dev,google,logout}to:8787(override withBACKEND_DEV_URL)./i/*and/join/*onto their pages, mirroringfunctions/./auth/callbackis left alone, since it is a page.Sign in from the browser without Google
/api/sessionnow reportsdevSignInwhereverPOST /auth/devis open. Both sides use one shared predicate.localnow runsSELF_HOSTED=falseSELF_HOSTED="true"in.dev.varsbrings back the self-hosted behaviour.2. RSVP funnel fixes (
ec32e34)I found these by driving the whole flow in Chromium: host publishes, guests open, answer and forward, host watches.
documentOf()calledstructuredCloneon Vue reactive proxiestoRawaddInvitenever gave the local row the server's idadoptRemoteGueststamps the id, or drops the local row if a poll already brought the server's copy inmergeFunnelcarried reactive rows into the new listpersist()failed withDataCloneError, so the party stopped savingwa.me/mailto:/sms:links with the invite text; system share sheet (or copy) for Instagram; downloadable QRhttps://hard-coded when copying linkshttp://localhost?, only Decline is offeredmargin-left: autos in the funnel headerThe READMEs (root and
backend/) describe the new local flow and the test licence.Testing
npm run lint,npm run typecheck,npm run buildandnpm run build:docsall pass. The docs build contains noapp/auth/i/joinroutes.npm test: 49 frontend tests and 113 backend tests pass. New tests cover:devSignInflagdocumentOfgiven a reactive partymergeFunneloutput survivingstructuredCloneadoptRemoteGuest, including the race where a poll lands firstwrangler devwith a local D1:Notes
--no-verify. The husky hook'snpx lint-stagedfails in the cloud sandbox (undefined@lint-staged), so I rannpx lint-stagedandnpm run lintby hand first.wrangler.jsonc.🤖 Generated with Claude Code
https://claude.ai/code/session_01TivKBtoj8GvfK4yeS44Vnr
Generated by Claude Code