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 @@ -40,7 +40,7 @@ Each feature file starts with an H1 title and one paragraph describing the user-
## Features

- [Demo-code sign-in](./auth-demo-login.md): sign-in, reload, rejected code, sign-out (`e2e/full-stack/auth.spec.ts`); email one-time-code sign-in through Mailpit, the emailed sign-in link through `/auth/callback` (including after a failed resend, and a used link), and a second account on the same browser (`recipes/auth.spec.ts`).
- [Case generation](./case-generation.md): generate, web research, save and reopen from History on a fresh device, PDF and DOCX export, single-section regeneration, provider failure and retry, a reply cut off at its length cap, quota block (`recipes/case-generation.spec.ts`).
- [Case generation](./case-generation.md): generate, web research, save and reopen from History on a fresh device, PDF and DOCX export, single-section regeneration, building a flowchart from the case, provider failure and retry, a reply cut off at its length cap, quota block (`recipes/case-generation.spec.ts`).
- [Library sources](./library-sources.md): add, pause, delete, grounding, and URL import (`recipes/library.spec.ts`).
- [AI tools](./ai-tools.md): Attack, Brainstorm, Factoids, and Ask streamed, cut off at its length cap, cited, and with web search (`recipes/ai-tools.spec.ts`).
- [Live generation](./live-generation.md): every AI tool shows its answer while the model writes it, Cancel mid-stream, and the event stream's shape (`recipes/live-generation.spec.ts`); Crossfire and Sparring requests stream too (`recipes/practice.spec.ts`).
Expand Down
2 changes: 2 additions & 0 deletions .claude/skills/verify-debatelab/features/case-generation.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ A signed-in user enters a motion on Generate and receives a structured debate ca
- `gen-save` saves a one-off case (the motion dialog answered with `Not now, use it once`) under a title into `cases` with `debate_id: null`. It appears in History under `One-off and unsorted work`, including from a fresh browser context.
- `gen-regenerate` regenerates one section. Only that section shows the overlay `Regenerating <section>…`; the rest of the case and the result toolbar stay on screen, and every regenerate button and the form's submit button are disabled until it finishes.
- `gen-export` downloads a `.pdf` (starting with `%PDF`) and a `.docx` (starting with `PK`).
- `gen-flowchart` builds a flowchart from the Current Debate's case. `/app/flowchart` opens the template; the button `Build from current case` replaces it with the thesis, one node per contention (claim and evidence), the predicted attacks, and the counters, fitted to the canvas. Node titles the app wrote (`Thesis`, `Argument 1`) follow the UI language.

## How to get to it (user POV)

Expand All @@ -33,6 +34,7 @@ Preconditions:
- **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. A full generation shows no toast: the result appears in place (and scrolls into view on phones).
- **Flowchart.** After generating in the debate, open `/app/flowchart` and click the button named `Build from current case` (Hungarian: `Létrehozás a jelenlegi esetből`). It appears only when the case belongs to the Current Debate, or to one-off work while no debate is chosen. The contention claims are visible, and an edge is a button named `From Thesis to Argument 1`.
- **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
30 changes: 30 additions & 0 deletions .claude/skills/verify-debatelab/recipes/case-generation.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,36 @@ test("gen-save: a one-off case is saved under a title and reopens from History o
await reopenFromHistory(browser, user, title, output.caseStructure.thesis, evidence, "02", "one-off");
});

test("gen-flowchart: the Flowchart builds from the Current Debate's case, and its app text follows the language", async ({ signedIn: page, evidence }) => {
const { output } = await generate(page);
await page.goto("/app/flowchart");
// The template stays the blank start; the case is one click away.
await expect(page.getByText("Your Thesis", { exact: true })).toBeVisible();
await page.getByRole("button", { name: "Build from current case" }).click();
await expect(page.getByText(output.caseStructure.thesis).first()).toBeVisible();
for (const contention of output.contentions) await expect(page.getByText(contention.claim).first()).toBeVisible();
await expect(page.getByText("Your Thesis", { exact: true })).toHaveCount(0);
// Every node fits the canvas once it is built.
await expect.poll(() => page.evaluate(() => {
const canvas = document.querySelector(".react-flow")!.getBoundingClientRect();
return [...document.querySelectorAll(".react-flow__node")].filter((node) => {
const box = node.getBoundingClientRect();
return box.left < canvas.left - 1 || box.right > canvas.right + 1 || box.top < canvas.top - 1 || box.bottom > canvas.bottom + 1;
}).length;
})).toBe(0);
await expect(page.getByRole("button", { name: "From Thesis to Argument 1" })).toHaveCount(1);
await evidence.shot("01-built-from-case");

// Node titles the app wrote follow the language on the same canvas; the case's own text does not change.
await page.getByRole("button", { name: "Language", exact: true }).click();
await page.getByRole("radiogroup", { name: "Interface language" }).getByRole("radio", { name: "Magyar" }).click();
await page.keyboard.press("Escape");
await expect(page.getByText("1. érv", { exact: true })).toBeVisible();
await expect(page.getByText("Argument 1", { exact: true })).toHaveCount(0);
await expect(page.getByText(output.caseStructure.thesis).first()).toBeVisible();
await evidence.shot("02-built-from-case-hu");
});

test("gen-research: generation researches the web and cites what it found", async ({ signedIn: page, evidence }) => {
const { body } = await generate(page);
await evidence.json("01-generate-response", body);
Expand Down
42 changes: 42 additions & 0 deletions src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -501,6 +501,48 @@ All colors MUST be HSL.
transition: transform 250ms var(--motion-ease-out-strong);
}

/* React Flow's controls, minimap, attribution, and edge labels are white in its own
stylesheet; here they follow the theme. Its stylesheet loads after this one, so
each rule carries the extra .react-flow class. */
.react-flow .react-flow__controls {
box-shadow: 0 0 0 1px hsl(var(--border));
}

.react-flow .react-flow__controls-button {
background: hsl(var(--card));
color: hsl(var(--card-foreground));
border-bottom-color: hsl(var(--border));
}

.react-flow .react-flow__controls-button:hover {
background: hsl(var(--accent));
}

.react-flow .react-flow__minimap {
background-color: hsl(var(--card));
box-shadow: 0 0 0 1px hsl(var(--border));
}

.react-flow .react-flow__minimap-mask {
fill: hsl(var(--muted) / 0.6);
}

.react-flow .react-flow__attribution {
background: hsl(var(--card) / 0.8);
}

.react-flow .react-flow__attribution a {
color: hsl(var(--muted-foreground));
}

.react-flow .react-flow__edge-textbg {
fill: hsl(var(--card));
}

.react-flow .react-flow__edge-text {
fill: hsl(var(--card-foreground));
}

/* React Flow's 26px canvas controls grow to touch size on phones. Its stylesheet loads
after this one with a content-box 16px + padding, so this needs the extra class. */
@media (pointer: coarse) {
Expand Down
Loading
Loading