"use client"; import { useId, useRef, useState } from "react"; import { ParamKey } from "./paramHelpContent"; import { useParamHelp } from "./ParamHelpProvider"; /** * "How these parameters relate" — a collapsed disclosure with a clean * HTML/flex pipeline diagram inside. Presentation only. * * Centered single-column layout, max-width 460px, width 100% — never * wider than the panel, never overflowing. Each step is a neutral box; * between consecutive boxes sits a coloured "pill" naming the * parameter(s) that govern that transition, with a calm ↓ between * pill and the next box. */ export default function ParameterFlow() { const [open, setOpen] = useState(false); const panelId = useId(); return (
{open && (
)}
); } // ---------- diagram model ------------------------------------------------ type PillKind = "SPACE" | "EFFORT" | "VALIDATION"; interface Box { title: string; sub?: string; amber?: boolean; } /** A pill is a list of inline fragments: either a plain string or a * {bold} fragment so we can highlight just the parameter name. The * bold fragments carry an optional ``paramKey`` — when present, that * fragment is rendered as a button that opens the ParamHelp modal. */ type Frag = string | { bold: string; paramKey?: ParamKey }; interface Pill { kind: PillKind; /** Optional small text prefix (e.g. "↻ " for the loop pill). */ prefix?: string; fragments: Frag[]; } const PILL_STYLES: Record = { SPACE: { bg: "#E7EFF1", fg: "#2C5563" }, EFFORT: { bg: "#F6ECE0", fg: "#8A4E20" }, VALIDATION: { bg: "#ECEBE6", fg: "#5A6670" }, }; const BOXES: Box[] = [ { title: "~20,000 genes", sub: "every gene measured" }, { title: "Gene pool — N genes", sub: "the shortlist the engine draws from" }, { title: "One program", sub: "Select → Reduce → Fit" }, { title: "One generation", sub: "every program scored & ranked" }, { title: "Winning program" }, { title: "permutation p", sub: "could this be luck?", amber: true }, ]; const PILLS: Pill[] = [ { kind: "SPACE", fragments: [ { bold: "Prefilter top-N", paramKey: "prefilter_n" }, " — keep the N most promising genes (off by default = all ~20,000)", ], }, { kind: "SPACE", fragments: [ { bold: "Max sets", paramKey: "max_sets" }, " × ", { bold: "Genes/set", paramKey: "genes_per_set" }, " — pick 1–2 sets, each ≤ G genes", ], }, { kind: "EFFORT", fragments: [ { bold: "Population", paramKey: "population" }, " — programs compete each round · ", { bold: "λ", paramKey: "lambda" }, " — taxes extra genes", ], }, { kind: "EFFORT", prefix: "↻ ", fragments: [ "× ", { bold: "Generations", paramKey: "generations" }, " — breed the best, repeat · ", { bold: "Seed", paramKey: "seed" }, " — fixes the randomness", ], }, { kind: "VALIDATION", fragments: [ { bold: "Permutations", paramKey: "permutations" }, " — re-run on N shuffled-label sets", ], }, ]; // ---------- render ------------------------------------------------------- function Pipeline() { return (
{BOXES.map((b, i) => (
{i < PILLS.length && ( <> )}
))}
); } function BoxRow({ box }: { box: Box }) { return (
{box.title}
{box.sub && (
{box.sub}
)}
); } function PillRow({ pill }: { pill: Pill }) { const { bg, fg } = PILL_STYLES[pill.kind]; return (
{pill.prefix && ( {pill.prefix} )} {pill.fragments.map((f, i) => { if (typeof f === "string") return {f}; if (!f.paramKey) { return ( {f.bold} ); } return ; })}
); } function ParamPillButton({ bold, paramKey, colour, }: { bold: string; paramKey: ParamKey; colour: string; }) { const ref = useRef(null); const { open } = useParamHelp(); return ( ); } function Arrow() { return (
); } function Legend() { return (
teal = where it searches amber = how hard it searches grey = validation
); } function LegendItem({ fill, ink, children, }: { fill: string; ink: string; children: React.ReactNode; }) { return ( {children} ); }