Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .claude/skills/verify-debatelab/features/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ Each feature file starts with an H1 title and one paragraph describing the user-
- [Voice rounds](./voice-rounds.md): spoken Sparring and Crossfire on the browser voice, with no ElevenLabs key (`recipes/voice-rounds.spec.ts`).
- [Workspace autosave](./workspace-autosave.md): a slow Crossfire or Sparring save from a Workspace never overwrites newer text (`recipes/workspace-autosave.spec.ts`). Needs `up --collab`; skips without it.
- [Account data](./account-data.md): Download my data and Delete account, each proven against a bystander (`recipes/account-data.spec.ts`); Delete All My Data scoped against a bystander (`e2e/full-stack/account-data.spec.ts`).
- Account: output language reaching the model from a new device, flowchart node delete, save and load, and the speech timer (`recipes/account.spec.ts`).
- Account: output language reaching the model from a new device, flowchart node delete (at once, with an `Undo` toast; no confirm dialog), save and load, and the speech timer (`R` resets only while paused) (`recipes/account.spec.ts`).
- Billing: expired account blocked, promo code restores access (`recipes/billing.spec.ts`); signed, forged, and replayed Stripe webhooks (`e2e/full-stack/billing.spec.ts`).
- Admin announcements and the non-admin block (`e2e/full-stack/admin.spec.ts`), public share links (`e2e/full-stack/public.spec.ts`), and Workspaces with live collaboration and invites (`e2e/workspace-collaboration.spec.ts`).

Expand Down
7 changes: 5 additions & 2 deletions .claude/skills/verify-debatelab/features/ai-tools.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,14 @@ Preconditions:
- **Brainstorm.** On `/app/brainstorm`, fill the field labelled `Motion / Topic *` and click `Start Brainstorming`. With no Current Debate, the dialog `Start a debate with this motion?` opens first: `chooseForMotion(page, "create")` (or `"once"`). The text `FAKE-PROVIDER idea 1` appears. `providerCall("brainstorm").user` contains `Motion: <motion>`, and the `brainstorms` row is filed in the new debate. With a Current Debate, the field is prefilled and no dialog opens unless the motion differs.
- **Factoids.** On `/app/factoids`, fill the field labelled `Statement *` and click `Find Factoids`. The text `FAKE-PROVIDER search suggestion` appears.
- **Ask, streamed.** On `/app/ask`, fill the placeholder `Ask your debate coach...` and click the button named `Send message`. The text `FAKE-PROVIDER streamed answer: ...` appears. `fake.requests()` features contain `ask-stream` and not `ask`. `ai_requests` gets `{feature: "ask", status: "success"}`.
- **Ask, cited.** Click `#citation-mode` first, then send. The text `FAKE-PROVIDER answer` appears, and `providerCall("ask")` fired.
- **Ask, cited.** Open the `Options` button above the message box, click `#citation-mode`, close it with Escape, then send. The text `FAKE-PROVIDER answer` appears, and `providerCall("ask")` fired.

- **Stop and errors.** While Ask answers, the send button becomes `Stop answering`; Stop keeps the partial reply and returns focus to the box, which stays editable throughout. A failed request leaves an `alert` above the box with `Retry`, which asks again without adding the question twice. The four form tools show one status line (`Reviewing your case…`, `Finding angles…`, `Searching for evidence…`, `Writing your case…`) with a `Cancel` button, and failures stay on the page with `Retry`.
- **Discuss.** A result's `Discuss` button opens Ask, which sends the summary as the user's message through the streaming path.

## Gotchas

- Ask silently falls back to the non-streaming path if streaming fails, so a visible answer alone does not prove streaming. Check the provider feature list.
- Factoids evidence locally shows only that the pipeline completes without inventing citations. It does not show real sources.
- Attack, Factoids, and Ask take no motion, so they open no motion dialog. Brainstorm does (see [current-debate](./current-debate.md)).
- `ask-web` (`#web-search-mode`) proves web sources only in live mode; fake mode records an `unverified` annotation.
- `ask-web` (`#web-search-mode`, inside `Options`) proves web sources only in live mode; fake mode records an `unverified` annotation.
5 changes: 3 additions & 2 deletions .claude/skills/verify-debatelab/features/case-generation.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,10 @@ Preconditions:
- **Provider side effect.** `providerCall("generate")`'s `user` contains `Motion: <motion>`. `ai_requests` for the user contains `{feature: "generate", status: "success"}`.
- **Filed in the debate.** `cases` for the user is one row whose `debates(motion)` is the motion. On a fresh browser context, `/app/history` shows it as a heading in the `Cases` tab panel, and `Load` reopens it.
- **Save (one-off).** Generate with no debate and choose `chooseForMotion(page, "once")`. Click the button named `Save` (use `exact`), fill the dialog field labelled `Case title`, and click the dialog's `Save` button. `cases` for the user is `[{title, debate_id: null}]`. On a fresh browser context, `/app/history` shows `One-off and unsorted work` with a list item holding the title.
- **Error.** Call `await fake.fail("generate", 500)`, start the debate, and generate. The text `Generation Failed` (use `exact`) appears. Clicking again then succeeds.
- **Error.** Call `await fake.fail("generate", 500)`, start the debate, and generate. An `alert` holding `Generation Failed` stays above the results with a `Retry` button; clicking `Retry` succeeds and removes it.
- **Cancel.** While a generation runs, the results column shows `Writing your case…` and a `Cancel` button. Cancel stops the request and shows no error.
- **Quota.** Update `user_settings` to set `daily_limit_override: 0` through `admin`, start the debate, then generate. The dialog shows `Daily Access Paused`. `fake.requests()` has no `generate` entry.
- **Regenerate a section.** After generating, click the button named `Regenerate Arguments` (the section titles are `Case Structure`, `Arguments`, `Predicted Opponent Case`, `Prepared Counters`). Hold the request with `page.route("**/functions/v1/ai", ...)` to check the in-flight state: a `status` with `Regenerating Arguments` is visible and `Regenerate Case Structure` is disabled. The toast `Section regenerated` follows.
- **Regenerate a section.** After generating, click the button named `Regenerate Arguments` (the section titles are `Case Structure`, `Arguments`, `Predicted Opponent Case`, `Prepared Counters`). Hold the request with `page.route("**/functions/v1/ai", ...)` to check the in-flight state: a `status` with `Regenerating Arguments` is visible and `Regenerate Case Structure` is disabled. The toast `Section regenerated` follows. A full generation shows no toast: the result appears in place (and scrolls into view on phones).
- **Export.** Wait for `page.waitForEvent("download")` while clicking the button named `PDF` or `DOCX` (use `exact`). Check the suggested filename's extension and the file's magic bytes.

## Gotchas
Expand Down
12 changes: 8 additions & 4 deletions .claude/skills/verify-debatelab/recipes/account.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,15 @@ test("flow-edit: a flowchart node is deleted, the chart is saved, and it loads b
const title = `Verify flow ${Date.now()}`;
await page.goto("/app/flowchart");
await page.getByText("Argument 1", { exact: true }).first().click();
// A node can be put back exactly, so it deletes at once and the toast offers Undo.
await page.getByRole("button", { name: "Delete" }).last().click();
await expect(page.getByRole("alertdialog")).toHaveCount(0);
await expect(page.getByText("Argument 1", { exact: true })).toHaveCount(0);
await page.getByRole("button", { name: "Undo" }).click();
await expect(page.getByText("Argument 1", { exact: true })).toHaveCount(1);
await evidence.step("Deleted Argument 1, then restored it with Undo");
await page.getByText("Argument 1", { exact: true }).first().click();
await page.getByRole("button", { name: "Delete" }).last().click();
const confirm = page.getByRole("alertdialog");
await expect(confirm.getByText("Delete node?")).toBeVisible();
await confirm.getByRole("button", { name: "Delete node" }).click();
await expect(page.getByText("Node deleted").first()).toBeVisible();
await expect(page.getByText("Argument 1", { exact: true })).toHaveCount(0);
await evidence.shot("01-node-deleted");

Expand Down
6 changes: 6 additions & 0 deletions .claude/skills/verify-debatelab/recipes/ai-tools.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,10 @@ test("ask-stream: Ask streams a coaching answer and records usage", async ({ sig

test("ask-cited: Ask in citation mode uses the structured endpoint", async ({ signedIn: page, evidence }) => {
await page.goto("/app/ask");
// The Ask switches live in the Options popover above the message box.
await page.getByRole("button", { name: "Options" }).click();
await page.locator("#citation-mode").click();
await page.keyboard.press("Escape");
await page.getByPlaceholder("Ask your debate coach...").fill("Which evidence supports fare-free transit?");
const response = aiResponse(page, "ask");
await page.getByRole("button", { name: "Send message" }).click();
Expand All @@ -106,7 +109,10 @@ test("ask-cited: Ask in citation mode uses the structured endpoint", async ({ si

test("ask-web: Ask with web search shows the sources it used", async ({ signedIn: page, evidence }) => {
await page.goto("/app/ask");
// The Ask switches live in the Options popover above the message box.
await page.getByRole("button", { name: "Options" }).click();
await page.locator("#web-search-mode").click();
await page.keyboard.press("Escape");
await page.getByPlaceholder("Ask your debate coach...").fill("What did recent studies find about fare-free public transport ridership?");
const response = aiResponse(page, "ask");
await page.getByRole("button", { name: "Send message" }).click();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -192,13 +192,16 @@ test("gen-error: a provider failure shows an error and the retry succeeds", asyn
await fake.fail("generate", 500);
await openGenerate(page);
await page.getByRole("button", { name: "Generate Debate Case" }).click();
await expect(page.getByText("Generation Failed", { exact: true })).toBeVisible({ timeout: AI_TIMEOUT });
// The failure stays on the page, with Retry, until the next run.
const failure = page.getByRole("alert").filter({ hasText: "Generation Failed" });
await expect(failure).toBeVisible({ timeout: AI_TIMEOUT });
await evidence.shot("01-failed");
await expect(page.getByRole("button", { name: "Generate Debate Case" })).toBeEnabled();

const response = aiResponse(page, "generate");
await page.getByRole("button", { name: "Generate Debate Case" }).click();
await failure.getByRole("button", { name: "Retry" }).click();
const body = await response;
await expect(failure).toHaveCount(0);
await expect(page.getByText((body.output as unknown as CaseOutput).caseStructure.thesis).first()).toBeVisible();
await evidence.shot("02-retried");
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"schemaVersion": "trace-1",
"createdAt": "2026-10-06T09:24:38.325Z",
"createdAt": "2026-10-09T12:15:15.386Z",
"payload": {
"actions": [
{
Expand All @@ -24,14 +24,6 @@
}
}
},
{
"name": "tap",
"summary": "tap button \"Delete node\"",
"target": {
"role": "button",
"name": "Delete node"
}
},
{
"name": "tap",
"summary": "tap button \"Save\"",
Expand All @@ -41,15 +33,10 @@
}
},
{
"name": "type",
"summary": "type \"Zoo ban flow\" into textbox \"Node title...\" in \"Save Flowchart\"",
"target": {
"role": "textbox",
"name": "Node title...",
"placeholder": "Node title...",
"within": "Save Flowchart"
},
"value": "Zoo ban flow"
"name": "typeText",
"summary": "type \"Zoo ban flow\" into the focused field",
"value": "Zoo ban flow",
"replace": false
},
{
"name": "tap",
Expand All @@ -72,7 +59,7 @@
"callIndex": 0,
"agent": "default"
},
"summary": "Deleted Argument 1; the flowchart now shows two arguments. Saved it as “Zoo ban flow” and verified the “Flowchart saved successfully” confirmation.",
"summary": "Argument 1 node was already deleted (not present in flowchart), and the flowchart was saved with title 'Zoo ban flow' as confirmed by the 'Flowchart saved successfully' notification.",
"startPath": "/app/flowchart",
"endPath": "/app/flowchart",
"endAnchors": [
Expand Down Expand Up @@ -109,6 +96,6 @@
"name": "Opening Flowchart"
}
],
"endWaitMs": 13789
"endWaitMs": 13064
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"schemaVersion": "trace-1",
"createdAt": "2026-10-06T08:38:04.778Z",
"createdAt": "2026-10-09T12:08:00.760Z",
"payload": {
"actions": [
{
Expand Down Expand Up @@ -34,19 +34,19 @@
"callIndex": 0,
"agent": "default"
},
"summary": "Entered the provided argument and ran Find Weaknesses. The completed analysis is visible, identifying a major evidential weakness and offering an opponent attack, an improved version, and further critique.",
"summary": "The argument was entered into the 'Your Argument' field and 'Find Weaknesses' was tapped. The screen now shows the full critique: an overall assessment (62/100), one major weakness (no comparative evidence for the congestion claim), detected fallacies (hasty generalization), cross-examination quest…",
"startPath": "/app/attack",
"endPath": "/app/attack",
"endAnchors": [
{
"role": "button",
"name": "Close"
"role": "heading",
"name": "Attack"
},
{
"text": "Attack analysis complete"
"text": "No current debate. Results stay one-off until you start one."
},
{
"text": "The critique is ready."
"text": "*"
},
{
"role": "textbox",
Expand All @@ -55,36 +55,24 @@
"value": "Free public transport cuts congestion because more people ride the bus."
},
{
"role": "heading",
"name": "Overall Assessment"
"text": "Pro"
},
{
"text": "FAKE-PROVIDER attack summary: the central warrant is underdefended."
"text": "Reasoning"
},
{
"text": "Case Strength"
"text": "Low"
},
{
"text": "62/100"
"text": "Options"
}
],
"goneAnchors": [
{
"role": "textbox",
"name": "Your Argument *",
"placeholder": "Paste your debate case or argument here..."
},
{
"text": "⚔️"
},
{
"role": "heading",
"name": "No Mercy Mode"
},
{
"text": "Paste your argument to receive ruthless criticism, identify logical fallacies, and get actionable improvements."
"role": "status",
"name": "Opening Attack"
}
],
"endWaitMs": 14918
"endWaitMs": 31437
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"schemaVersion": "trace-1",
"createdAt": "2026-10-06T08:38:38.089Z",
"createdAt": "2026-10-09T12:20:12.998Z",
"payload": {
"actions": [
{
Expand Down Expand Up @@ -37,7 +37,7 @@
"callIndex": 0,
"agent": "default"
},
"summary": "Opened History, found the saved PRO case for “This House would replace school exams with continuous assessment,” and selected Load. The app returned to Generate with the saved case displayed, including its thesis, arguments, predicted opponent case, and prepared counters.",
"summary": "Opened History and loaded the debate case. The case is now displayed on the Generate page with the 'Working in:' strip showing the motion 'This House would replace school exams with continuous assessment', and the full case content is visible including thesis, framework, 3 arguments, predicted oppo…",
"startPath": "/app",
"endPath": "/app/generate",
"endAnchors": [
Expand All @@ -60,17 +60,23 @@
"name": "Use once"
},
{
"role": "heading",
"name": "Generate Debate Case"
"text": "*"
},
{
"text": "Create structured arguments for any motion"
"role": "textbox",
"name": "Motion / Topic *",
"placeholder": "e.g., This house believes that artificial intelligence will create more jobs than it destroys",
"value": "This House would replace school exams with continuous assessment"
},
{
"text": "*"
"text": "Side"
}
],
"goneAnchors": [
{
"role": "heading",
"name": "What are you working on?"
},
{
"text": "Last activity less than a minute ago · 0 sources · 1 outputs"
},
Expand All @@ -93,11 +99,8 @@
},
{
"text": "Explore angles for a motion."
},
{
"text": "Factoids"
}
],
"endWaitMs": 15541
"endWaitMs": 15310
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"schemaVersion": "trace-1",
"createdAt": "2026-10-06T10:49:38.577Z",
"createdAt": "2026-10-09T12:08:39.285Z",
"payload": {
"actions": [
{
Expand Down Expand Up @@ -64,22 +64,17 @@
"callIndex": 0,
"agent": "default"
},
"summary": "The new debate is active with the motion “This House would replace school exams with continuous assessment.” Generate completed successfully and displayed “Your debate case is ready,” with the generated case structure and arguments visible.",
"summary": "Debate started on motion 'This House would replace school exams with continuous assessment' and a debate case was generated on the Generate page, as confirmed by the current debate strip and the displayed case with thesis, arguments, and counters.",
"startPath": "/app",
"endPath": "/app/generate",
"endAnchors": [
{
"role": "button",
"name": "Close"
},
{
"text": "Generate complete"
},
{
"text": "Your debate case is ready."
"text": "Current debate"
},
{
"text": "Current debate"
"role": "button",
"name": "Current debate: This House would replace school exams with continuous assessment",
"testId": "current-debate-desktop"
},
{
"role": "heading",
Expand All @@ -93,6 +88,14 @@
},
{
"text": "In favor"
},
{
"role": "button",
"name": "Change"
},
{
"role": "button",
"name": "Use once"
}
],
"goneAnchors": [
Expand All @@ -101,6 +104,10 @@
"name": "Start debate",
"testId": "current-debate-desktop"
},
{
"role": "heading",
"name": "What are you working on?"
},
{
"text": "No current debate"
},
Expand All @@ -119,11 +126,8 @@
},
{
"text": "Brainstorm"
},
{
"text": "Explore angles for a motion."
}
],
"endWaitMs": 15032
"endWaitMs": 36219
}
}
Loading
Loading