Repository navigation
Free no-login debate tools: timer, motion generator, flow sheet, speech calculator - #181
Conversation
…lculator Four public tools under /tools, prerendered with their own heads and JSON-LD, plus an embeddable timer at /embed/debate-timer. vercel.json allows framing on /embed/* only; every other path still refuses it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… line Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Warning Review limit reachedEnable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. Next included review available in 4 minutes. View limit details
ℹ️ Recent review info
📝 Walkthrough
Note 🎁 Summarized by CodeRabbit Free
Comment |
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ard focus - The custom format editor's minute fields no longer fail the browser's step check, so Use this format applies; an e2e test covers it. Formats too long to share are refused with a message instead of vanishing on reload. - Only /embed/debate-timer may be framed, not every /embed/ path. - On the tool page the timer's keys act only once it has focus, so Space scrolls the article. - The calculator's fit pace follows the custom rate; quieter live regions; narrower privacy copy; a behavioural service worker test replaces a source-text check. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Deployment failed for project debatelab-staging with the following error: Learn More: https://vercel.com/adam-gaborjanis-projects?upgradeToPro=build-rate-limit |
|
Deployment failed for project debate-navigator with the following error: Learn More: https://vercel.com/adam-gaborjanis-projects?upgradeToPro=build-rate-limit |
|
Interim review of
Dismissed:
Checks for |
|
Codex review (gpt-6.1-sol, xhigh) of the merged head
|
Four free debate tools that need no login and cost nothing in AI calls. Each has its own prerendered, indexable URL, and a hub page links them. The aim is pages people link to and embed, because Google crawls the guides but does not index them. Each page ends with one soft call to action for the paid app.
URLs
/tools: hub with a card per tool (CollectionPage and ItemList JSON-LD)/tools/debate-timer: round timer with format presets/tools/debate-motion-generator: 300 original practice motions/tools/debate-flow-sheet: printable flow sheet/tools/speech-time-calculator: how long is my speech/embed/debate-timer?format=bp: chrome-less timer for iframes (noindex, canonical is/tools/debate-timer)Each page is prerendered to real HTML through the existing pipeline (
PAGE_HEADS,scripts/prerender.mjs,src/prerender/entry.tsx,routeLoaders). Each has its own title, description, self-referencing canonical, andWebApplication+BreadcrumbList+FAQPageJSON-LD. The JSON-LD has price 0,isAccessibleForFree,dateModified, and no ratings or reviews.dist/sitemap.xmllists/toolsand the four tools (44 URLs in total) and skips/embed/debate-timer. Built pages run from about 1,150 visible words on the flow sheet and calculator pages to 2,300 on the timer page, which carries every format table. The motion page also lists all 300 motions by topic.Security review:
vercel.jsonframing changeAdam approved this in the thread before work started. Framing is allowed on the embed only, and after review that means exactly
/embed/debate-timer.X-Frame-Options: DENYand the main CSP (withframe-ancestors 'none') move from/(.*)to/((?!embed/debate-timer$).*). The other headers (nosniff, Referrer-Policy, Permissions-Policy, HSTS) stay on/(.*)for every path./embed/debate-timerrule sendsframe-ancestors *with no X-Frame-Options,X-Robots-Tag: noindex, nofollow, and a tighter CSP than the main site:default-src,script-srcandconnect-srcare'self'only,frame-srcandworker-srcare'none', andform-actionis'none'. There is no Jam, Supabase, Sentry or PostHog.sourceToRegexin@vercel/routing-utils6.6.0). I compiled both sources with that function. Only/embed/debate-timermatches the embed rule. Everything else matches only the deny rule, including/embed,/embed/nope,/embed/debate-timer/,/embed/debate-timer/index.htmland/EMBED/debate-timer. A future embed needs its path added to both rules.src/test/vercel-deployment-config.test.tsnow resolves headers per path. It fails if two matching rules set the same header. It asserts that/,/index.html,/app,/app/settings,/login,/share/abc,/tools,/tools/debate-timer, a guide,/404.html,/app-shell.html,/embed,/embedded/x,/EMBED/debate-timerand/assets/*all send DENY andframe-ancestors 'none'. Only/embed/debate-timeris frameable, and it keeps the shared headers. I checked that the test fails against the oldvercel.jsonand when the embed rule also sets X-Frame-Options. The existing security-header assertions all still run, now against the resolved headers for/.public/sw.jsnow ignores/embed/requests. Without that, the service worker's navigation handler would store an iframe's embed page as the offline/index.html, and could answer an embed with the cached app shell, which refuses to be framed.The embed is a second Vite HTML entry (
embed/debate-timer/index.html,src/embed/debate-timer.tsx), not a route in the app. It never loads the app bundle, the auth provider, Jam, Vercel Analytics or the service worker. In the browser it made no request to another origin. Its JavaScript is 87.6 kB gzipped (React, i18next, icons and the timer). A small dev and preview middleware invite.config.tsmaps/embed/debate-timerto itsindex.html, which Vercel does on its own.Timer
?format=custom&custom=…, a validated base64url payload with limits on length and values.+0:12), with BP's and AP's 15-second grace period labelled.src/lib/timer-clock.tsunchanged; nothing is forked. The speech clock is a small count-up stopwatch, becausetimer-clockstops at zero by design and overtime needs negative time.ClockAnnouncer,clockToneand the clock colours come fromRoundClock. I movedplaySignal(adding a knock) and the wake-lock effect out ofTimerPage.tsxintosrc/lib/timer-signals.tsandsrc/hooks/use-screen-wake-lock.ts./app/timerbehaves as before, and its diff is the import swap only.Sources for the format times
Every number comes from a document I or a research pass fetched. I checked the starred numbers myself against the PDF text.
Each format's table on the page cites its source and says what varies, such as PF's old 2/2 summary and prep, LD's 4-or-5 minutes of prep, and the 4-minute rebuttals some Policy circuits still use.
Motion generator
src/lib/free-tools/practice-motions.tshas 300 motions written for this PR, 25 per topic across 12 topics. They split 96 novice, 108 intermediate and 96 advanced. By type there are 120 policy, 72 value, 48 actor, 30 regret and 30 prefer. 31 have an info slide. None is copied from a tournament archive. Each motion has hand-written "This House" and "Resolved:" wording. I read all 300 for balance and school suitability.?m=edu-004, filters kept in the URL); and the full bank as a list grouped by topic in the prerendered HTML./login. The app has no parameter that carries a motion through sign-in (LoginPagereads none, andRequireAuthonly keeps the path), so I didn't invent one. Follow-up below.Flow sheet and calculator
window.print()with an@pagesize set from the picker. Checked with Chromium's print-to-PDF: one page each for Letter landscape and A4 portrait.Site integration
/tools./how-to-flow-in-debate,/debate-prep-time-management,/debate-motion-analysis,/debate-topics-for-studentsand others), and to the other tools.toolsnamespace (src/locales/tools/{en,es,hu}.json), andsrc/locales/*.jsonis untouched.i18n-keys.test.tsnow resolves namespaces, andi18n.test.tschecks parity for the new catalogs. The article copy, URLs and prerendered HTML stay English, with no hreflang.free_tool_used, through the existing allowlistedcaptureProductEvent, with closed sets forfreeTool,action(start,draw,print,calculate) andformat. No new vendor. The embed sends nothing.index.htmlorLandingPage. Two bundle changes are worth knowing. Thetoolscatalogs load with the rest of i18n, about 5.5 kB gzipped across three languages, so the footer label works on the landing page. And with a second HTML entry, Rollup moves React, i18next and the other shared libraries out of the main entry into a shared chunk (button-*.js). The main entry is now two files, about 371 kB gzipped together.docs/operations.mddescribes the framing rules, the tool prerender and the embed entry.docs/testing.mddescribes the new spec, and the verify-debatelab feature index notes that the tools are covered bye2e/free-tools.spec.ts. The skill's recipes need the backend stack, and these pages have no backend.src/.Verification
Local runs on this VPS at
632aee06. Two later commits followed:795579e0sizes the embed snippet, andf682816afixes the interim review findings listed in the PR comments.f682816awas checked with ESLint and a scopedtscon the touched files, the affected unit tests, ande2e/free-tools.spec.tson both projects. The full ladder was not rerun because the VPS is out of memory. These are local passes, not CI. Every GitHub Actions job on this PR failed within 2 seconds with no logs, which is the Actions-minutes problemAGENTS.mddescribes, so there is no CI result yet.npm run verifypassed end to end: docs check, lint (ESLint, knip, repo rules), typecheck on all three tsconfigs, 343 unit tests in 40 files, 30 collaboration tests, 356 edge tests, build with prerender, sitemap and the bundle-boundary check,test:sw, and the desktop@smokebrowser set (42 passed, including the new tool specs).e2e/free-tools.spec.tsande2e/public-pages.spec.tson desktop and mobile Chromium: 54 passed.npm run test:e2e, the extra part ofverify:full): 260 passed, 5 flaky, 5 skipped, 2 failed. The first attempt was invalid because the box's memory guard killed the dev server and Chromium mid-run. The failures are not from this PR:e2e/search-quality.spec.ts(authenticated Factoids source panel) fails on desktop and mobile, and fails the same way at this branch's base4f9e0a0eand on currentmain(34fb5856).e2e/home-navigation.spec.ts:90(onboarding tooltip within 100 ms) failed once on mobile under load and passed when rerun alone.navigation-performancetiming tests.dist/tools/*/index.htmlhas its h1, intro, how-to, tables and FAQ as HTML before any JavaScript runs, plus its JSON-LD.dist/sitemap.xmllists the five URLs withlastmod, and the sitemap script logs that it skipped/embed/debate-timer.@vercel/routing-utilssourceToRegex, as described above.632aee06(staging project, read-only HEAD requests, before the rule narrowed to the exact embed path; Vercel rate-limited the preview build forf682816a),/,/app/settings,/login,/tools,/tools/debate-timer,/online-debate-timer,/embedand/EMBED/debate-timersendX-Frame-Options: DENYandframe-ancestors 'none'./embed/debate-timer(200) and/embed/nope(404) sendframe-ancestors *with no X-Frame-Options andX-Robots-Tag: noindex, nofollow. HSTS, nosniff and Permissions-Policy are on every path./tools/debate-timerwas refused with aframe-ancestorsCSP violation.+0:12, grace), the BP POI-open state on a phone, the embed in dark mode, the motion generator with a three-motion permalink, and the flow sheet and calculator. Print preview through Chromium's PDF output: PF on Letter landscape and LD on A4 portrait, each one page. GitHub's CLI can't attach images to a PR, so the screenshots are in the T3 thread that opened this PR.verify-debatelabrecipes (they need the backend stack, and these pages have none), and the agentic suite (see follow-ups). The box's memory guard killedtsctwice while other worktrees were busy. Each step above passed on a rerun when memory was free.Follow-ups after #156 merges
public/llms.txt./online-debate-timerto/tools/debate-timer.Other follow-ups:
?motion=handoff through sign-in, so the motion CTA can open DebateLab with the motion set.agentic/pages.e2e.tsdoes not cover/tools. In Spanish and Hungarian the judge would flag the English article copy, which is English by design. An English-only pass could be added.?lang=es|huthe embed translates its controls, but speech names stay English (format data).Cut
Nothing. All four tools shipped.
🤖 Generated with Claude Code