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
6 changes: 3 additions & 3 deletions .claude/skills/verify-debatelab/features/library-sources.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,11 @@ Preconditions:

- Doctor passes. The user is signed in (`signedIn` fixture). A Current Debate exists: `await startDebate(page, motion)`. Without one, `Add Source` stays disabled.

- **Add.** Click the button named `Add Source`. Fill the textbox named `Source title...`, fill the textbox named `Paste source content...` with a unique marker, and click `Save to Library`. The switch named `Toggle <title>` is checked. `sources` has a row with `is_active: true` and the debate's `debate_id`.
- **Add.** Click the button named `Add Source`. Fill the textbox named `Title` (exact), fill the textbox named `Content` (exact) with a unique marker, and click `Save to Library`. Leaving either empty shows an inline error under the field and moves focus to it. The switch named `Toggle <title>` is checked. `sources` has a row with `is_active: true` and the debate's `debate_id`.
- **Toggle.** Click the switch named `Toggle <title>`. It becomes not checked.
- **Ground.** Go to `/app/brainstorm`; the motion is prefilled from the debate. Run it (see [ai-tools](./ai-tools.md)). `providerCall("brainstorm").system` contains the active marker and does not contain the paused marker.
- **Delete.** Click the button named `Delete <title>` (use `exact`). The switch count drops to 0, and stays 0 after `page.reload()`. `sources` no longer has the title.
- **Import.** In the add panel, fill `Import from URL` and click `Import URL`. `Source Ready` appears, then `Save imported source` stores a row whose `origin` is the URL.
- **Delete.** Click the button named `Delete <title>` (use `exact`). It deletes at once, with no confirmation, and a toast `Deleted <title>` offers `Undo`, which saves the source again. The switch count drops to 0, and stays 0 after `page.reload()`. `sources` no longer has the title.
- **Import.** In the add panel, fill `Import from URL` and click `Import URL`. The `Import preview` panel appears, then `Save imported source` stores a row whose `origin` is the URL.
- **Erase.** On Settings, click `Delete All My Data`. In the dialog, fill `Type your email to confirm` with `user.email` and click the dialog's `Delete All My Data`. The text `Data Deleted` appears, and `sources` for the user is `[]`.

## Gotchas
Expand Down
4 changes: 2 additions & 2 deletions .claude/skills/verify-debatelab/recipes/account-data.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ const accountUrl = `${process.env.LOCAL_SUPABASE_URL}/functions/v1/account`;
async function addSource(page: Page, title: string, content: string) {
await page.goto("/app/library");
await page.getByRole("button", { name: "Add Source" }).click();
await page.getByRole("textbox", { name: "Source title..." }).fill(title);
await page.getByRole("textbox", { name: "Paste source content..." }).fill(content);
await page.getByRole("textbox", { name: "Title", exact: true }).fill(title);
await page.getByRole("textbox", { name: "Content", exact: true }).fill(content);
await page.getByRole("button", { name: "Save to Library" }).click();
await expect(page.getByRole("switch", { name: `Toggle ${title}` })).toBeChecked();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ const LIVE_AWARE_TIMEOUT = LIVE ? 900_000 : 150_000;
async function addSource(page: Page, title: string, content: string) {
await page.goto("/app/library");
await page.getByRole("button", { name: "Add Source" }).click();
await page.getByRole("textbox", { name: "Source title..." }).fill(title);
await page.getByRole("textbox", { name: "Paste source content..." }).fill(content);
await page.getByRole("textbox", { name: "Title", exact: true }).fill(title);
await page.getByRole("textbox", { name: "Content", exact: true }).fill(content);
await page.getByRole("button", { name: "Save to Library" }).click();
await expect(page.getByRole("switch", { name: `Toggle ${title}` })).toBeChecked();
}
Expand Down
23 changes: 19 additions & 4 deletions .claude/skills/verify-debatelab/recipes/library.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ const motion = "Public transport should be free";

async function addSource(page: Page, title: string, content: string) {
await page.getByRole("button", { name: "Add Source" }).click();
await page.getByRole("textbox", { name: "Source title..." }).fill(title);
await page.getByRole("textbox", { name: "Paste source content..." }).fill(content);
await page.getByRole("textbox", { name: "Title", exact: true }).fill(title);
await page.getByRole("textbox", { name: "Content", exact: true }).fill(content);
await page.getByRole("button", { name: "Save to Library" }).click();
await expect(page.getByRole("switch", { name: `Toggle ${title}` })).toBeChecked();
}
Expand Down Expand Up @@ -37,13 +37,28 @@ test("lib-add, lib-toggle, lib-ground, lib-delete: only active sources ground AI
await evidence.json("02-grounding", { activeInPrompt: true, pausedInPrompt: false });

await page.goto("/app/library");
const userId = await user.userId();
const pausedRow = async () =>
((await admin.from("sources").select("id,created_at,is_active").eq("user_id", userId).eq("title", "Paused study")).data ?? [])
// The client keeps milliseconds; Postgres keeps microseconds.
.map((row) => ({ id: row.id, at: Math.floor(Date.parse(row.created_at)), active: row.is_active }));
const before = await pausedRow();
expect(before).toHaveLength(1);
// Deleting a source acts at once; the toast's Undo puts the same row back (same id, so a
// per-request exclusion still applies, and the same date, so it keeps its place).
await page.getByRole("button", { name: "Delete Paused study", exact: true }).click();
await expect(page.getByRole("switch", { name: "Toggle Paused study" })).toHaveCount(0);
await expect.poll(pausedRow).toEqual([]);
await page.getByRole("button", { name: "Undo" }).click();
await expect(page.getByRole("switch", { name: "Toggle Paused study" })).toHaveCount(1);
await expect.poll(pausedRow).toEqual(before);
await evidence.step("Deleted Paused study, then restored the same row with Undo");
await page.getByRole("button", { name: "Delete Paused study", exact: true }).click();
await expect(page.getByRole("switch", { name: "Toggle Paused study" })).toHaveCount(0);
await page.reload();
await expect(page.getByRole("switch", { name: "Toggle Active study" })).toBeVisible();
await expect(page.getByRole("switch", { name: "Toggle Paused study" })).toHaveCount(0);
// Live research can save the web sources it found, so check only the two added here.
const userId = await user.userId();
const { data: debate } = await admin.from("debates").select("id").eq("user_id", userId).eq("motion", motion).single();
const { data } = await admin.from("sources").select("title,is_active,debate_id").eq("user_id", userId).in("title", ["Active study", "Paused study"]);
expect(data).toEqual([{ title: "Active study", is_active: true, debate_id: debate?.id }]);
Expand All @@ -58,7 +73,7 @@ test("lib-import: a web page imports into the Library", async ({ signedIn: page,
await page.getByRole("button", { name: "Add Source" }).click();
await page.getByRole("textbox", { name: "Import from URL" }).fill(url);
await page.getByRole("button", { name: "Import URL" }).click();
await expect(page.getByText("Source Ready", { exact: true })).toBeVisible({ timeout: AI_TIMEOUT });
await expect(page.getByText("Import preview", { exact: true })).toBeVisible({ timeout: AI_TIMEOUT });
await evidence.shot("01-import-ready");
await page.getByRole("button", { name: "Save imported source" }).click();
await expect.poll(async () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"schemaVersion": "trace-1",
"createdAt": "2026-10-06T08:42:12.424Z",
"createdAt": "2026-10-09T07:51:18.781Z",
"payload": {
"actions": [
{
Expand Down Expand Up @@ -54,20 +54,20 @@
},
{
"name": "type",
"summary": "type \"Plastic bag levy study\" into textbox \"Source title...\"",
"summary": "type \"Plastic bag levy study\" into textbox \"Title\"",
"target": {
"role": "textbox",
"name": "Source title...",
"name": "Title",
"placeholder": "Source title..."
},
"value": "Plastic bag levy study"
},
{
"name": "type",
"summary": "type \"After a 5p levy, single-use bag sales a…\" into textbox \"Paste source content...\"",
"summary": "type \"After a 5p levy, single-use bag sales a…\" into textbox \"Content\"",
"target": {
"role": "textbox",
"name": "Paste source content...",
"name": "Content",
"placeholder": "Paste source content..."
},
"value": "After a 5p levy, single-use bag sales at major retailers fell by more than 95 percent."
Expand All @@ -93,26 +93,21 @@
"callIndex": 0,
"agent": "default"
},
"summary": "Started the debate “This House would ban single-use plastics” and added “Plastic bag levy study” to the Library with the provided content. The Library shows the saved source and a “Source Added” confirmation.",
"summary": "Started debate on 'This House would ban single-use plastics', opened Library, and added source 'Plastic bag levy study' with content about 5p levy reducing bag sales by 95%. Source now appears in library with 1 source active and grounding enabled.",
"startPath": "/app",
"endPath": "/app/library",
"endAnchors": [
{
"role": "button",
"name": "Close"
},
{
"text": "Source Added"
},
{
"text": "Your source is now available for grounding."
},
{
"text": "Current debate"
},
{
"text": "This House would ban single-use plastics"
},
{
"role": "button",
"name": "Current debate: This House would ban single-use plastics",
"testId": "current-debate-mobile"
},
{
"role": "heading",
"name": "Source Library"
Expand All @@ -122,7 +117,14 @@
"name": "Change"
},
{
"text": "All folders"
"text": "1 sources · 1 active · 1 folders"
},
{
"text": "Smart"
},
{
"role": "heading",
"name": "Your Knowledge Base"
}
],
"goneAnchors": [
Expand All @@ -131,6 +133,10 @@
"name": "Start debate",
"testId": "current-debate-mobile"
},
{
"role": "heading",
"name": "What are you working on?"
},
{
"text": "No current debate"
},
Expand All @@ -149,11 +155,8 @@
},
{
"text": "Brainstorm"
},
{
"text": "Explore angles for a motion."
}
],
"endWaitMs": 12722
"endWaitMs": 13153
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"schemaVersion": "trace-1",
"createdAt": "2026-10-06T08:52:21.869Z",
"createdAt": "2026-10-09T07:48:57.404Z",
"payload": {
"actions": [
{
Expand Down Expand Up @@ -32,7 +32,7 @@
"callIndex": 0,
"agent": "default"
},
"summary": "Changed the Interface Language setting to Español. The settings page and navigation are now displayed in Spanish, including “Ajustes” and “Idioma de Interfaz.”.",
"summary": "The interface language has been switched to Español. The settings page now displays entirely in Spanish (Ajustes, Herramientas, Biblioteca, Historial, Idioma de Interfaz), and the combobox shows Español as the selected value. The language switch took effect successfully.",
"startPath": "/app/settings",
"endPath": "/app/settings",
"endAnchors": [
Expand All @@ -41,7 +41,7 @@
"name": "Logotipo de DebateLab"
},
{
"text": "Inicio"
"text": "Herramientas"
},
{
"role": "button",
Expand Down Expand Up @@ -72,7 +72,7 @@
"name": "DebateLab logo"
},
{
"text": "Home"
"text": "Tools"
},
{
"role": "button",
Expand All @@ -97,6 +97,6 @@
"name": "Settings"
}
],
"endWaitMs": 17439
"endWaitMs": 32208
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"schemaVersion": "trace-1",
"createdAt": "2026-10-06T08:52:18.268Z",
"createdAt": "2026-10-09T07:52:16.070Z",
"payload": {
"actions": [
{
Expand Down Expand Up @@ -32,17 +32,14 @@
"callIndex": 0,
"agent": "default"
},
"summary": "Changed the Interface Language setting to Español. The settings page now displays Spanish labels, including “Idioma de Interfaz” with “Español” selected.",
"summary": "The interface language has been switched to Español. The settings page now displays all labels in Spanish (Ajustes, Idioma de Interfaz, Suscripción, Cuenta, Zona de peligro, etc.) and the Interface Language combobox shows Español as the selected value.",
"startPath": "/app/settings",
"endPath": "/app/settings",
"endAnchors": [
{
"role": "image",
"name": "Logotipo de DebateLab"
},
{
"text": "Cambiar tema"
},
{
"role": "button",
"name": "Iniciar debate",
Expand All @@ -56,51 +53,52 @@
"role": "heading",
"name": "Suscripción"
},
{
"text": "Pro"
},
{
"text": "El acceso a la IA está activo. Pueden aplicarse límites de uso."
},
{
"role": "heading",
"name": "Ajustes globales"
},
{
"text": "Idioma de Interfaz"
},
{
"text": "Español"
}
],
"goneAnchors": [
{
"role": "status",
"name": "Opening Settings"
},
{
"role": "image",
"name": "DebateLab logo"
},
{
"text": "Toggle theme"
},
{
"role": "button",
"name": "Start debate",
"testId": "current-debate-mobile"
},
{
"role": "link",
"name": "Terms"
"role": "heading",
"name": "Settings"
},
{
"role": "link",
"name": "Privacy"
"role": "heading",
"name": "Subscription"
},
{
"role": "link",
"name": "DebateLab logo DebateLab"
"text": "AI access is active. Usage limits may apply."
},
{
"role": "button",
"name": "Language"
"role": "heading",
"name": "Global Settings"
},
{
"text": "Interface Language"
},
{
"text": "AI Output Language"
}
],
"endWaitMs": 12650
"endWaitMs": 28157
}
}
Loading
Loading