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 (

Compare tools

Pick two to five tools. The first table lists documented facts from primary sources; the second is editorial assessment. Nothing here implies measured performance.

{tools.map((t) => s !== t.slug).join(",")}`} rel="nofollow" className="chip chip-accent">{t.name} remove)} {tools.length < 5 && }
{tools.length < 2 ? (

Add at least two tools to compare. Tip: every tool page has a “Compare” button and lists its alternatives.

) : ( <>

Documented facts

{tools.map((t) => )}{facts.map((f) => {tools.map((t) => )})}
Documented facts for {tools.map((t) => t.name).join(", ")}
Fact{t.name}
{f.k}{f.v(t)}

Editorial assessment

{tools.map((t) => )}{editorial.map((e) => {tools.map((t) => )})} {tools.map((t) => )}
Editorial assessment
Aspect{t.name}
{e.k}
    {e.v(t).map((x) =>
  • {x}
  • )}
Summary{t.summary}
)}
); }