import Link from "next/link"; import { enrolmentLabel } from "@/lib/enrolment"; import { publicTags, tagRoute } from "@/lib/tags"; import { Fragment, type ReactNode } from "react"; import { EVIDENCE_TIER_LABEL, TARGET_ROLE_LABEL, type Entity, type Roadmap, type Term } from "@/lib/schema"; import { KIND_META, KINDS, phaseLabel, routeFor, type Kind } from "@/lib/kinds"; import { PRODUCT_CLASS_META } from "@/lib/product-class"; import { graph } from "@/lib/graph"; import { Bullets, ChipList, Container, PageHeader, StatusChip } from "./ui"; import { Block, Field, KeyPapers, keyPapersFor, LatestLiterature, Refs, Summary, ToolsStrip } from "./record-blocks"; import { PrevalenceTable } from "./PrevalenceTable"; import { WhatIsBeingDone } from "./WhatIsBeingDone"; import { regimensFor, regimenRoute, cycleSummary } from "@/lib/regimens"; import { interventionQuery } from "@/lib/ctgov"; import { CitationChip, CitationChipDefs } from "./CitationChip"; import { OrganSchematic } from "./OrganSchematic"; import { cancerTabs } from "./CancerRecord"; import { NEIGHBOUR_CAP, forwardedAnchors } from "@/lib/record-sections"; import { Neighbours, productGroups } from "./Neighbours"; import { PathwayDiagram } from "./PathwayDiagram"; import { rankInstitutions } from "@/lib/ranking"; import { MoleculeViewer, type StructureEntry } from "./MoleculeViewer"; import { Logo } from "./Logo"; import { Portrait, PortraitCredit } from "./Portrait"; import { JsonLd } from "./JsonLd"; import { MachineLinks } from "./MachineLinks"; import { AkaLine } from "./AkaLine"; import { CheckpointPills } from "./CheckpointPills"; import { ModalityPills } from "./ModalityPills"; import { PrintButton } from "./PrintButton"; import { TrialFinderGeo as TrialFinder } from "./TrialFinderGeo"; import { TrialCounts } from "./TrialCounts"; import { hasReviewCard, ReviewBadge, ReviewNote } from "./ReviewBadge"; import { TechSchematic } from "./TechSchematic"; import { Wireframe3D } from "./Wireframe3D"; import { StickyAside } from "./StickyAside"; import { schematicFor } from "@/data/schematics"; import { Tabs, type Tab } from "./Tabs"; import { ScrollRow } from "./ScrollRow"; import { RoadmapStory } from "./RoadmapStory"; import { TrialOutcomes } from "./Pictogram"; import { TrialParticipation } from "./TrialParticipation"; import { TrialExplainer } from "./TrialExplainer"; import { EvidenceBar } from "./EvidenceBar"; import { EvidenceGradeChip } from "./EvidenceGradeChip"; import { SourceList } from "./SourceList"; import { SuggestEdit } from "./SuggestEdit"; import { sourceLocation } from "@/lib/source-location"; import { ProvenanceLine } from "./ProvenanceLine"; import { ConfidenceChip, ConfidenceLegend } from "./ConfidenceChip"; import { ProcessSchematic, processSchematicKey } from "./ProcessSchematic"; import { DosingCard } from "./DosingCard"; import { ToxicityTable } from "./ToxicityTable"; import { RecoveryPanel } from "./RecoveryPanel"; import { cellsForDrug as recoveryCellsForDrug } from "@/data/recovery-matrix"; import { AccessTable } from "./AccessTable"; import { RegulatoryTimeline } from "./RegulatoryTimeline"; import { SeeItInAction } from "./SeeItInAction"; import { modalityGroup } from "@/lib/modality-group"; import { TldrText } from "./TldrText"; import { FrontSchematic } from "./FrontSchematic"; import { TermSchematic } from "./TermSchematic"; import { termVisual } from "@/lib/term-visual"; import { DrugGrid } from "./DrugCard"; import type { Drug, Paper } from "@/lib/schema"; import { TargetSpecificityPills } from "./TargetSpecificityPills"; import { SupportivePill } from "./SupportivePill"; import { CompanyTypePill } from "./CompanyTypePill"; import { TargetWhereFound, hpaFor } from "./TargetWhereFound"; import { KindName, ProductClassName, TL } from "./T"; import { KIND_COLOR, statusClass } from "@/lib/text"; import { withTermHovers } from "@/lib/term-hover"; import { paperQuery } from "@/lib/europepmc"; import { roadmapStorySteps } from "@/lib/roadmap-story"; import { defaultOpen, eraBody, eraCounts, roadmapFile, WATCH_PAGE, watchRows } from "@/lib/roadmap-eras"; import { RoadmapEra, RoadmapWatchMore, WatchRowView } from "./RoadmapEras"; import structureIndex from "../../public/structures/index.json"; import { RegionStrip } from "./RegionMatrix"; import { RegistryCheck } from "./RegistryCheck"; import { regionalApprovals } from "@/data/regional-approvals"; import { CancerIcon } from "./CancerIcon"; import { YearEvents, YearNav, YearStats } from "./YearRecord"; import { ResearchOutput } from "./ResearchOutput"; import { confidence } from "@/data/confidence"; import { FrontIcon } from "./FrontIcon"; import { ApprovalChip } from "./ApprovalChip"; import { approvedYearOf } from "@/lib/approved-year"; import { MechanicsPills } from "./MechanicsPills"; import { TargetSchematic } from "./TargetSchematic"; import { TargetExplainer } from "./TargetExplainer"; import { CoverageUsCard } from "./CoverageUs"; import { CoverageUkCard } from "./CoverageUk"; import { coverageUs } from "@/data/coverage-us"; import { coverageUk } from "@/data/coverage-uk"; import { similarLinks } from "@/lib/similar"; import { agentById } from "@/lib/interactions"; import { IdentifierRow, XrefStrip } from "./XrefStrip"; import { nameAttrs } from "@/lib/translate"; import { HotspotPlot } from "./HotspotPlot"; import { OpenMedicalPanel } from "./OpenMedicalPanel"; import { OpenSourcePanel } from "./OpenSourcePanel"; import { SoftwarePanel } from "./SoftwarePanel"; import { hotspotsFor } from "@/data/hotspots"; import { questionsFor } from "@/data/open-questions"; import { assaysForTarget, assaysForDrug } from "@/data/assays"; import { modelFor, datasetFor } from "@/data/model-registry"; import { modelsFor } from "@/data/preclinical-models"; import { CatalystsPanel, CompanyScorePanel, DealsPanel, ExclusivityPanel, ManufacturingPanel } from "@/components/InvestorPanels"; import { FundingPanel, PortfolioPanel } from "@/components/StartupPanels"; import { StageIcon } from "@/components/StageIcon"; import { portfolioOf, STAGE_LABEL, stageOf, ycBatchLabel } from "@/lib/startups"; import { BiomarkerDetail, ReadoutStrip, ReadoutsForDrug, ReadoutsForTerm } from "./BiomarkerDetail"; const STRUCTURES = structureIndex as Record; /** PDB structures of drugs caught in the act on this target (antibody with its antigen, small molecule in the pocket), from the products that hit it. Representative IgG stand-ins are left out. */ function structuresForTarget(targetId: string): StructureEntry[] { const g = graph(); const seen = new Set(); const out: StructureEntry[] = []; for (const d of g.incoming(targetId).get("drug") ?? []) for (const en of STRUCTURES[d.id] ?? []) { if (en.source !== "pdb" || seen.has(en.file) || !/bound to|with /i.test(en.label) || /representative/i.test(en.label)) continue; seen.add(en.file); out.push(en); } return out.slice(0, 6); } /** Labels and block titles are English source strings, translated on the client through the chrome dictionary (`TL`). */ /** A Wayback Machine snapshot recorded as the website is shown as an archived copy of the original address. */ function websiteView(url: string): { label: string; text: string } { const m = url.match(/^https?:\/\/web\.archive\.org\/web\/\d+\/(https?:\/\/.+)$/); if (m) return { label: "Archived website", text: m[1].replace(/^https?:\/\//, "") }; if (/^https?:\/\/(www\.)?ycombinator\.com\/companies\//.test(url)) return { label: "Y Combinator profile", text: url.replace(/^https?:\/\//, "") }; return { label: "Website", text: url.replace(/^https?:\/\//, "") }; } /** Fetcher notes on a person's papers, rendered once under the table in plain English rather than under every row. */ const PAPER_PROVENANCE: Record = { "OpenAlex author record matched by institution": "These papers come from the OpenAlex author record for this person at their institution, taking the most cited articles with a DOI.", "Europe PMC author record matched by affiliation": "These papers come from a Europe PMC search on the author's name and institution, taking the most cited articles.", }; function paperProvenance(papers: ReadonlyArray<{ note?: string }>): string[] { return [...new Set(papers.map((p) => (p.note && PAPER_PROVENANCE[p.note]) || "").filter(Boolean))]; } export function EntityDetail({ e }: { e: Entity }) { const g = graph(); const neighbours = g.neighbours(e.id); const nCon = [...neighbours.values()].reduce((a, l) => a + l.length, 0); // Each tab's content is one keyed fragment. React Flight collapses a keyless fragment into a plain array as it // crosses to the client, and Tabs would then render that array as a list without keys (the warning on every record // page); a fragment keyed on the tab id crosses intact, so the section has one child. const tabs: Tab[] = keyedContent([ ...kindTabs(e), // Cancer records carry notes, key papers and the literature inside their Evidence and Data sections (CancerRecord.tsx). ...(e.kind === "cancer" ? [] : [ ...(e.notes.length ? [{ id: "notes", label: "Notes", content: withTermHovers(t, { skipId: e.id })} /> }] : []), ...keyPapersTab(e), ...papersTab(e), ]), // Cancer pages carry "Related pages" (every direct link plus the products' targets, companies and technologies, with // the Similar strip) instead: the generic Connected tab repeated the same lists and doubled the markup of the page. ...(e.kind === "cancer" ? [] : [{ id: "connected", label: "Connected", count: nCon, content: }]), ]); const aside = ; const openMedical = (e.kind === "section" || e.kind === "technology") ? : null; const openSource = (e.kind === "technology" || e.kind === "collection" || e.kind === "institution" || e.kind === "company") ? : null; const software = (e.kind === "technology" || e.kind === "company" || e.kind === "institution" || e.kind === "cancer") ? : null; const afterTabs = (openSource || openMedical || software) ? <>{openSource}{software}{openMedical} : null; return ( <> Technologies > In vivo CAR-T, so the kicker repeated the word "Technologies" and then a chip repeated it again as "Technology", and the status pill sat above the title of the thing it describes. Owner, 6 October 2026. Checked before removing: EntityDetail is rendered from exactly one route, src/app/[kind]/[id]/page.tsx, which always renders Breadcrumbs immediately above it, and the breadcrumb is not hidden at any width. */ title={e.name} titleAttrs={nameAttrs(e.kind)} ledeNode={} logo={e.kind === "cancer" ? : e.kind === "section" ? : "website" in e ? : "url" in e && (e.kind === "collection" || e.kind === "journal") ? : undefined} under={
{e.kind === "drug" ? : }
} /> {(e.kind === "target" || e.kind === "pathway") && } {e.kind === "drug" && e.supportive && } {e.kind !== "cancer" && } {(e.kind === "target" || e.kind === "pathway" || e.kind === "term" || e.kind === "technology") && } {(e.kind === "technology" || e.kind === "drug") && } {/* The tab bar takes the full content width and both columns start beneath it (Tabs owns the grid), so the right column never cuts the tabs short. Pages with one section keep the plain grid. */} {tabs.length > 1 ? :
{tabs.map((t) => {t.content})}
{afterTabs}
{aside}
}
{/* Who last edited this and when. It opened the right-hand column until 6 October 2026; the owner: "not useful surface area, this should goto the bottom of the page". It is provenance, which a reader wants after the page rather than before it. */} ); } /** Wraps each tab's content in a fragment keyed on the tab id (see the note where the tabs are assembled). */ function keyedContent(tabs: Tab[]): Tab[] { return tabs.map((t) => ({ ...t, content: {t.content} })); } /** * Record kinds where the patient pack earns the control in the Data row: the pages a patient or a carer takes to * an appointment, where "every section in plain language, dated, with a QR code back to the page and the * disclaimer" is something a browser cannot produce. On a company, journal, person or paper page the three modes * collapse into the browser's own print command, so the control is not shown there (see the note in PrintButton). */ const PACK_KINDS: ReadonlySet = new Set(["cancer", "drug", "trial", "term"]); /** * The right-hand column of a record page: evidence, Wikipedia and tags, the patient pack, suggest an edit, * quick links, and last of all the review panel. * * Provenance, the source count and the JSON link all left it on 6 October 2026. Each was a pointer to * something that now lives at the foot of the page, and a pointer above the fold costs more attention than the * thing it points at is worth. * * The sources are no longer here. `e.links` rendered as one unbounded list, 124 of them on pancreatic cancer * and 109 on triple-negative breast cancer, so the column was worst on the pages with the most work in them * and on a phone it stacked above the reader's own content. They sit at the foot of the page now, grouped by * publisher (`SourceList`), and the column keeps one line saying how many there are and where. Wikipedia * stays: it identifies the record rather than citing it. * * The review panel used to sit second, above everything a reader came for. It is the page talking about itself: * machine commentary on 21 records, and on the other ~19,000 a card saying nobody has reviewed the page yet. * Neither is what a reader opens a cancer page for, so it goes at the foot of the column, after the actions. * `src/app/record-top.test.ts` keeps it there. */ export function RecordAside({ e }: { e: Entity }) { return ( {e.kind === "person" && } {e.kind === "technology" && e.tags.some((t) => t.startsWith("evidence:")) &&
Evidence grade

How much and what kind of evidence, for the stated purpose. Grades are explained on the complementary approaches page.

} {(e.kind === "drug" || e.kind === "technology" || e.kind === "target" || e.kind === "trial") && } {/* A roadmap has no Wikipedia article and no public tags, so this card drew an empty box above the actions on every one of them. It renders only when it has something in it. */} {(e.wikipedia || (e.kind === "term" && e.wikipediaChecked) || publicTags(e.tags).length > 0) &&
{e.wikipedia && } {!e.wikipedia && e.kind === "term" && e.wikipediaChecked &&

} {publicTags(e.tags).length > 0 &&
{publicTags(e.tags).map((t) => {t})}
}
} {PACK_KINDS.has(e.kind) &&
} {e.kind !== "cancer" && } {/* Only when there is something to show: a model panel or a named reviewer. An unreviewed page says so with a chip at the foot of the page instead (ReviewNote). */} {hasReviewCard(e.id) &&
}
); } /** * The sidebar's way into the Connected section: each kind it holds, with how many, as one link to it. Until * 19 October 2026 this card reprinted up to eight names per kind, and on the record page measured ten of ten of * them were already in Connected under a second vocabulary ("Products" for drugs); a roadmap with fourteen eras * carried 19 KB of duplicate links. Counts and kind names keep the navigation without saying anything twice, and * the names come from the same KindName labels Connected uses (docs/INFORMATION-ARCHITECTURE.md, items 1 and 2). */ function QuickLinks({ e }: { e: Entity }) { const groups = graph().neighbours(e.id); const order = KINDS.filter((k) => (groups.get(k)?.length ?? 0) > 0); if (!order.length) return null; // Plain anchors, as the tabs are: a router link to "#connected" made the router prefetch the page the reader is // already on, 30 to 70 KB on every record page viewed on a wide screen. return ( ); } /** Key papers in the corpus that cite this object, with what they mean in plain English (record-blocks.tsx renders the cards). */ function keyPapersTab(e: Entity): Tab[] { const all = keyPapersFor(e); if (!all.length) return []; return [{ id: "key-papers", label: "Key papers", count: all.length, content: }]; } /** Live literature from Europe PMC plus the weekly trend, for the kinds a query can be built for. */ function papersTab(e: Entity): Tab[] { if (!["drug", "target", "technology"].includes(e.kind) || !paperQuery(e)) return []; return [{ id: "papers", label: "Latest papers", content: }]; } /** Per-kind tabs. The first tab is always "Overview" and contains the summary. */ function kindTabs(e: Entity): Tab[] { const g = graph(); const overview = (extra?: ReactNode): Tab => ({ id: "overview", label: "Overview", content: <>{extra} }); switch (e.kind) { case "cancer": return cancerTabs(e); case "technology": return [ overview(<>
{modelFor(e.id) && (() => { const m = modelFor(e.id)!; return (
Model registry
Modality: {m.modality}
{m.parametersM &&
Parameters: {m.parametersM >= 1000 ? `${m.parametersM / 1000} B` : `${m.parametersM} M`}
}
Weights: {m.weights}
{m.licence &&
Licence: {m.licence}
}
Training data: {m.trainingData}
{m.benchmark &&
Reported result: {m.benchmark}
}
Compare all models →
); })()} {questionsFor(e.id).length > 0 &&
    {questionsFor(e.id).map((q) =>
  • {q.question}
  • )}
}

{e.principle}

withTermHovers(t, { skipId: e.id })} /> withTermHovers(t, { skipId: e.id })} /> {e.generation} {e.since}
), ...productsTab(g.incoming(e.id).get("drug") ?? []), ]; case "target": return [ overview(<>
{structuresForTarget(e.id).length > 0 &&
Solved structures with a drug bound
}
Full dossier: hotspots, trials, resistance, assays, models, open questions →
{(e.role.length > 0 || e.evidenceTier) && (
{e.role.map((r) => {TARGET_ROLE_LABEL[r]})} {e.evidenceTier && Evidence: {EVIDENCE_TIER_LABEL[e.evidenceTier]}} All cancer genes by role →
)} {e.firstDescribed !== undefined && (

First described {e.firstDescribed}.{" "} {e.firstDescribedNote}{e.firstDescribedSource && <> Source.}

)} {e.sources.length > 0 && (

Sources:{" "} {e.sources.map((s, i) => {i > 0 && "; "}{s.label}{s.note && ({s.note})})}

)} {hotspotsFor(e.id) &&

Residue-by-residue table on the dossier →

} {questionsFor(e.id).length > 0 &&
    {questionsFor(e.id).map((q) =>
  • {q.question}
  • )}
} {assaysForTarget(e.id).length > 0 &&
    {assaysForTarget(e.id).map((a) =>
  • {a.name} · {a.cutoff}
  • )}
} {modelsFor(e.id) &&

Cell lines and mouse models for this target →

}

{withTermHovers(e.biology, { skipId: e.id })}

withTermHovers(t, { skipId: e.id })} /> {e.targetClass.replace("-", " ")}{e.symbol && · {e.symbol}} {e.hgnc && }
{hpaFor(e.id) && } {e.prevalence.length > 0 && } ), ...productsTab(g.incoming(e.id).get("drug") ?? []), ]; case "drug": return [ overview(<> {STRUCTURES[e.id] ?
: }
s.source === "pdb" && /bound to/i.test(s.label))} molecule={STRUCTURES[e.id]?.find((s) => s.source === "pubchem")} targets={e.targets.map((id) => graph().get(id)).filter((t): t is NonNullable => !!t).map((t) => ({ id: t.id, name: t.name, route: routeFor(t) }))} schematic={processSchematicKey(e) ? : undefined} />
{/resist|escape|progress/i.test(e.summary) &&
}
{e.modality} {e.mechanism} {[e.brand, e.code].filter(Boolean).join(" · ")} {e.payload} {e.linker}
{e.dosing &&
} {assaysForDrug(e.id).length > 0 &&
    {assaysForDrug(e.id).map((a) =>
  • {a.name} · {a.cutoff}
  • )}
} {(coverageUs[e.id] || coverageUk[e.id]) &&
{coverageUs[e.id] && }{coverageUk[e.id] && }
}
{regionalApprovals[e.id] &&

Compare all products across the US, EU, UK, Japan, China and Australia →

} ), ...(e.approvals.length || e.regulatoryEvents.length ? [{ id: "approvals", label: "Regulatory", count: e.regulatoryEvents.length || e.approvals.length, content: (<> {e.regulatoryEvents.length > 0 && } {e.approvals.length > 0 && {e.approvals.map((a, i) => )}
RegionYearIndication
{a.region}{a.year}{a.indication}{a.note && · {a.note}}
} ) }] : []), ...(regimensFor(e.id).length ? [{ id: "regimens", label: "Regimens", count: regimensFor(e.id).length, content: (
    {regimensFor(e.id).map((r) =>
  • {r.name}
    {r.setting}
    {cycleSummary(r)}
  • )}
) }] : []), ...(e.toxicity.length || agentById(e.id) || recoveryCellsForDrug(e.id).length ? [{ id: "safety", label: "Safety", count: e.toxicity.length || undefined, content: (<> {e.toxicity.length > 0 && } {agentById(e.id) &&

Check interactions for {e.name} →

} {/anti-pd|anti-ctla|pd-1|pd-l1|ctla-4|checkpoint/i.test(e.modality + " " + e.mechanism) &&

Checkpoint side effects by organ →

} ) }] : []), ...(e.access.length ? [{ id: "access", label: "Cost & access", count: e.access.length, content: }] : []), { id: "trials", label: "Trials", content: <>{e.trials.length > 0 && } }, ]; case "company": { const stage = stageOf(e); const portfolio = e.companyType === "investor" ? portfolioOf(e.id) : []; return [ overview(
{e.hq}, {e.country}
{e.ticker && · {e.ticker}} {stage && {STAGE_LABEL[stage]}{e.ycBatch && · Y Combinator {ycBatchLabel(e.ycBatch)}}} {e.website && {websiteView(e.website).text}} {e.founded} {/* Cooperative groups came from institution records and keep their OpenAlex snapshot under the same id. */} {e.companyType === "cooperative-group" && }
), ...(e.companyType === "investor" ? [{ id: "portfolio", label: "Portfolio", count: portfolio.length, content: }] : []), ...productsTab([...new Map([...e.drugs.map((id) => g.must(id)), ...(g.incoming(e.id).get("drug") ?? [])].map((d) => [d.id, d])).values()]), ...peopleTab([...(g.incoming(e.id).get("person") ?? []), ...e.people.map((id) => g.must(id))]), ]; } case "institution": { const row = rankInstitutions().find((r) => r.institution.id === e.id); return [ overview(<>
{e.city}, {e.country} {e.institutionType.replace("-", " ")}{e.university && · {e.university}} {e.website && {websiteView(e.website).text}} {e.newsweekOncology2026 ? `#${e.newsweekOncology2026}` : "Not in top 300 listing used"} {e.nci && {e.nci}} {row && #{row.rank} · {row.score} points ({row.newsweekPoints} Newsweek + {row.nciPoints} NCI + {row.linkPoints} from {row.links} linked objects) · ranking}
), ...(e.programs.length ? [{ id: "programmes", label: "Programmes", count: e.programs.length, content: withTermHovers(t, { skipId: e.id })} /> }] : []), ...peopleTab([...(g.incoming(e.id).get("person") ?? []), ...e.people.map((id) => g.must(id))]), ]; } case "pathway": return [ overview(

{e.analogy}

), { id: "diagram", label: "Diagram", content: }, { id: "interventions", label: "How drugs attack it", count: e.interventions.length, content: withTermHovers(t, { skipId: e.id })} /> }, ]; case "term": return [overview(<>
{e.category}
)]; case "trial": return [ overview(
{e.setting} {phaseLabel(e.phase)} {e.sponsor} {e.nct && {e.nct}} {e.result} {e.started && {e.started}{e.startedType === "estimated" && (estimated)}} {e.yearReported} {e.enrolled !== undefined && {enrolmentLabel(e.enrolled, e.enrolledBasis)}{e.enrolledNote && {e.enrolledNote}}} {e.replication}
), ...(e.outcomes.length ? [{ id: "outcomes", label: "Outcomes", count: e.outcomes.length, content:
}] : []), // What the registry says about joining. Added 8 October 2026 with the trial-participation capture. ...((e.participation?.length || e.alternateParticipation?.length) ? [{ id: "taking-part", label: "Taking part", content: }] : []), ]; case "pairing": { const a = g.get(e.a), b = g.get(e.b); return [ overview(<>
{a && {a.name}} {e.pairingType === "caution" ? "⚠ with" : e.pairingType === "sequence" ? "→ then" : e.pairingType === "diagnostic-therapeutic" ? "→ selects" : "+"} {b && {b.name}} {e.pairingType.replace("-", " → ")}
{e.rationale} {e.evidence}
), ]; } case "roadmap": return [overview(), { id: "steps", label: "Steps", count: e.steps.length, content: }, { id: "story", label: "Story", content: }]; case "idea": return [ overview(
{(confidence[e.id] || e.confidence) && } {e.hypothesis} {e.rationale} {e.test} {e.maturity.replace(/-/g, " ")} {(e.actor || e.cost || e.horizonYears !== undefined) &&
{e.actor && {e.actor}} {e.cost && {e.cost === "small" ? "Small (under $1M)" : e.cost === "medium" ? "Medium ($1M to $50M)" : "Large (over $50M)"}} {e.horizonYears !== undefined && {e.horizonYears}}
} {e.bottlenecks.length > 0 &&
    {e.bottlenecks.map((id) => { const b = g.get(id); return b ?
  • {b.name} · {b.tldr}
  • : null; })}
}
), ]; case "paper": { const typeLabel: Record = { rct: "Randomised controlled trial", "meta-analysis": "Meta-analysis", observational: "Observational study", "real-world": "Real-world evidence", basic: "Basic science", translational: "Translational study", review: "Review", guideline: "Guideline", methods: "Methods" }; return [ overview(
{typeLabel[e.paperType] ?? e.paperType} {e.changedPractice !== undefined && {e.changedPractice ? "Changed practice" : "Has not changed practice yet"}} {e.participants !== undefined && {e.participants.toLocaleString()} participants}
{e.authors} {(() => { const j = g.kind("journal").find((x) => x.kind === "journal" && (x.name === e.journal || x.matchNames.includes(e.journal))); return j ? {e.journal} : e.journal; })()}, {e.year} {(e.doi || e.pmid) && {e.doi && doi:{e.doi}}{e.pmid && PubMed {e.pmid}}}
{e.findings.length > 0 &&
    {e.findings.map((f, i) =>
  • {withTermHovers(f)}
  • )}
}

{withTermHovers(e.whatItMeans)}

{e.caveats.length > 0 &&
    {e.caveats.map((c, i) =>
  • {withTermHovers(c)}
  • )}
}
), ]; } case "journal": { const papers = g.kind("paper").filter((p): p is Paper => p.kind === "paper" && (p.journal === e.name || e.matchNames.includes(p.journal))).sort((a, b) => b.year - a.year); const people = g.kind("person").filter((p) => p.kind === "person" && p.papers.some((pp) => pp.journal && (pp.journal === e.name || e.matchNames.includes(pp.journal)))); return [ overview(
{e.publisher}{e.society && · {e.society}} {e.scope} {e.url.replace(/^https?:\/\//, "")} {e.access && {e.access.replace(/-/g, " ")}} {e.founded && {e.founded}} {e.issn && {e.issn}} {e.impactFactor && {e.impactFactor.value}{e.impactFactor.source && · {e.impactFactor.source}}}
), ...(papers.length ? [{ id: "key-papers", label: "Key papers", count: papers.length, content: (
{papers.map((p) =>
{p.paperType.replace(/-/g, " ")}{p.year}{p.changedPractice && changed practice}
{p.name}

{p.whatItMeans}

)}
) }] : []), ...peopleTab(people), ]; } case "bottleneck": { const ideas = g.incoming(e.id).get("idea") ?? []; const relievers = [...(g.incoming(e.id).get("technology") ?? []), ...(g.incoming(e.id).get("collection") ?? []), ...(g.incoming(e.id).get("trial") ?? [])]; const sevClass = e.severity === "critical" ? "negative" : e.severity === "major" ? "phase-2" : "mixed"; return [ overview(
{e.severity}{e.stage.replace(/-/g, " ")}{ideas.length} ideas to fix it
{e.metrics.length > 0 &&
{e.metrics.map((m, i) =>
{m.value}
{m.label}
{(m.source || m.url) &&
{m.url ? {m.source ?? "source"} : m.source}
}
)}
} {e.causes.length > 0 &&
    {e.causes.map((c, i) =>
  • {withTermHovers(c)}
  • )}
} {e.currentEfforts.length > 0 &&
    {e.currentEfforts.map((c, i) =>
  • {withTermHovers(c)}
  • )}
} {e.successLooksLike && {e.successLooksLike}}
), ...(ideas.length ? [{ id: "ideas", label: "Ideas to fix it", count: ideas.length, content: (
{ideas.map((i) => i.kind === "idea" &&
{i.maturity.replace(/-/g, " ")}{i.actor && {i.actor}}{i.cost && {i.cost} cost}
{i.name}

{i.tldr}

)}
) }] : []), ...(relievers.length ? [{ id: "relievers", label: "Relief available today", count: relievers.length, content: r.id)} /> }] : []), ]; } case "biomarker": return [overview()]; case "year": // Generated from every dated field in the corpus (src/data/years.ts). One tab: the counts strip, then every // dated entry grouped by what kind of thing it was, then the previous and next year. return [ overview(<>), ]; case "collection": return [ overview(
{e.url.replace(/^https?:\/\//, "")} {e.holds} {e.license} {e.maintainer} {datasetFor(e.id) && (() => { const d = datasetFor(e.id)!; return (
Dataset registry
Size: {d.size}
Access: {d.access}
Consent and reuse: {d.consent}
All datasets and the models trained on them →
); })()}
), ]; case "person": { const inst = e.institutionId ? g.get(e.institutionId) : undefined; return [ overview(
{e.role} {inst && {inst.name}}
{e.specialisms.map((s) => {s})}
), ...(e.papers.length ? [{ id: "papers", label: "Papers", count: e.papers.length, content: (
{e.papers.map((p, i) => )}
TitleJournalYear
{p.url || p.doi ? {p.title} : p.title}{p.note && !PAPER_PROVENANCE[p.note] &&
{p.note}
}
{p.journal}{p.year}
{paperProvenance(e.papers).map((line) =>

{line}

)}
) }] : []), ]; } case "section": { const techs = g.incoming(e.id).get("technology") ?? []; return [ overview(
), { id: "technologies", label: "Technologies", count: techs.length, content: (
{techs.map((t) => t.kind === "technology" &&
{t.name}

{t.tldr}

)}
) }, ]; } } } function peopleTab(items: Entity[]): Tab[] { const people = [...new Map(items.filter((x) => x.kind === "person").map((x) => [x.id, x])).values()]; if (!people.length) return []; return [{ id: "people", label: "People", count: people.length, content: (
{people.map((p) => p.kind === "person" && (
{p.name}
{p.role}
{p.specialisms.slice(0, 3).map((s) => {s})}

{p.tldr}

))}
) }]; } function productsTab(drugs: Entity[]): Tab[] { if (!drugs.length) return []; // Grouped by what each product is, as Connected is: on the PD-L1 page three staining assays sat in one grid with // the antibodies they select patients for (src/lib/product-class.ts). One class keeps the plain grid. const groups = productGroups(drugs.filter((d): d is Drug => d.kind === "drug")); const content = groups.length < 2 ? : (
{groups.map(({ cls, drugs: list }) => (

{cls === "medicine" ? : }

))}
); return [{ id: "products", label: "Products", count: drugs.length, content }]; } /** Refs with product cards (molecule thumbnails) for drugs and chips for everything else. */ function RefsWithMolecules({ ids }: { ids: string[] }) { const g = graph(); const items = ids.map((id) => g.get(id)).filter((x): x is Entity => !!x); const drugs = items.filter((x): x is Drug => x.kind === "drug"); const rest = items.filter((x) => x.kind !== "drug"); return (<> {drugs.length > 0 && } {rest.length > 0 &&
} ); } /** * The timeline. Every era's date range, status, title and summary are in the HTML; each era's records, trial * outcomes and papers sit behind a "Show era" pill that fetches /api/v1/roadmaps/.json (src/lib/roadmap-eras.ts), * except the first and the current era, which are open and carry their bodies. The watch table carries its first * WATCH_PAGE rows and pages the rest from the same file. */ function RoadmapSteps({ r }: { r: Roadmap }) { const g = graph(); const tone: Record = { historic: "bg-zinc-400", current: "bg-emerald-500", emerging: "bg-amber-500", speculative: "bg-violet-500" }; const src = roadmapFile(r.id); const open = new Set(defaultOpen(r)); const watch = watchRows(g, r); return (<>
    {r.steps.map((s, i) => { const body = eraBody(g, s); return (
  1. {s.era}{s.status}{s.status === "speculative" && }

    {s.title}

    {s.description}

  2. ); })}

Every era's records, trial outcomes and papers, and every watch item, as JSON.

{r.steps.some((s) => s.status === "speculative") &&
} {watch.length > 0 && (

What to watch ({watch.length.toLocaleString("en-GB")})

Readouts, decisions and registry completion dates ahead. Each date is quoted from its source, not inferred; a missing date means no source states one.

    {watch.slice(0, WATCH_PAGE).map((w, i) => )} {watch.length > WATCH_PAGE && }
)} ); } /** "Depends on" and "Needed by" strips from the technology dependency DAG (`dependsOn`), linking to the map with this technology as root. */ function TechDependencies({ e }: { e: Extract }) { const g = graph(); const up = e.dependsOn.map((id) => g.get(id)).filter((t): t is Entity => !!t); const down = g.kind("technology").filter((t) => t.dependsOn.includes(e.id)); if (!up.length && !down.length) return null; const chip = (t: Entity) => {t.name}; const mapHref = `/dependencies/?root=${e.id}`; return ( Open the dependency map →}>
({up.length})
{up.length ?
{up.map(chip)}
:

Nothing recorded yet: a foundation, or a gap to fill.

}
({down.length})
{down.length ?
{down.map(chip)}
:

Nothing in the corpus depends on this yet.

}

Dependencies are what this technology cannot be delivered without: manufacturing steps, instruments, software, upstream methods. See its full chain on the map.

); } /** Products with no molecule (cells, vaccines, devices, tests) get the schematic of their primary technology. */ function DrugSchematic({ technologies, modality }: { technologies: string[]; modality: string }) { const g = graph(); const tech = technologies.map((id) => g.get(id)).find((t) => t && t.kind === "technology"); if (!tech || tech.kind !== "technology") return null; const { mesh } = schematicFor(tech.id, tech.sections); return (
Schematic of the modality · {modality} · not a molecule; see the {tech.name} page
); } /** A glossary term's picture: its target, molecule, technology or organ when it has one, otherwise the category animation. */ function TermVisualPanel({ term }: { term: Term }) { const g = graph(); const v = termVisual(term, g); switch (v.kind) { case "target": return <>

Showing the target this term concerns: {v.target.name}.

; case "molecule": { const entries = STRUCTURES[v.drugId] ?? []; const d = g.get(v.drugId); return <>

Showing the molecule this term concerns: {d ? {d.name} : v.drugId}.

; } case "tech": return <>

Showing the technology this term belongs to: {v.tech.name}.

; case "cancer": { const c = g.get(v.cancerId); return <>

Showing the organ this term concerns{c ? <>: {c.name} : null}.

; } default: return ; } }