Free-tier Gemini unblocked in dev; four Playwright specs back online

Two stuck things fixed together: the free-tier Gemini path failed before it could check the allowlist, and all four product-flow e2e specs had been sitting behind `test.describe.skip()` since the UI drifted away from their selectors.

infrastructure

The Gemini fix is a logic reorder. In dev mode, a guard was rejecting requests before the allowlist check ran, so the opt-in path was never reachable. The PR moves the allowlist evaluation earlier and adds clearer error messages on the refusal path.

The e2e revival required individual adjustments for each flow. Project rename moved into a row-level kebab menu because clicking the row now navigates. Document upload runs through a modal whose hidden file input only mounts when the modal is open. Chat creation goes through the project's assistant tab and an empty-state spawn button. The tabular add-column path lives on the toolbar once documents are present. Citation chip assertions also changed: chips now render as numeric buttons rather than literal [1] text, so the regex assertion was replaced with a dedicated data-testid marker.

All load-bearing controls picked up stable data-testid hooks rather than relying on text or structural selectors. The specs were rewritten against the current component architecture.

Setup gaps documented along the way: the test Supabase project needs the schema applied, .env.test needs real Cloudflare R2 credentials (previously unmentioned in the README), and the Gemini key in that file rotates and needs replacing when chat and tabular flows start failing with auth errors. The full suite came in at 13/13 in roughly 1.4 minutes locally before merge.

So what Worth a look if you've had e2e specs drift out of sync with a moving UI and want to see a disciplined testid-only revival approach. The documented setup gaps (R2 credentials, Supabase schema, rotating Gemini key) are also useful context if you run the same test infrastructure. The Gemini guard fix is specific to this fork's allowlist pattern.

View this fork on GitHub →

Spotted something wrong? Or know the PR text has fresher detail than the writeup above?