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>
|
@qxZap is attempting to deploy a commit to the Wandercode Team on Vercel. A member of the Team first needs to authorize it. |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
cmin764
self-requested a review
September 9, 2026 09:11
Member
|
Superseded by #14. I don't have push access to your fork ( Closing this one in favor of #14. |
cmin764
added a commit
that referenced
this pull request
Sep 9, 2026
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 - **Timeout on the submit request.** Without one, a hung `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. - **Dropped a duplicate `.sr-only` rule.** Tailwind v4 already emits it (`@import "tailwindcss"` plus the class being used is enough), so the hand-written copy in `index.css` was dead weight. - **Dropped `.dockerignore`.** `compose.yaml` bind-mounts the repo with no `build:` step, so the file was never read. - **Real message on a duplicate submission.** Tally rejects a repeat submission with `FORM_UNIQUE_SUBMISSION_CONFLICT`; the form now shows that reason instead of the generic "went wrong on our side" error, and drops the `mailto:` fallback since there's nothing to report. - **Docs sync**: `docs/site-copy.md` and `docs/build-plan.md` still described the old six-role Tally-hosted field set; updated to match `waitlist.form.*` / `TALLY_ROLE_OPTIONS`. ## Verified - `bun run typecheck`, `lint`, `build`: clean. - `TALLY_FIELDS` / `TALLY_ROLE_OPTIONS` UUIDs checked against the live form definition at `tally.so/embed/xXvOJk`: exact match on all 4 field UUIDs and all 3 role option UUIDs/labels. - CORS preflight against `api.tally.so/forms/xXvOJk/respond` for both prod and localhost origins: 204, origin reflected. - Browser (light + dark): empty submit shows all 4 errors, focuses the first invalid field, fires zero network requests; a valid submission returns 200 and routes client-side to `/thank-you`; a forced network failure shows the error with the `mailto:` fallback, keeps typed values, and re-enables the button. - Duplicate submission reproduced directly against the live endpoint and through the form: shows the correct message, no `mailto:` link, form data preserved. ## To watch - The undocumented Tally endpoint could change shape without notice; the failure path is designed to degrade loudly if it does. - `TALLY_FIELDS` / `TALLY_ROLE_OPTIONS` are a manual contract with the Tally dashboard: editing the form there requires re-reading the UUIDs (documented in `src/config.ts`). --------- Co-authored-by: qxZap <MileaMihai@outlook.it> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.
The problem
The waitlist form was a cross-origin Tally iframe, so 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.
Why the iframe could not be fixed
Every route was tested against the live embed. All are closed:
postMessageFormLoaded,FormPageView,FormSubmitted,FormRedirect, viewport height). No inbound command existssettings.stylesFREEcolor-schemeinto the framecolor-scheme: darkon the iframe →prefers-color-schemeinside still reports light. Tally'sembed.jsalso force-setsiframe.style.colorScheme = 'light'whentransparentBackground=1An interim fix (hosting the iframe on a light card) made it readable but never themed: a light form on a black page. It is preserved as a
[cut]note indocs/site-copy.md.What this does
Renders the form as our own markup and POSTs it 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; the markup is ours, so it themes with the rest of the site.Verified end to end
Checked at 390 / 820 / 1440px in both themes.
typecheck,lintandbuildall clean.Notes for review
TALLY_FIELDS/TALLY_ROLE_OPTIONSare Tally's internal block UUIDs and are the contract with Tally's columns. If the form is edited in the Tally dashboard, they must be re-read or answers land in the wrong column.src/config.tsdocuments how.mailto:fallback./thank-youclient-side, bypassing Tally's absolute production redirect (which previously sent people to the live site even from localhost).aria-invalid/aria-describedbyper field, focus to the first invalid field, submit error viarole="alert", required marker reads as "Required". An invalid field's focus ring flips to the danger colour, outranking the global:focus-visibleaccent ring.:rootnow setscolor-schemeper theme so native controls follow the site theme.compose.yaml, stockoven/bun:1, no Dockerfile), which is how this was validated.Follow-up worth considering
There is no test runner in this repo, so the verification above was run as an ad-hoc Playwright script rather than committed. If you want that permanently guarded, it needs a test setup decision first.
🤖 Generated with Claude Code