import type { Metadata } from "next"; import Link from "next/link"; import { getToolsBySlugs, type ToolDetail } from "@/lib/public/queries"; import { Breadcrumbs } from "@/components/breadcrumbs"; import { label, fmtDate } from "@/lib/utils"; import { CompareSearch } from "@/components/compare-search"; import { X } from "lucide-react"; export const dynamic = "force-dynamic"; export const metadata: Metadata = { title: "Compare tools", description: "Side-by-side comparison of two to five tools: documented facts (license, pricing model, platforms, interfaces) separated from editorial assessment.", robots: { index: false, follow: true } }; export default async function Compare({ searchParams }: { searchParams: Promise<{ tools?: string }> }) { const slugs = ((await searchParams).tools ?? "").split(",").map((s) => s.trim()).filter(Boolean).slice(0, 5); const tools = await getToolsBySlugs(slugs); const ids = tools.map((t) => t.slug); const facts: { k: string; v: (t: ToolDetail) => React.ReactNode }[] = [ { k: "Vendor", v: (t) => t.vendor?.name ?? "—" }, { k: "Product type", v: (t) => label(t.productType) }, { k: "Status", v: (t) => label(t.productStatus) }, { k: "Source model", v: (t) => label(t.sourceModel) }, { k: "License", v: (t) => t.licenseSpdx ?? (t.sourceModel === "proprietary" ? "Proprietary" : "Not verified") }, { k: "Pricing model", v: (t) => t.pricingModel.map(label).join(", ") }, { k: "Runs", v: (t) => t.executionModes.map(label).join(", ") }, { k: "Self-hostable", v: (t) => (t.selfHostable === null ? "Unknown" : t.selfHostable ? "Yes" : "No") }, { k: "Interfaces", v: (t) => t.interfaces.map(label).join(", ") }, { k: "Operating systems", v: (t) => t.operatingSystems.map(label).join(", ") || "—" }, { k: "Languages", v: (t) => t.languages.join(", ") || "—" }, { k: "Data formats", v: (t) => t.dataFormats.join(", ") || "—" }, { k: "Integrations", v: (t) => t.integrations.join(", ") || "—" }, { k: "Sources cited", v: (t) => String(t.sources.length) }, { k: "Links verified", v: (t) => (t.linkState === "ok" ? fmtDate(t.checkedAt) : label(t.linkState)) }, ]; const editorial: { k: string; v: (t: ToolDetail) => string[] }[] = [{ k: "Capabilities", v: (t) => t.capabilities }, { k: "Strengths", v: (t) => t.advantages }, { k: "Limitations", v: (t) => t.limitations }, { k: "Best for", v: (t) => t.audiences }]; return (
Pick two to five tools. The first table lists documented facts from primary sources; the second is editorial assessment. Nothing here implies measured performance.
Add at least two tools to compare. Tip: every tool page has a “Compare” button and lists its alternatives.
) : ( <>| Fact | {tools.map((t) =>{t.name} | )}
|---|---|
| {f.k} | {tools.map((t) =>{f.v(t)} | )}
| Aspect | {tools.map((t) =>{t.name} | )}
|---|---|
| {e.k} | {tools.map((t) =>
| )}
| Summary | {tools.map((t) =>{t.summary} | )}