Skip to content

Generate writes cases the way school debaters prepare them - #244

Merged
Programcode merged 13 commits into
mainfrom
feat/case-structure-v2
Oct 28, 2026
Merged

Programcode merged 13 commits into
mainfrom
feat/case-structure-v2

Conversation

@Programcode

@Programcode Programcode commented Oct 27, 2026 •

Copy link
Copy Markdown
Owner

Restructures the cases Generate writes to match how Adam prepares his own school cases. It follows sections (d) and (e) of the 2026-10-24 case-structure research, Adam's 2026-10-27 decisions, and the 2026-10-28 R2 spec where they differ. No new features and no database migration: cases are JSON, and old saved cases still load and render.

What a case looks like now

  • Opening: a hook, then "Today we will show N arguments that prove…". The Introductor can add one line explaining a motion term, but only when the term is genuinely unclear.
  • Arguments: title, point, explanation, evidence (with source and year), and why it matters.
  • If they say…: their claim, our answer, and a source when there is one. This replaces the predicted opponent case and the prepared counters.
  • Closing line: one sentence.
  • Sources: organisation (year), the claim it supports, and the link.

The role shapes the speech:

  • Introductor (4 min): the opening plus short arguments.
  • R1 (6 min): full arguments, then "If they say…".
  • R2 (6 min): no new arguments. See the next section.
  • Conclusor (3 min): no new arguments and no answers list. Instead, 2-3 "Why we win" points and the closing line.

R2, matched to Adam's own R2 documents

I matched R2 against "Debate print 1" (Debate print 1_merged_removed.pdf). It holds the R2 speech Adam gave for the side arguing schools are not safe, which he emailed to his teacher on 2025-03-26 under the subject "Printing For Adam". The PDF also holds his refutation prep, his argument paragraph, the sources list and his flashcards. It is the only R2 material among his documents: the scratch folder holds the research notes, not other speeches.

What the document contains:

  • 15 prepared "they say → we answer" blocks in four groups: 5 in the speech itself, 3 on chemicals, 4 on bullying and 3 flashcards.
  • Short answers: 15-39 words each (25 on average). Five cite a number and four end on a question. They are notes he expands out loud.
  • His argument paragraph ("Nuestro segundo argumento es sobre el bullying…") runs 141 words.

How R2 now works:

  • No new arguments. contentions is always empty for R2, and R2 has no argument count anywhere.
  • Opening: a hook that turns the motion around, then "I'm the R2 of my team, and I'm here to show that…" (Adam: "Soy Ádám, el R2 de mi equipo, y estoy aquí para demostrar que…").
  • Backing up our case (reinforce): unchanged from the previous round. Short blocks return to the Introductor's and R1's arguments by name ("Our second argument was about…"), each with a fact and its source; the prompt asks for 100-120 words each.
  • If they say…: 6 blocks at standard and comprehensive length, 5 at brief. That follows Adam's feedback on the first sample, and matches the 5 blocks in his speech and the range of his prep groups. The blocks cover the other side's main arguments, the points they will use to back them up, and their answers to our case. He underlines the blocks they actually used, then speaks and expands them.
  • Each answer: their claim in one sentence, then our full answer, about 170-220 words at standard length. That is fuller than his written notes, as he asked:
    1. It pushes back in the first sentence and never opens by conceding ("Eso es lo que ven los adultos, pero…").
    2. It gives at least two specific facts, with numbers where the research has them, naming the source and year.
    3. It says what those facts mean for real people.
    4. It asks a question that turns their point around ("Si los protocolos fueran efectivos, ¿por qué…?").
    5. It ties back to our argument by name ("This is exactly our second argument…").
    6. It names the source as "Organisation, year".
  • Sourcing: every number must come from the attached research or Library, and every source an answer names must also be in the Sources list. With no number to cite, the answer argues from reasoning instead.
  • Closing line in his style: "That is why it is clear that… Thank you." (Adam: "Por todo esto, queda claro que… Muchas gracias.")
  • Order: his printout puts the refutations before the argument paragraph. The R2 spec asks for backing up first, then the answers, so that is the order.

Measured on live Claude Haiku 5.5 runs, standard length, same motion as the earlier sample ("This House would make public transport free at the point of use"):

  • English (~/.t3/scratch/case-v2-shots/r2-case.json):
    • 6 blocks, answers of 184-208 words (average 198).
    • All 6 cite numbers with source and year, ask a question, and name a source that is in the Sources list (9 sources).
    • The backing-up blocks ran 175-176 words, longer than the 100-120 the prompt asks for.
  • Hungarian:
    • 6 blocks, answers of 169-188 words (average 177).
    • The same 6-of-6 on numbers, questions and sources.
    • Backing-up blocks of 136-140 words.

Token budget for the longer R2

  • A long R2 reply is 4,000-5,500 tokens. Claude Haiku's reasoning at low effort ran to 6,000-10,000 tokens on top of that, far past the 3,000 that a 15,000 max_tokens left it.
  • One English R2 was cut off. Its retry finished at 128 s of the ai function's 130 s budget.
  • Generate now keeps at least 10,000 tokens for reasoning (REASONING_FLOOR_TOKENS in _shared/output-caps.ts): max_tokens is 22,000 at low effort and 34,000 on a cut-off retry. Its reply cap stays at 12,000, so the existing rule that every cap fits its retry still holds; a 20,000 cap would break it at high effort.
  • The floor is used on both the streaming and non-streaming paths, because both go through callAI. Other features are unchanged.
  • After the change, live runs in English and Hungarian finished on the first try: English R2 used 12,278 output tokens (7,351 reasoning) in 62 s, Hungarian 7,680 tokens in 39 s, with no cap hits.
  • No other limit applies to a generated case's size: no schema maximum, no client limit, and no export-gdoc size cap.

No jargon in the prompt, labels or exports

  • The generate prompt contains none of framework, burden of proof, weighing, warrant or impact. The model now writes whyItMatters, and the server files it under the existing impact key, so stored cases keep their shape.
  • The case UI labels, the clipboard text and the three exports (export-gdoc, PDF, DOCX) have no Thesis, Framework, Burden of Proof, Predicted Opponent Case or Counterpoint labels, and no "No framework provided." or "No burden of proof provided." placeholders. The unused translation keys are removed in EN, HU and ES.
  • Old saved cases still show their extra sections, under plain names:
    • framework: "How to judge this debate"
    • burden of proof: "What we have to show"
    • definitions: "What we mean by the motion"
    • predicted arguments: "They may also say"
    • the old second rebuttal: "Say it like this"
  • DOCX and Google Docs exports no longer require predictedOpponentCase.
  • The model can still write "impact" or "thesis" as ordinary words in its content. One live run cited a "master thesis" as a source and said "clear impact on". That is case text, not a label.

Schema (packages/core/src/ai/schemas/generate.ts)

The storage keys stay as they were. The labels change on screen and in exports.

  • New, all optional, and all accept null:
    • opening { hook, definition }
    • contentions[].title
    • counters[].source
    • reinforce[] { argument, text } (R2)
    • whyWeWin[] (Conclusor)
    • closingLine
    • sources[] { org, year, claim, url }
  • Now optional:
    • caseStructure.framework, keyDefinitions and burdenOfProof
    • predictedOpponentCase and its three lists
    • counters[].rebuttal
  • caseStructure.thesis is the opening's "we will show…" line (for R2, "I'm the R2 of my team…").
  • packages/core/src/ai/case-view.ts (readCase) reads old and new cases into the same sections, and every reader uses it:
    • GenerateResults
    • the DOCX and PDF exports
    • the clipboard text
    • the Crossfire prefill
    • Discuss
    • the flowchart (R2's blocks become its argument nodes)
    • export-gdoc keeps a small mirror, because it runs in Deno.

Server rules

enforceGenerateControls is role-aware:

  • R2: fails closed without at least one non-empty reinforce block or with fewer than 4 answers. It keeps at most 3 reinforce blocks, cuts the answers at 5 (brief) or 6, and empties contentions.
  • Conclusor: fails closed with fewer than 2 whyWeWin items.
  • Introductor: the only role that keeps opening.definition.
  • Other roles: keep the exact argument count per length.

enforceGenerateSources keeps a source's link only when the research or the student's Library supplied that URL. readCase also links only http(s) URLs.

Also in this PR

  • Sparring's school-role guidance (SparringRound.tsx, prompts/sparring.ts and the sparring.roleGuidance strings in EN, HU and ES) describes the same four jobs. The Introductor no longer "sets the framework", and R2 brings no new arguments.
  • e2e.config.ts describes the new case shape. This changes the agentic agent's context, so the next agentic run re-records its replay cache; I didn't run it.
  • Merged origin/main with a normal merge (no rebase, no force-push). The conflicts were the sentence-case relabels in en.json and the verify-skill docs and recipes. I kept the new section names, main's sentence case and main's "Daily AI uses used up" wording. Main didn't change GenerateResults.tsx, live-sections.ts or the other case readers, so the restyle, tab bar, motion and DebateOwl rename merged cleanly. The case cards keep main's motion-list-item and owl UI.

Staging deploy for the Google Docs export (approved by Adam: staging only)

  • Deployed export-gdoc alone to staging (brifeyypjauyusmkptkx) from commit a0c16ae, after its local tests passed (10 of 10). Staging went from version 20 (sha 8852d4e1) to version 21 (sha 6302cb22). No other function was deployed, and production was not touched.
  • export-gdoc hasn't changed since a0c16ae, so v21 is the final export code.
  • Staging's sync timer puts main's copy back when it rechecks for drift, so expect v21 to be replaced by main's v20 code.
  • The real Google Docs export is not proven. It needs a live Google sign-in with Drive access. The Google refresh token on the VPS returns invalid_grant, and Adam asked us not to use his browser session, so we skipped it for now. The content builder is covered by supabase/functions/export-gdoc/generate_test.ts (new, old and R2 cases; no removed labels).

Deploy order (when this merges)

  1. Merge first: the client renders old- and new-shape output, and it accepts R2 and Conclusor cases from the old backend.
  2. Then deploy the ai and export-gdoc Edge Functions to production. In the other order, the current client sends new-shape output down its old repair path, which makes up a "General framework" and drops the new sections.

Verification

GitHub Actions is billing-blocked, so all of these are local runs. A local pass is not a CI pass.

  • npm run verify at 08db62f passed, exit 0:
    • docs check, lint (eslint, knip, repo rules) and typecheck
    • unit tests: 713 of 713
    • collaboration tests: 30 of 30
    • Edge tests: 492 of 492
    • build and the service-worker offline test
    • desktop @smoke Playwright: 47 of 47
    • The three specs known to fail on main (loading-performance, navigation-performance, search-quality) are not in the smoke set, so verify never ran them. verify:full was not run.
  • verify-debatelab, fake provider, desktop-chromium and mobile-chromium:
    • recipes/case-generation: 20 of 20 at 7cdc2e8, including gen-roles with 6 R2 blocks. The later commit changed only the prompt wording and the token room, neither of which the fake provider reads.
    • recipes/live-generation: 6 of 6 and recipes/current-debate: 6 of 6 at 747729f.
    • e2e/sparring.spec.ts: 6 of 6.
  • verify-debatelab --live (Claude Haiku 5.5 and real Exa), desktop: gen-roles passed at 08db62f. Every generate call used max_tokens 22,000 and finished on the first try, with R2 in 36 s.
    • It ran on the local stack, not on staging. Staging runs main's ai function, and only export-gdoc was approved for a staging deploy.
  • Live samples on the same motion as before, all with no horizontal overflow:
    • ~/.t3/scratch/case-v2-shots/r2-390.png, r2-1440.png and r2-case.json (English)
    • r2-hu-390.png (Hungarian)
    • introductor- and conclusor-390/1440.png, from 2026-10-28 09:45; their prompt sections haven't changed since.
  • Not run:
    • a real Google Docs export (see above)
    • the agentic suite (the e2e.config.ts context change means its replay cache re-records on the next run)
    • WebKit and the staging E2E suite

🤖 Generated with Claude Code

Programcode and others added 5 commits October 27, 2026 09:07
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Oct 27, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Warning

Review limit reached

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

Next included review available in 9 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

Learn how review limits work.

Review configuration:

Run configuration
Commits

Reviewing files that changed from the base of the PR and between 2916c51 and 08db62f.

Files selected for processing: 11
  • .claude/skills/verify-debatelab/features/case-generation.md
  • .claude/skills/verify-debatelab/recipes/case-generation.spec.ts
  • e2e/full-stack/fake-openrouter.mjs
  • supabase/functions/_shared/control-enforcement.test.ts
  • supabase/functions/_shared/control-enforcement.ts
  • supabase/functions/_shared/output-caps.test.ts
  • supabase/functions/_shared/output-caps.ts
  • supabase/functions/_shared/prompts/generate.ts
  • supabase/functions/ai/control-matrix.test.ts
  • supabase/functions/ai/prompt-builder.test.ts
  • supabase/functions/ai/provider.ts
Recent review info
Run configuration
Commits

Reviewing files that changed from the base of the PR and between 9868daa and 2916c51.

Files selected for processing: 43
  • .claude/skills/verify-debatelab/features/case-generation.md
  • .claude/skills/verify-debatelab/features/live-generation.md
  • .claude/skills/verify-debatelab/recipes/case-generation.spec.ts
  • .claude/skills/verify-debatelab/recipes/live-generation.spec.ts
  • agentic/case-generation.e2e.ts
  • e2e.config.ts
  • e2e/fixtures/ai-responses.ts
  • e2e/full-stack/fake-openrouter.mjs
  • e2e/sparring.spec.ts
  • packages/core/src/ai/case-view.ts
  • packages/core/src/ai/schemas/generate.ts
  • packages/core/src/crossfire/prefill.ts
  • src/components/features/generate/GenerateResults.tsx
  • src/components/features/sparring/SparringRound.tsx
  • src/components/features/tools/discuss.ts
  • src/components/features/tools/live-sections.ts
  • src/components/features/tools/result-text.ts
  • src/lib/ai/features/generate.ts
  • src/lib/export-utils.ts
  • src/lib/flowchart.ts
  • src/lib/pdf-export-utils.ts
  • src/locales/en.json
  • src/locales/es.json
  • src/locales/hu.json
  • src/pages/GeneratePage.tsx
  • src/test/helpers/cases.ts
  • src/test/i18n-untranslated.test.ts
  • src/test/lib/case-exports.test.ts
  • src/test/lib/case-view.test.ts
  • src/test/lib/flowchart.test.ts
  • src/test/pages/GeneratePage.test.tsx
  • supabase/functions/_shared/control-enforcement.test.ts
  • supabase/functions/_shared/control-enforcement.ts
  • supabase/functions/_shared/debate-context.ts
  • supabase/functions/_shared/prompts/generate.ts
  • supabase/functions/_shared/prompts/prompt-examples.test.ts
  • supabase/functions/_shared/prompts/prompt-examples.ts
  • supabase/functions/_shared/prompts/sparring.ts
  • supabase/functions/ai/control-matrix.test.ts
  • supabase/functions/ai/features/generate.ts
  • supabase/functions/ai/prompt-builder.test.ts
  • supabase/functions/export-gdoc/generate_test.ts
  • supabase/functions/export-gdoc/index.ts

Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review.

Walkthrough

The change updates case generation to use speaker-specific openings, arguments, reinforcement, opponent responses, reasons to win, closing lines, and sources. A shared case view normalizes current and legacy data for display, Crossfire, flowcharts, and exports. Generation controls, prompts, and tests now cover role-specific output.

Changes: Speaker-specific case generation

Layer / File(s) Summary
Case schema and normalization
packages/core/src/ai/schemas/generate.ts, packages/core/src/ai/case-view.ts, src/test/helpers/cases.ts, src/test/lib/case-view.test.ts
The generation schema adds fields and types for openings, reinforcement, counters, and sources. readCase maps current and legacy output into a normalized view, and source helpers format and merge sources.
Role-specific generation and validation
supabase/functions/_shared/prompts/*, supabase/functions/_shared/control-enforcement.ts, supabase/functions/ai/features/generate.ts, src/lib/ai/features/generate.ts, e2e/full-stack/fake-openrouter.mjs, e2e/fixtures/ai-responses.ts, supabase/functions/ai/*test.ts, supabase/functions/_shared/*test.ts, .claude/skills/verify-debatelab/recipes/case-generation.spec.ts
Prompts and controls define role-specific output. R2 uses reinforcement and counters, while the Conclusor uses reasons to win. Source validation filters URLs against known sources. Response transformation and tests cover the role-specific shapes.
Case display and regeneration
src/components/features/generate/GenerateResults.tsx, src/components/features/tools/*, src/pages/GeneratePage.tsx, packages/core/src/crossfire/prefill.ts, src/components/features/sparring/SparringRound.tsx, src/locales/*, .claude/skills/verify-debatelab/*, agentic/case-generation.e2e.ts, e2e/sparring.spec.ts
Case display and live-draft sections use the normalized case fields. Regeneration maps selections to the updated sections and merges sources. Crossfire, discussion, sparring guidance, translations, and UI checks reflect the revised content and labels.
Exports and case flowcharts
src/lib/export-utils.ts, src/lib/pdf-export-utils.ts, supabase/functions/export-gdoc/index.ts, src/lib/flowchart.ts, src/test/lib/case-exports.test.ts, supabase/functions/export-gdoc/generate_test.ts, src/test/lib/flowchart.test.ts
DOCX, PDF, and Google Docs exports render available case sections and sources, including legacy content where present. Flowcharts use arguments, reinforcement, or win reasons and map opponent claims to responses.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant PromptBuilder as buildGenerateUserPrompt
  participant Model as Generation model
  participant Generate as handleGenerate
  participant Controls as enforceGenerateControls
  participant Sources as enforceGenerateSources
  participant Results as GenerateResults
  User->>PromptBuilder: request role-specific case
  PromptBuilder->>Model: generation prompt
  Model->>Generate: generated case output
  Generate->>Controls: enforce role-specific sections
  Controls->>Sources: validate sources against known URLs
  Sources->>Results: normalized generation result
Loading
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Note

🎁 Summarized by CodeRabbit Free

Your organization is on the Free plan. CodeRabbit will generate a high-level summary and a walkthrough for each pull request. For a comprehensive line-by-line review, please upgrade your subscription to CodeRabbit Essentials by visiting https://app.coderabbit.ai/settings/billing.

Comment @coderabbitai help to get the list of available commands.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Programcode and others added 3 commits October 28, 2026 09:26
R2 now follows Adam's own R2 speech (Debate print 1, 2025-03-26): who is
speaking, about 2 minutes backing up the team's earlier arguments
(reinforce), then the bulk in "If they say…" answers, and a closing line.
Length sets R2's answer count (5/6/8) instead of an argument count.

The prompt, case labels and exports no longer use framework, burden of
proof, weighing, warrant, impact, thesis or counterpoint. Old saved cases
keep those sections under plain names. Sparring's school-role guidance and
the agentic test context describe the same structure.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… three languages

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Programcode and others added 2 commits October 28, 2026 10:09
The other side usually runs only 2-3 arguments, so R2's "If they say…"
holds one full answer per likely argument (about 90-120 words at standard
length), built the way Adam writes his own refutations in Debate print 1:
a direct reply, a fact with its source, what it means, a tie back to our
argument, and a closing question or firm line. He underlines the ones the
other side used and builds on them in the round.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Programcode
Programcode marked this pull request as ready for review October 28, 2026 10:26
Copilot AI balanced review requested due to automatic review settings October 28, 2026 10:26

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota.

Programcode and others added 2 commits October 28, 2026 10:39
Adam asked for more "If they say…" blocks, each with more content, like
his own debates. R2 now writes 6 blocks at standard length (5 brief): their
main arguments, the points they use to back them up, and their answers to
our case. Each answer pushes back at once, gives at least two specific
facts with source and year, says what they mean, asks a question that
turns their point around, and ties back to our argument by name. Numbers
must come from the attached research and every cited source must be in
the sources list.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude Haiku reasoned for 6,000-10,000 tokens on an R2 case at low
effort, past the 3,000 that a 15,000 max_tokens left it. One reply was
cut off and its retry finished at 128 s of the 130 s budget. Generate now
keeps at least 10,000 tokens for reasoning (max_tokens 22,000 at low) while
its reply cap stays at 12,000, so every cap still fits its retry. Also keep
R2's backing-up paragraphs short beside the longer answers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Programcode
Programcode merged commit a079628 into main Oct 28, 2026
3 of 14 checks passed
Programcode added a commit that referenced this pull request Oct 28, 2026
* Describe the new case format on the public guides

The guides still described the old case: thesis, framework, burden of
proof, contentions with warrants and impacts, and a predicted opponent
case. Generate now writes an opening, arguments (point, explanation,
evidence, why it matters), "If they say… we answer…" replies, a closing
line and sources (#244). The copy changes in seo_guides.py, seo_answers.py
and the PAGES meta in generate_seo_content.py, and the pages and llms.txt
are regenerated from them. The LD, PF, Policy and weighing format guides
are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Match the case guide to the shipped R2: no new arguments, five or six If they say blocks

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants