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
Binary file added docs/evidence/retained-ui-bugs-before-after.mp4
Binary file not shown.
107 changes: 107 additions & 0 deletions e2e/quality-backlog.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import { expect, test } from '@playwright/test';
import { setLocale, stubSupabaseSession } from './helpers/auth';

const locales = ['en', 'es', 'hu'] as const;
const titledRoutes = ['generate', 'attack', 'brainstorm', 'factoids', 'ask'] as const;

test('Sources does not show the stale consultation date', async ({ page }) => {
await page.goto('/expocit/sources');

await expect(page.getByRole('heading', { name: 'Sources' })).toBeVisible();
await expect(page.getByText(/Consulta: 8 de marzo de 2026/i)).toHaveCount(0);
});

for (const locale of locales) {
test(`navigation and page titles agree in ${locale}`, async ({ page }, testInfo) => {
await setLocale(page, locale);
await stubSupabaseSession(page);
await page.addInitScript(() => {
window.localStorage.setItem('debate-trainer-onboarding', JSON.stringify({
state: { hasCompletedOnboarding: true, isTourRunning: false },
version: 0,
}));
});
await page.goto('/app');

for (const route of titledRoutes) {
const desktopLink = page.locator(`[data-tour="nav-${route}"]`).first();
const label = await desktopLink.getAttribute('aria-label');
expect(label).toBeTruthy();
if (testInfo.project.name === 'mobile-chromium') {
await page.locator('header button.lg\\:hidden[aria-expanded]').click();
await page.getByRole('navigation', { name: 'Mobile navigation' })
.getByRole('link')
.filter({ hasText: label! })
.first()
.click();
} else {
await desktopLink.click();
}
await expect(page).toHaveURL(new RegExp(`/app/${route}$`));
await expect(page.getByRole('heading', { level: 1, name: label!, exact: true }).last()).toBeVisible();
}
});
}

test('Factoids names the save action with its claim', async ({ page }) => {
await setLocale(page, 'en');
await stubSupabaseSession(page);
await page.route('**/functions/v1/ai', async (route) => {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({
output: {
factoids: [{
id: 'fact-1',
shortClaim: 'Solar costs fell by 42 percent',
keyFigure: '42%',
context: 'The official report compares the relevant period.',
limitations: 'The result depends on the measured market segment.',
citation: {
title: 'Official energy statistics',
publisher: 'Department of Energy',
date: '2024',
url: 'https://energy.gov/reports/solar-costs-2024',
verified: 'verified',
},
howToUse: 'Use this as a bounded cost trend.',
relevanceScore: 92,
}],
searchSuggestions: [],
warnings: [],
verificationStatus: 'complete',
},
}),
});
});

await page.goto('/app/factoids');
await page.getByRole('textbox', { name: /statement/i }).fill('Solar energy costs are falling');
await page.getByRole('button', { name: /find factoids/i }).click();

await expect(page.getByRole('button', {
name: 'Save Solar costs fell by 42 percent to library',
})).toBeVisible();
});

test('Flowchart confirms selected-node deletion and reports success', async ({ page }, testInfo) => {
await setLocale(page, 'en');
await stubSupabaseSession(page);
await page.goto('/app/flowchart');

await page.getByText('Argument 1', { exact: true }).click();
await page.getByRole('button', { name: 'Delete' }).last().click();
await expect(page.getByRole('alertdialog', { name: 'Delete node?' })).toBeVisible();
const confirm = page.getByRole('button', { name: 'Delete node' });
if (testInfo.project.name === 'mobile-chromium') {
const box = await confirm.boundingBox();
expect(box).not.toBeNull();
await page.touchscreen.tap(box!.x + box!.width / 2, box!.y + box!.height / 2);
} else {
await confirm.click();
}

await expect(page.getByText('Node deleted', { exact: true })).toBeVisible();
await expect(page.getByText('Argument 1', { exact: true })).toHaveCount(0);
});
1 change: 1 addition & 0 deletions src/components/features/factoids/FactoidsResults.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ function FactoidCard({ factoid, onSave }: { factoid: Factoid, onSave?: FactoidsR
size="icon"
className="h-8 w-8 text-muted-foreground hover:text-primary"
onClick={() => onSave(factoid.shortClaim, `${factoid.context}\n\nKey Figure: ${factoid.keyFigure}\nLimitations: ${factoid.limitations}`, factoid.citation?.url)}
aria-label={t('factoidsResults.saveClaimToLibrary', { claim: factoid.shortClaim })}
title={t('factoidsResults.saveToLibrary')}
>
<Bookmark className="h-4 w-4" />
Expand Down
8 changes: 7 additions & 1 deletion src/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -747,6 +747,7 @@
"urlNeedsVerification": "URL requires verification",
"howToUse": "How to use:",
"saveToLibrary": "Save to Library",
"saveClaimToLibrary": "Save {{claim}} to library",
"discuss": "Discuss Evidence",
"verifyAll": "Verify All Sources",
"verifying": "Verifying Sources...",
Expand Down Expand Up @@ -1000,7 +1001,12 @@
"saveFailedToast": "Failed to save flowchart",
"loadedToast": "Flowchart loaded",
"deletedToast": "Flowchart deleted",
"deleteFailedToast": "Failed to delete flowchart"
"deleteFailedToast": "Failed to delete flowchart",
"deleteNodeTitle": "Delete node?",
"deleteNodeDescription": "{{label}} and all of its connections will be removed. This cannot be undone.",
"deleteNodeConfirm": "Delete node",
"deleteNodeCancel": "Cancel",
"nodeDeletedToast": "Node deleted"
},
"historyPage": {
"title": "History",
Expand Down
8 changes: 7 additions & 1 deletion src/locales/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -747,6 +747,7 @@
"urlNeedsVerification": "La URL requiere verificación",
"howToUse": "Cómo usar:",
"saveToLibrary": "Guardar en Biblioteca",
"saveClaimToLibrary": "Guardar {{claim}} en la biblioteca",
"discuss": "Discutir Evidencia",
"verifyAll": "Verificar Todas las Fuentes",
"verifying": "Verificando Fuentes...",
Expand Down Expand Up @@ -1000,7 +1001,12 @@
"saveFailedToast": "Error al guardar diagrama",
"loadedToast": "Diagrama cargado",
"deletedToast": "Diagrama eliminado",
"deleteFailedToast": "Error al eliminar diagrama"
"deleteFailedToast": "Error al eliminar diagrama",
"deleteNodeTitle": "¿Eliminar nodo?",
"deleteNodeDescription": "Se eliminarán «{{label}}» y todas sus conexiones. Esta acción no se puede deshacer.",
"deleteNodeConfirm": "Eliminar nodo",
"deleteNodeCancel": "Cancelar",
"nodeDeletedToast": "Nodo eliminado"
},
"historyPage": {
"title": "Historial",
Expand Down
8 changes: 7 additions & 1 deletion src/locales/hu.json
Original file line number Diff line number Diff line change
Expand Up @@ -747,6 +747,7 @@
"urlNeedsVerification": "Az URL ellenőrzést igényel",
"howToUse": "Használat:",
"saveToLibrary": "Mentés a Könyvtárba",
"saveClaimToLibrary": "„{{claim}}” mentése a könyvtárba",
"discuss": "Bizonyítékok Megvitatása",
"verifyAll": "Összes Forrás Ellenőrzése",
"verifying": "Források Ellenőrzése...",
Expand Down Expand Up @@ -1000,7 +1001,12 @@
"saveFailedToast": "Nem sikerült menteni a folyamatábrát",
"loadedToast": "Folyamatábra betöltve",
"deletedToast": "Folyamatábra törölve",
"deleteFailedToast": "Nem sikerült törölni a folyamatábrát"
"deleteFailedToast": "Nem sikerült törölni a folyamatábrát",
"deleteNodeTitle": "Csomópont törlése?",
"deleteNodeDescription": "A(z) „{{label}}” csomópont és minden kapcsolata törlődik. Ez a művelet nem vonható vissza.",
"deleteNodeConfirm": "Csomópont törlése",
"deleteNodeCancel": "Mégse",
"nodeDeletedToast": "Csomópont törölve"
},
"historyPage": {
"title": "Előzmények",
Expand Down
2 changes: 1 addition & 1 deletion src/pages/AskPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,7 @@ export default function AskPage() {
<div className="container py-8 px-4">
<div className="max-w-5xl mx-auto">
<AnimatedSection className="mb-8">
<h1 className="text-3xl font-bold text-foreground">{t('ask.title')}</h1>
<h1 className="text-3xl font-bold text-foreground">{t('nav.ask')}</h1>
</AnimatedSection>
<AnimatedSection delay={75}>
<AskChat
Expand Down
2 changes: 1 addition & 1 deletion src/pages/AttackPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ export default function AttackPage() {
<div className="max-w-6xl mx-auto">
<AnimatedSection className="mb-8 flex flex-col sm:flex-row sm:items-end justify-between gap-4">
<div>
<h1 className="text-3xl font-bold tracking-tight text-foreground">{t('attack.title')}</h1>
<h1 className="text-3xl font-bold tracking-tight text-foreground">{t('nav.attack')}</h1>
<p className="text-muted-foreground mt-1 transition-colors duration-300">
{t('attack.subtitle')}
</p>
Expand Down
2 changes: 1 addition & 1 deletion src/pages/BrainstormPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ export default function BrainstormPage() {
<div className="max-w-6xl mx-auto">
<AnimatedSection className="mb-8 flex flex-col sm:flex-row sm:items-end justify-between gap-4">
<div>
<h1 className="text-3xl font-bold tracking-tight text-foreground">{t('brainstorm.title')}</h1>
<h1 className="text-3xl font-bold tracking-tight text-foreground">{t('nav.brainstorm')}</h1>
<p className="text-muted-foreground mt-1 transition-colors duration-300">
{t('brainstorm.subtitle')}
</p>
Expand Down
2 changes: 1 addition & 1 deletion src/pages/FactoidsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ export default function FactoidsPage() {
<div className="max-w-6xl mx-auto">
<AnimatedSection className="mb-8 flex flex-col sm:flex-row sm:items-end justify-between gap-4">
<div>
<h1 className="text-3xl font-bold tracking-tight text-foreground">{t('factoids.title')}</h1>
<h1 className="text-3xl font-bold tracking-tight text-foreground">{t('nav.factoids')}</h1>
<p className="text-muted-foreground mt-1 transition-colors duration-300">
{t('factoids.subtitle')}
</p>
Expand Down
42 changes: 39 additions & 3 deletions src/pages/FlowchartPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,16 @@ import {
DialogTitle,
DialogTrigger,
} from '@/components/ui/dialog';
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
} from '@/components/ui/alert-dialog';
import {
Plus,
Download,
Expand Down Expand Up @@ -219,6 +229,7 @@ export default function FlowchartPage() {
const [newNodeType, setNewNodeType] = useState<NodeType>('contention');
const [newNodeStrength, setNewNodeStrength] = useState<'strong' | 'moderate' | 'weak'>('moderate');
const [selectedNode, setSelectedNode] = useState<string | null>(null);
const [isDeleteNodeDialogOpen, setIsDeleteNodeDialogOpen] = useState(false);
const [editingNodeId, setEditingNodeId] = useState<string | null>(null);
const [selectedEdgeType, setSelectedEdgeType] = useState<EdgeKind>('support');

Expand Down Expand Up @@ -300,6 +311,7 @@ export default function FlowchartPage() {
const handleLoadFlowchart = (data: FlowchartData) => {
setNodes(data.nodes);
setEdges(data.edges);
setSelectedNode(null);
setIsLoadDialogOpen(false);
toast.success(t('flowchart.loadedToast'));
};
Expand Down Expand Up @@ -339,7 +351,9 @@ export default function FlowchartPage() {
setNodes((nds) => nds.filter((n) => n.id !== selectedNode));
setEdges((eds) => eds.filter((e) => e.source !== selectedNode && e.target !== selectedNode));
setSelectedNode(null);
}, [selectedNode, setNodes, setEdges]);
setIsDeleteNodeDialogOpen(false);
toast.success(t('flowchart.nodeDeletedToast'));
}, [selectedNode, setNodes, setEdges, t]);

const autoLayout = useCallback(() => {
const lanes: Record<NodeType, number> = {
Expand Down Expand Up @@ -583,7 +597,7 @@ export default function FlowchartPage() {
<GitBranch className="h-4 w-4 mr-2" />
{t('flowchart.edit')}
</Button>
<Button variant="destructive" size="sm" onClick={deleteSelectedNode}>
<Button variant="destructive" size="sm" onClick={() => setIsDeleteNodeDialogOpen(true)}>
<Trash2 className="h-4 w-4 mr-2" />
{t('flowchart.delete')}
</Button>
Expand Down Expand Up @@ -655,7 +669,7 @@ export default function FlowchartPage() {
{t('flowchart.edit')}
</Button>
{selectedNode !== 'thesis' && (
<Button variant="destructive" size="sm" onClick={deleteSelectedNode}>
<Button variant="destructive" size="sm" onClick={() => setIsDeleteNodeDialogOpen(true)}>
<Trash2 className="h-4 w-4 mr-2" />
{t('flowchart.delete')}
</Button>
Expand Down Expand Up @@ -702,6 +716,28 @@ export default function FlowchartPage() {
</CardContent>
</Card>

<AlertDialog open={isDeleteNodeDialogOpen} onOpenChange={setIsDeleteNodeDialogOpen}>
<AlertDialogContent style={{ zIndex: 51 }}>
<AlertDialogHeader>
<AlertDialogTitle>{t('flowchart.deleteNodeTitle')}</AlertDialogTitle>
<AlertDialogDescription>
{t('flowchart.deleteNodeDescription', {
label: nodes.find((node) => node.id === selectedNode)?.data.label,
})}
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>{t('flowchart.deleteNodeCancel')}</AlertDialogCancel>
<AlertDialogAction
onClick={deleteSelectedNode}
className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
>
{t('flowchart.deleteNodeConfirm')}
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>

{/* Legend */}
<Card>
<CardHeader>
Expand Down
2 changes: 1 addition & 1 deletion src/pages/GeneratePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -329,7 +329,7 @@ export default function GeneratePage() {
<div className="max-w-6xl mx-auto">
<AnimatedSection className="mb-8 flex flex-col sm:flex-row sm:items-end justify-between gap-4">
<div>
<h1 className="text-3xl font-bold tracking-tight text-foreground">{t('generate.title')}</h1>
<h1 className="text-3xl font-bold tracking-tight text-foreground">{t('nav.generate')}</h1>
<p className="text-muted-foreground mt-1 transition-colors duration-300">
{t('generate.subtitle')}
</p>
Expand Down
3 changes: 1 addition & 2 deletions src/pages/SourcesPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -151,8 +151,7 @@ export default function SourcesPage() {
className="text-primary underline underline-offset-2 break-words [overflow-wrap:anywhere] hover:text-primary/80 transition-colors"
>
{source.url}
</a>{" "}
[Consulta: 8 de marzo de 2026].
</a>
</li>
))}
</ol>
Expand Down
15 changes: 15 additions & 0 deletions src/test/components/FactoidsResults.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,18 @@ test('renders consulted sources with a fallback label for weak sources', () => {
expect(screen.getByRole('link', { name: /broad fallback article/i })).toHaveAttribute('href', discoveredSources[0].url);
expect(screen.getByText('fallback context only')).toBeInTheDocument();
});

test('names the save action with the factoid claim', () => {
render(<FactoidsResults result={{
...result,
factoids: [{
...result.factoids[0],
shortClaim: 'Solar costs fell by 42 percent',
citation: { ...result.factoids[0].citation, verified: 'verified' },
}],
}} onSaveSource={vi.fn()} />);

expect(screen.getByRole('button', {
name: 'Save Solar costs fell by 42 percent to library',
})).toBeInTheDocument();
});
2 changes: 1 addition & 1 deletion src/test/pages/AttackPage.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ describe('AttackPage Integration', () => {
</BrowserRouter>
);

expect(screen.getByText('Attack My Case')).toBeInTheDocument();
expect(screen.getByRole('heading', { name: 'Attack' })).toBeInTheDocument();
expect(screen.getByTestId('attack-form')).toBeInTheDocument();
expect(screen.queryByTestId('attack-results')).not.toBeInTheDocument();
});
Expand Down
Loading
Loading