Render the waitlist form natively so it themes in light and dark - #14
Merged
Merged
Conversation
The waitlist form was a cross-origin Tally iframe, which meant it could not follow the site theme: in dark mode its labels rendered near-black on the near-black page and the fields read as grey on grey. Every route to theming the embed was tested against the live form, and all of them are closed: - CSS into the frame is blocked by same-origin policy. - Tally's postMessage protocol is outbound-only (FormLoaded, FormPageView, FormSubmitted, FormRedirect, viewport height). No inbound command exists, so the frame cannot be driven from the parent either. - There is no theme or color URL parameter; the palette lives server-side in the form's settings.styles. - Tally's custom CSS is a paid feature and this workspace is on FREE. - color-scheme does not propagate into the frame: with the OS in light mode and color-scheme:dark on the iframe, prefers-color-scheme inside still reports light. Tally's own embed.js additionally force-sets iframe.style.colorScheme = 'light' whenever transparentBackground=1. So the form is now our own markup, POSTed straight to api.tally.so/forms/<id>/respond, which takes no API key and reflects our origin in its CORS headers. Same Tally form, same dashboard, same downstream pipeline, but the markup is ours and themes with the rest of the site. Verified end to end: Tally returns 200 with a submissionId, and success routes to /thank-you client-side rather than following Tally's absolute production redirect, so the visitor stays in the SPA. Field UUIDs are Tally's internal block identifiers and are the contract between our fields and Tally's columns; config.ts documents how to re-read them if the form is edited in the dashboard. A dropdown answer is sent as an array containing the option UUID, not its label. Accessibility: labels bound to inputs, aria-invalid and aria-describedby per field, focus moved to the first invalid field on a failed submit, the submit error announced via role="alert", and the required marker reads as "Required" rather than a bare asterisk. An invalid field's focus ring flips to the danger colour, outranking the global :focus-visible accent ring. :root now sets color-scheme per theme so native controls follow the site theme. A failed submission keeps everything the visitor typed, shows the error, and offers a mailto fallback. Spam is handled by an off-screen honeypot, since the embed's own heuristics are gone. CSP: connect-src allows https://api.tally.so; tally.so is dropped from script-src and frame-src as nothing is embedded any more. Privacy policy updated: Tally is no longer an embed, so nothing loads from Tally while browsing and no Tally script runs on the site. Tally remains the processor for submissions. Also adds an optional Docker dev setup, which is how this was validated across mobile, tablet, and desktop. Docs synced: dev-guide (why an embed cannot be themed, and the rules for posting to Tally directly), design-system.html, site-copy (with the iframe kept as a [cut] note), build-plan, README, and the frontend-review checklist. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Tally rejects a repeat submission from the same respondent with FORM_UNIQUE_SUBMISSION_CONFLICT, which the form was reporting as the generic "something went wrong on our side" error. Detect it and show the actual reason instead, without the mailto fallback since there's nothing to report.
In case the visitor skipped booking the first time. Reuses CAL_BOOKING_URL and the same outbound-link pattern as the /thank-you CTA.
Accent color, bold, underline, so it reads as clickable against the red error text instead of blending in.
whiteSpace: nowrap on the anchor stops the arrow orphaning onto its own line at narrow widths.
One canonical file for Claude and Codex project instructions instead of a manually-copied duplicate that can drift.
AGENTS.md is the tool-neutral convention multiple agents look for; CLAUDE.md becomes the pointer instead. Reworded the intro line to not name Claude Code specifically.
Matches the repo's own @-import convention (see @docs/dev-guide.md below) and avoids symlink quirks in tools that don't resolve them (GitHub raw/zip, Windows checkouts).
This branch was successfully deployed
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.
Supersedes #13 (same branch/commit history plus fixes from review). Original PR by @qxZap: renders the waitlist form as our own markup instead of a cross-origin Tally iframe, so it themes in light and dark. Every route to CSS-theme or drive the iframe was tested and closed; details in #13 and
docs/dev-guide.md.On top of the original commit
fetch(not a rejected one, a stalled one) left the button on "Joining…" forever with no error ever shown, which the PR's own rule says can't happen.AbortSignal.timeout(15_000)on the request fixes it..sr-onlyrule. Tailwind v4 already emits it (@import "tailwindcss"plus the class being used is enough), so the hand-written copy inindex.csswas dead weight..dockerignore.compose.yamlbind-mounts the repo with nobuild:step, so the file was never read.FORM_UNIQUE_SUBMISSION_CONFLICT; the form now shows that reason instead of the generic "went wrong on our side" error, and drops themailto:fallback since there's nothing to report.docs/site-copy.mdanddocs/build-plan.mdstill described the old six-role Tally-hosted field set; updated to matchwaitlist.form.*/TALLY_ROLE_OPTIONS.Verified
bun run typecheck,lint,build: clean.TALLY_FIELDS/TALLY_ROLE_OPTIONSUUIDs checked against the live form definition attally.so/embed/xXvOJk: exact match on all 4 field UUIDs and all 3 role option UUIDs/labels.api.tally.so/forms/xXvOJk/respondfor both prod and localhost origins: 204, origin reflected./thank-you; a forced network failure shows the error with themailto:fallback, keeps typed values, and re-enables the button.mailto:link, form data preserved.To watch
TALLY_FIELDS/TALLY_ROLE_OPTIONSare a manual contract with the Tally dashboard: editing the form there requires re-reading the UUIDs (documented insrc/config.ts).