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
8 changes: 6 additions & 2 deletions docs/PROJECT-REVIEW-2026-07-18.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,9 @@ Baseline captured on 2026-07-18:
## P1 — Product Experience

- [x] Stop statically generating every possible two-model comparison pair.
- [ ] Unify `/models/comparison` and `/models/compare/...` into one understandable comparison
- [x] Unify `/models/comparison` and `/models/compare/...` into one understandable comparison
journey.
- [ ] Add “compare” actions to cards and detail pages with persistent selected items.
- [x] Add “compare” actions to cards and detail pages with persistent selected items.
- [x] Search localized names/descriptions, capabilities, modalities, platforms, vendors, and types.
- [ ] Add explicit use-case metadata and include it in search when the schema supports it.
- [ ] Add guided selection filters: interface, budget, model freedom, privacy/local execution,
Expand Down Expand Up @@ -167,3 +167,7 @@ evidence; completed items are reflected in the checklists and implementation log
providers and representative GPT-5.2, Claude Haiku 4.5, and Gemini 3 Flash model records.
- Corrected GPT-5.2 from `latest` to `maintained` after its official model page identified it as a
previous frontier model that remains available.
- Made `/models/compare` the canonical model comparison journey, retained the old all-model URL as
a permanent redirect, and removed its duplicate comparison implementation.
- Added a persistent two-model selection shared by model cards, detail-page comparison actions,
canonical pair URLs, and the comparison selector; adding a third model replaces the oldest pick.
40 changes: 23 additions & 17 deletions src/app/[locale]/models/compare/[models]/page.client.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@

import { ExternalLink } from 'lucide-react'
import { useTranslations } from 'next-intl'
import { Fragment, useEffect, useState } from 'react'
import { Fragment, useEffect, useRef } from 'react'
import { useModelComparison } from '@/components/controls/useModelComparison'
import type { Locale } from '@/i18n/config'
import { Link, useRouter } from '@/i18n/navigation'
import { providersData } from '@/lib/generated'
import { buildModelComparisonPath } from '@/lib/model-comparison'
import type {
ManifestBenchmarks,
ManifestModel,
Expand Down Expand Up @@ -180,10 +182,17 @@ export default function ComparePageClient({
const router = useRouter()
const tPage = useTranslations('pages.modelCompare')
const tShared = useTranslations('shared')
const [selectedModel1, setSelectedModel1] = useState<string>(initialModels[0]?.id ?? '')
const [selectedModel2, setSelectedModel2] = useState<string>(initialModels[1]?.id ?? '')
const [prevModel1, setPrevModel1] = useState<string>(initialModels[0]?.id ?? '')
const [prevModel2, setPrevModel2] = useState<string>(initialModels[1]?.id ?? '')
const initialModelIds = initialModels.map(model => model.id)
const { selectedIds, isHydrated, setSelection } = useModelComparison(initialModelIds)
const selectedModel1 = selectedIds[0] ?? ''
const selectedModel2 = selectedIds[1] ?? ''
const lastComparisonPath = useRef(buildModelComparisonPath(initialModelIds))

const updateModelSlot = (slotIndex: number, value: string) => {
const nextSelection = [selectedModel1, selectedModel2]
nextSelection[slotIndex] = value
setSelection(nextSelection.filter(Boolean))
}

const findProviderId = (vendor: string): string | null => {
const normalizedVendor = vendor.toLocaleLowerCase()
Expand Down Expand Up @@ -471,17 +480,14 @@ export default function ComparePageClient({
)
}

// Update URL when both models are selected and at least one has changed
// Keep the shareable URL aligned with the persisted selection.
useEffect(() => {
if (selectedModel1 && selectedModel2 && selectedModel1 !== selectedModel2) {
if (selectedModel1 !== prevModel1 || selectedModel2 !== prevModel2) {
const url = `/models/compare/${selectedModel1}-vs-${selectedModel2}`
router.push(url)
setPrevModel1(selectedModel1)
setPrevModel2(selectedModel2)
}
}
}, [selectedModel1, selectedModel2, prevModel1, prevModel2, router])
if (!isHydrated) return
const nextPath = buildModelComparisonPath(selectedIds)
if (nextPath === lastComparisonPath.current) return
lastComparisonPath.current = nextPath
router.replace(nextPath)
}, [isHydrated, router, selectedIds])

const model1 = allModels.find(m => m.id === selectedModel1)
const model2 = allModels.find(m => m.id === selectedModel2)
Expand All @@ -498,7 +504,7 @@ export default function ComparePageClient({
<div className="flex flex-col gap-2 items-center">
<ModelSelect
value={selectedModel1}
onChange={setSelectedModel1}
onChange={value => updateModelSlot(0, value)}
availableModels={getAvailableModelsForSlot(0)}
/>
</div>
Expand All @@ -507,7 +513,7 @@ export default function ComparePageClient({
<div className="flex flex-col gap-2 items-center">
<ModelSelect
value={selectedModel2}
onChange={setSelectedModel2}
onChange={value => updateModelSlot(1, value)}
availableModels={getAvailableModelsForSlot(1)}
/>
</div>
Expand Down
Loading
Loading