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 e2e/navigation-performance.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ const privateRoutes = [
['/app/attack', { selector: 'main h1', text: 'Attack' }],
['/app/flowchart', { selector: 'main h1', text: 'Argument Flowchart' }],
['/app/history', { selector: 'main h1', text: 'History' }],
['/app/admin', { selector: 'main h3', text: 'Access Denied' }],
['/app/admin', { selector: 'main h1', text: 'Access Denied' }],
] as const;

const attackContent: ContentMarker = { selector: 'main h1', text: 'Attack' };
Expand Down
4 changes: 2 additions & 2 deletions src/components/LanguageSwitcher.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@ export function LanguageSwitcher() {
<Popover>
<HeaderTooltip label={t('langSwitcher.title', 'Language')}>
<PopoverTrigger asChild>
<Button variant="ghost" size="icon" className="text-muted-foreground hover:text-foreground" aria-label={t('langSwitcher.label', 'Language settings')}>
<Globe className="h-5 w-5" />
<Button variant="ghost" size="icon" className="text-muted-foreground hover:text-foreground" aria-label={t('langSwitcher.label', 'Language')}>
<Globe className="h-5 w-5" aria-hidden="true" />
</Button>
</PopoverTrigger>
</HeaderTooltip>
Expand Down
16 changes: 9 additions & 7 deletions src/components/features/attack/AttackForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -94,17 +94,19 @@ export function AttackForm({ onSubmit, isLoading, importedCase, debateContext =
<ModelSelector feature="attack" showLabel />

<div className="space-y-2">
<Label className="text-sm font-medium">{t('attackForm.options')}</Label>
<Label id="attack-options-label" htmlFor="attack-options-toggle" className="text-sm font-medium">{t('attackForm.options')}</Label>
<Button
id="attack-options-toggle"
type="button"
variant="outline"
className="h-10 w-full justify-between"
aria-labelledby="attack-options-label attack-options-toggle-text"
aria-expanded={optionsOpen}
aria-controls="attack-options"
onClick={() => setOptionsOpen((open) => !open)}
>
<span className="flex items-center gap-2">
<SlidersHorizontal className="h-4 w-4" />
<span id="attack-options-toggle-text" className="flex items-center gap-2">
<SlidersHorizontal className="h-4 w-4" aria-hidden="true" />
{t('attackForm.moreOptions')}
</span>
<ChevronDown className={`h-4 w-4 transition-transform duration-150 ease-out-strong ${optionsOpen ? 'rotate-180' : ''}`} />
Expand All @@ -123,12 +125,12 @@ export function AttackForm({ onSubmit, isLoading, importedCase, debateContext =
<div className="grid gap-4 sm:grid-cols-2">
{/* Aggressiveness */}
<div className="space-y-2">
<Label className="text-sm font-medium">{t('attackForm.intensity')}</Label>
<Label id="attack-intensity-label" htmlFor="attack-intensity" className="text-sm font-medium">{t('attackForm.intensity')}</Label>
<Select
value={currentValues.aggressiveness}
onValueChange={(value) => setValue('aggressiveness', value as AttackInput['aggressiveness'])}
>
<SelectTrigger>
<SelectTrigger id="attack-intensity" aria-labelledby="attack-intensity-label">
<SelectValue />
</SelectTrigger>
<SelectContent>
Expand All @@ -141,12 +143,12 @@ export function AttackForm({ onSubmit, isLoading, importedCase, debateContext =

{/* Judge Paradigm */}
<div className="space-y-2">
<Label className="text-sm font-medium">{t('attackForm.paradigm')}</Label>
<Label id="attack-paradigm-label" htmlFor="attack-paradigm" className="text-sm font-medium">{t('attackForm.paradigm')}</Label>
<Select
value={currentValues.judgeParadigm}
onValueChange={(value) => setValue('judgeParadigm', value as AttackInput['judgeParadigm'])}
>
<SelectTrigger>
<SelectTrigger id="attack-paradigm" aria-labelledby="attack-paradigm-label">
<SelectValue />
</SelectTrigger>
<SelectContent>
Expand Down
10 changes: 5 additions & 5 deletions src/components/features/attack/AttackResults.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ function WeaknessCard({ weakness }: { weakness: Weakness }) {
{t(`attackResults.types.${weakness.type}`, weakness.type)}
</Badge>
</div>
<h4 className="mt-1 font-medium text-foreground">{weakness.description}</h4>
<h3 className="mt-1 font-medium text-foreground">{weakness.description}</h3>
</div>
</div>
</div>
Expand Down Expand Up @@ -221,7 +221,7 @@ export function AttackResults({ result, discoveredSources, onDiscuss, onPractice
</div>

<div>
<h4 className="text-sm font-medium text-muted-foreground mb-2">{t('attackResults.strengths')}</h4>
<h3 className="text-sm font-medium text-muted-foreground mb-2">{t('attackResults.strengths')}</h3>
<ul className="space-y-1">
{strengthAreas.map((strength, i) => (
<li key={i} className="flex items-baseline gap-2 text-sm">
Expand Down Expand Up @@ -303,12 +303,12 @@ export function AttackResults({ result, discoveredSources, onDiscuss, onPractice
>
<div className="space-y-4">
<div>
<h4 className="text-sm font-medium text-muted-foreground mb-1">{t('attackResults.currentWeighing')}</h4>
<h3 className="text-sm font-medium text-muted-foreground mb-1">{t('attackResults.currentWeighing')}</h3>
<p className="text-foreground">{weighingAnalysis.currentWeighing}</p>
</div>
<Separator />
<div>
<h4 className="text-sm font-medium text-muted-foreground mb-2">{t('attackResults.missingComparisons')}</h4>
<h3 className="text-sm font-medium text-muted-foreground mb-2">{t('attackResults.missingComparisons')}</h3>
<ul className="space-y-1">
{(weighingAnalysis.missingComparisons ?? []).map((comparison, i) => (
<li key={i} className="flex items-start gap-2 text-sm">
Expand All @@ -320,7 +320,7 @@ export function AttackResults({ result, discoveredSources, onDiscuss, onPractice
</div>
<Separator />
<div>
<h4 className="text-sm font-medium text-muted-foreground mb-2">{t('attackResults.improvements')}</h4>
<h3 className="text-sm font-medium text-muted-foreground mb-2">{t('attackResults.improvements')}</h3>
<ul className="space-y-1">
{(weighingAnalysis.improvementSuggestions ?? []).map((suggestion, i) => (
<li key={i} className="flex items-start gap-2 text-sm">
Expand Down
6 changes: 3 additions & 3 deletions src/components/features/brainstorm/BrainstormResults.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ function IdeaCard({ idea }: { idea: Idea }) {
return (
<div className={cn('rounded-lg border p-4 space-y-3 motion-list-item', strengthColors[strength] || strengthColors.moderate)}>
<div className="flex items-start justify-between gap-2">
<h4 className="font-semibold text-foreground">{idea.title || t('brainstormResults.untitled')}</h4>
<h3 className="font-semibold text-foreground">{idea.title || t('brainstormResults.untitled')}</h3>
<div className="flex gap-1 shrink-0">
<Badge className={cn('text-xs', sideColors[side] || sideColors.neutral)}>
{side === 'pro' ? t('brainstormResults.pro') : side === 'con' ? t('brainstormResults.con') : t('brainstormResults.neutral')}
Expand Down Expand Up @@ -203,10 +203,10 @@ export function BrainstormResults({ result, discoveredSources, onDiscuss }: Brai
<div className="space-y-6">
{result.mindmap.map((category, i) => (
<div key={i}>
<h4 className="text-lg font-semibold text-foreground mb-3 flex items-center gap-2">
<h3 className="text-lg font-semibold text-foreground mb-3 flex items-center gap-2">
<div className="h-2 w-2 rounded-full bg-primary" />
{category.category}
</h4>
</h3>
<div className="grid gap-3 sm:grid-cols-2">
{category.items && Array.isArray(category.items) && category.items.map((idea) => (
<IdeaCard key={idea.id} idea={idea} />
Expand Down
6 changes: 3 additions & 3 deletions src/components/features/factoids/FactoidsResults.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,13 +54,13 @@ function FactoidCard({ factoid, onSave }: { factoid: Factoid, onSave?: FactoidsR
rel="noopener noreferrer"
className="group flex items-center gap-2"
>
<h4 className="font-semibold text-foreground group-hover:text-primary transition-colors underline-offset-4 group-hover:underline text-left">
<h3 className="font-semibold text-foreground group-hover:text-primary transition-colors underline-offset-4 group-hover:underline text-left">
{factoid.shortClaim}
</h4>
</h3>
<ExternalLink className="h-3 w-3 shrink-0 opacity-60 transition-opacity group-hover:opacity-100 group-focus-visible:opacity-100" aria-hidden="true" />
</a>
) : (
<h4 className="font-semibold text-foreground">{factoid.shortClaim}</h4>
<h3 className="font-semibold text-foreground">{factoid.shortClaim}</h3>
)}
<div className="flex items-center gap-2 mt-1">
<Badge className={cn('text-xs', config.bg, config.color)}>
Expand Down
26 changes: 19 additions & 7 deletions src/components/features/generate/GenerateForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,15 +33,17 @@ interface GenerateFormProps {
debatePrefill?: boolean;
}

function OptionSelect<V extends string>({ value, onValueChange, options, triggerClassName }: {
function OptionSelect<V extends string>({ id, labelId, value, onValueChange, options, triggerClassName }: {
id: string;
labelId: string;
value: V;
onValueChange: (value: V) => void;
options: { value: V; label: string }[];
triggerClassName?: string;
}) {
return (
<Select value={value} onValueChange={(next) => onValueChange(next as V)}>
<SelectTrigger className={triggerClassName}>
<SelectTrigger id={id} aria-labelledby={labelId} className={triggerClassName}>
<SelectValue />
</SelectTrigger>
<SelectContent>
Expand Down Expand Up @@ -157,8 +159,10 @@ export function GenerateForm({ onSubmit, isLoading, debatePrefill = true }: Gene
<div className="grid gap-4 sm:grid-cols-2">
{/* Side */}
<div className="space-y-2">
<Label className="text-sm font-medium">{t('generateForm.side')}</Label>
<Label id="generate-side-label" htmlFor="generate-side" className="text-sm font-medium">{t('generateForm.side')}</Label>
<OptionSelect
id="generate-side"
labelId="generate-side-label"
value={currentValues.side}
onValueChange={(value) => setValue('side', value)}
options={[
Expand All @@ -170,8 +174,10 @@ export function GenerateForm({ onSubmit, isLoading, debatePrefill = true }: Gene

{/* Role */}
<div className="space-y-2">
<Label className="text-sm font-medium">{t('generateForm.role')}</Label>
<Label id="generate-role-label" htmlFor="generate-role" className="text-sm font-medium">{t('generateForm.role')}</Label>
<OptionSelect
id="generate-role"
labelId="generate-role-label"
value={currentValues.role}
onValueChange={(value) => setValue('role', value)}
options={[
Expand Down Expand Up @@ -215,8 +221,10 @@ export function GenerateForm({ onSubmit, isLoading, debatePrefill = true }: Gene
<div className="grid gap-4 pt-1 sm:grid-cols-3">
{/* Length */}
<div className="space-y-2 min-w-0">
<Label className="text-sm font-medium truncate block">{t('generateForm.length')}</Label>
<Label id="generate-length-label" htmlFor="generate-length" className="text-sm font-medium truncate block">{t('generateForm.length')}</Label>
<OptionSelect
id="generate-length"
labelId="generate-length-label"
value={currentValues.length}
onValueChange={(value) => setValue('length', value)}
triggerClassName="min-w-0"
Expand All @@ -230,8 +238,10 @@ export function GenerateForm({ onSubmit, isLoading, debatePrefill = true }: Gene

{/* Audience */}
<div className="space-y-2 min-w-0">
<Label className="text-sm font-medium truncate block">{t('generateForm.audience')}</Label>
<Label id="generate-audience-label" htmlFor="generate-audience" className="text-sm font-medium truncate block">{t('generateForm.audience')}</Label>
<OptionSelect
id="generate-audience"
labelId="generate-audience-label"
value={currentValues.audience}
onValueChange={(value) => setValue('audience', value)}
triggerClassName="min-w-0"
Expand All @@ -245,8 +255,10 @@ export function GenerateForm({ onSubmit, isLoading, debatePrefill = true }: Gene

{/* Tone */}
<div className="space-y-2 min-w-0">
<Label className="text-sm font-medium truncate block">{t('generateForm.tone')}</Label>
<Label id="generate-tone-label" htmlFor="generate-tone" className="text-sm font-medium truncate block">{t('generateForm.tone')}</Label>
<OptionSelect
id="generate-tone"
labelId="generate-tone-label"
value={currentValues.tone}
onValueChange={(value) => setValue('tone', value)}
triggerClassName="min-w-0"
Expand Down
24 changes: 12 additions & 12 deletions src/components/features/generate/GenerateResults.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,10 @@ function ArgumentCard({ contention, index, unverified }: { contention: Contentio
return (
<div className="rounded-lg border border-border bg-card p-4 space-y-3 motion-list-item">
<div className="flex items-start justify-between gap-2">
<h4 className="font-semibold text-foreground">
<h3 className="font-semibold text-foreground">
<span className="text-primary mr-2">#{index + 1}</span>
{contention.claim}
</h4>
</h3>
{contention.needsSource && (
<Badge variant="outline" className="shrink-0 border-amber-500 text-amber-600">
<AlertTriangle className="mr-1 h-3 w-3" />
Expand Down Expand Up @@ -173,7 +173,7 @@ export function GenerateResults({ result, discoveredSources, onRegenerateSection
if (!result?.caseStructure || !result?.contentions || !result?.predictedOpponentCase || !result?.counters) {
return (
<div className="rounded-lg border border-destructive/50 bg-destructive/5 p-6 text-center">
<h3 className="text-lg font-semibold text-destructive mb-2">{t('generateResults.incomplete')}</h3>
<h2 className="text-lg font-semibold text-destructive mb-2">{t('generateResults.incomplete')}</h2>
<p className="text-sm text-muted-foreground">
{t('generateResults.incompleteDesc')}
</p>
Expand Down Expand Up @@ -265,12 +265,12 @@ export function GenerateResults({ result, discoveredSources, onRegenerateSection
>
<div className="space-y-4">
<div>
<h4 className="text-sm font-medium text-muted-foreground mb-1">{t('generateResults.hook')}</h4>
<h3 className="text-sm font-medium text-muted-foreground mb-1">{t('generateResults.hook')}</h3>
<p className="text-foreground italic">{result.exordio.hook}</p>
</div>
<Separator />
<div>
<h4 className="text-sm font-medium text-muted-foreground mb-1">{t('generateResults.transition')}</h4>
<h3 className="text-sm font-medium text-muted-foreground mb-1">{t('generateResults.transition')}</h3>
<p className="text-foreground">{result.exordio.transition}</p>
</div>
</div>
Expand All @@ -287,21 +287,21 @@ export function GenerateResults({ result, discoveredSources, onRegenerateSection
>
<div className="space-y-4">
<div>
<h4 className="text-sm font-medium text-muted-foreground mb-1">{t('generateResults.thesis')}</h4>
<h3 className="text-sm font-medium text-muted-foreground mb-1">{t('generateResults.thesis')}</h3>
<Markdown>{result.caseStructure.thesis}</Markdown>
</div>
<Separator />
<div>
<h4 className="text-sm font-medium text-muted-foreground mb-1">{t('generateResults.framework')}</h4>
<h3 className="text-sm font-medium text-muted-foreground mb-1">{t('generateResults.framework')}</h3>
<Markdown>{result.caseStructure.framework}</Markdown>
</div>
<Separator />
<div>
<h4 className="text-sm font-medium text-muted-foreground mb-1">{t('generateResults.burdenOfProof')}</h4>
<h3 className="text-sm font-medium text-muted-foreground mb-1">{t('generateResults.burdenOfProof')}</h3>
<Markdown>{result.caseStructure.burdenOfProof}</Markdown>
</div>
<div>
<h4 className="text-sm font-medium text-muted-foreground mb-2">{t('generateResults.keyDefinitions')}</h4>
<h3 className="text-sm font-medium text-muted-foreground mb-2">{t('generateResults.keyDefinitions')}</h3>
<ul className="list-disc list-inside space-y-1 text-sm text-foreground">
{result.caseStructure.keyDefinitions?.map((def, i) => (
<li key={i}>{def}</li>
Expand Down Expand Up @@ -336,7 +336,7 @@ export function GenerateResults({ result, discoveredSources, onRegenerateSection
>
<div className="space-y-4">
<div>
<h4 className="text-sm font-medium text-muted-foreground mb-2">{t('generateResults.likelyArguments')}</h4>
<h3 className="text-sm font-medium text-muted-foreground mb-2">{t('generateResults.likelyArguments')}</h3>
<ul className="space-y-2">
{result.predictedOpponentCase?.likelyArguments?.map((arg, i) => (
<li key={i} className="flex items-start gap-2 text-sm">
Expand All @@ -348,7 +348,7 @@ export function GenerateResults({ result, discoveredSources, onRegenerateSection
</div>
<Separator />
<div>
<h4 className="text-sm font-medium text-muted-foreground mb-2">{t('generateResults.strategicWeaknesses')}</h4>
<h3 className="text-sm font-medium text-muted-foreground mb-2">{t('generateResults.strategicWeaknesses')}</h3>
<ul className="space-y-2">
{result.predictedOpponentCase?.strategicWeaknesses?.map((weakness, i) => (
<li key={i} className="flex items-start gap-2 text-sm">
Expand All @@ -360,7 +360,7 @@ export function GenerateResults({ result, discoveredSources, onRegenerateSection
</div>
<Separator />
<div>
<h4 className="text-sm font-medium text-muted-foreground mb-2">{t('generateResults.keyClashes')}</h4>
<h3 className="text-sm font-medium text-muted-foreground mb-2">{t('generateResults.keyClashes')}</h3>
<div className="flex flex-wrap gap-2">
{result.predictedOpponentCase?.keyClashes?.map((clash, i) => (
<Badge key={i} variant="outline">{clash}</Badge>
Expand Down
Loading
Loading