Skip to content

feat: two-lab switcher (synthetic Okafor + real McClean) - #6

Merged
build-with-dhiraj merged 2 commits into
mainfrom
feat/lab-switcher
Jun 21, 2026
Merged

build-with-dhiraj merged 2 commits into
mainfrom
feat/lab-switcher

Conversation

@build-with-dhiraj

Copy link
Copy Markdown
Owner

Two-lab switcher: synthetic Okafor + real McClean

The app shipped one lab. This PR restores the original synthetic Okafor Lab as a second selectable dataset alongside the real-public McClean Lab, and adds a calm, on-brand lab switcher in the top nav. Default active lab = McClean (the real PoC headline). Switching swaps every flow, graph, and tour to the active lab with zero state bleed.

Supersedes #5 (the prior McClean-only seed PR). This PR is the broader two-lab scope: it keeps the real McClean seed intact (integrity boundary preserved verbatim) and adds Okafor back as a switchable illustrative demo. #5 can be closed in favor of this.

Architecture (routing refactor, not a rewrite)

  • LabDataset type (src/lib/types.ts): each lab carries its identity, data arrays, per-lab lookups, scripts, a per-lab knowledge graph + neighborsOf, and a narration object holding every authored lab-specific string and step-id gate. This is what removes hardcoded lab names from the flow components.
  • buildGraph(src) (src/lib/graph.ts): builds the node set + a neighborsOf that closes over this lab's links (not a global). NODE_TYPE_LABELS stays shared.
  • Bundles: src/lib/labs/mcclean.ts (real-public; the INTEGRITY BOUNDARY header, every // source: comment, and every DEMO: marker preserved verbatim), src/lib/labs/okafor.ts (synthetic; faithfully recovered from f4a4f3d — the A-grade flows, the Sofia / Chen Yu / Ada Okafor / Liang Wei cast, iPSC protocols, the Sofia journey, the Okafor interview + chat scripts), registry in labs/index.ts.
  • LabProvider + useLab(): context is always available; a tiny Suspense-wrapped child reads ?lab= (Next 15 requirement). Precedence: URL → localStorage → default (mcclean). setLab updates the URL via router.replace (preserves route + flow position) and mirrors to localStorage. Nested outside Compliance/Tour so both see it.
  • LabSwitcher: accessible Radix dropdown, aria-label="Active lab", ring-2 ring-action focus, check on the active lab, per-lab provenance chip (McClean "Real public data" / Okafor "Illustrative demo"), single action-blue accent only.

State-bleed prevention

  • GraphExplorer / ForceGraph reset selection on dataset.id change.
  • Flow content is keyed by activeLabId (FlowKey in flows/layout.tsx) so the flow subtree remounts on switch — no stale chat history / wizard phase / journey view / selected node leaks between labs. Chrome (nav/sidebar/tour) stays mounted above the key boundary.

Verification

  • tsc --noEmit clean; next build clean (9/9 static pages). No lint script (CI skips by design).
  • Zero em-dashes in src/ (grep -rn '—' src/ is empty).
  • Headless (Playwright) per lab across all four flows + the guided tour: McClean strings (McClean / Ping Xu / Nikon Eclipse Ti / PEG) and Okafor strings (Okafor / Sofia / iPSC / Chen / patch-clamp) render exclusively per lab.
  • Switch-stress (Okafor → McClean → Okafor, mid-flow) shows no bleed and a clean console; ?lab= survives a full reload; Brain answers cite only the active lab's nodes.
  • Branding (joVE | LabOS), pain-first A-grade landing copy, and the global Compliance toggle are visually unchanged on both labs.

A Vercel preview deploy from this PR is fine. Do not merge or touch production.

🤖 Generated with Claude Code

build-with-dhiraj and others added 2 commits June 21, 2026 19:31
…public data)

Swaps the prototype's single seed lab from the synthetic "Okafor Lab" to the
real McClean Lab (PI Megan N. McClean, Dept. of Biomedical Engineering,
UW-Madison), populated only from cited public evidence. All four flows + the
guided tour now render coherently with McClean data and real citations.

INTEGRITY BOUNDARY (documented at the top of src/lib/data.ts):

REAL (from public sources, never invented):
- LAB identity, mission, dept, Room 3156 ECB  [mccleanlab.bme.wisc.edu]
- PEOPLE names/roles/emails (emails left "" where not public)
  [mccleanlab.bme.wisc.edu/people/people/]
- SKILLS, EQUIPMENT + models (Nikon Eclipse Ti, Eppendorf 5810R/5418)
  [openwetware.org/wiki/McClean:_Nikon_TI_Basic_Use]
- REAGENTS + catalog numbers (PEG Sigma P3640, carrier DNA Sigma D1626,
  glass beads Fisher 11-312B, filter Nalgene 295-4545)
  [openwetware.org/wiki/McClean:Yeast_Transformation]
- PROTOCOLS + steps + why + hazards (yeast transformation, Nikon Ti startup)
- TACIT_NOTES verbatim (PEG-through-filter, carrier-DNA boil, scope warm-up,
  mandatory log book)
- ONBOARDING journey (Statement of Expectations, WisCard, Ashley Prochaska HR,
  Larry Wheeler keys, OWW/CAE/Quartzy, biosafety training)
  [openwetware.org/wiki/McClean:_Joining_The_Lab]

DEMO (synthesized, marked illustrative; captured at onboarding in production):
- skills-competency MATRIX cell states
- per-person joined dates, isNewHire / leavingInDays flags
- the literal sole-ownership claim

Bus-factor-1 thread is grounded in REAL named-author protocol attribution
[openwetware.org/wiki/McClean:Protocols]: Ping Xu owns the "P. Xu" FISH variant
(departing-expert thread), Renee Szakaly owns the "Mix & Go" competent-cell prep.
The departing/new-hire TIMING is marked demo. New hire = Timothy Wakiyama (real
roster member). Capturing Ping's FISH know-how drops bus-factor-1 risks 2 -> 1.

CI: typecheck + next build green. Zero em-dashes. JoVE branding and
DESIGN-SYSTEM intact. README data-provenance note updated.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Restore the original synthetic Okafor Lab as a second selectable dataset
alongside the real-public McClean Lab, with a calm, on-brand lab switcher.
Default active lab = McClean (the real PoC headline). Every flow, graph, and
tour swaps to the active lab with zero state bleed.

Architecture (routing refactor, not a rewrite):
- LabDataset type (src/lib/types.ts): carries each lab's identity, data,
  per-lab lookups, scripts, knowledge graph + per-lab neighborsOf, and a
  `narration` object that holds every authored lab-specific string and step-id
  gate (the part that kills hardcoded lab names in components).
- buildGraph(src) (src/lib/graph.ts): builds the node set + a neighborsOf that
  closes over THIS lab's links (not a global). NODE_TYPE_LABELS stays shared.
- Bundles: src/lib/labs/mcclean.ts (real-public, INTEGRITY BOUNDARY header +
  every // source: comment + DEMO: marker preserved verbatim) and
  src/lib/labs/okafor.ts (synthetic, faithfully recovered from f4a4f3d: the
  A-grade flows, the Sofia/Chen/Ada/Liang cast, iPSC protocols, the Sofia
  journey, the Okafor interview + chat scripts). Registry in labs/index.ts.
- LabProvider + useLab() (src/components/shell/LabProvider.tsx): context always
  available; a tiny Suspense-wrapped child reads ?lab= from the URL. Selection
  precedence: URL, then localStorage, then DEFAULT (mcclean). setLab updates the
  URL via router.replace (preserves route + flow position) and localStorage.
  Nested OUTSIDE Compliance/Tour so both tour and flows see it.
- LabSwitcher (top nav): accessible Radix dropdown, aria-label="Active lab",
  ring-2 ring-action focus, check on the active lab, per-lab provenance chip
  (McClean "Real public data" / Okafor "Illustrative demo"), single action-blue
  accent only.

State-bleed prevention:
- GraphExplorer/ForceGraph reset selection on dataset.id change.
- Flow content is keyed by activeLabId (FlowKey in flows/layout.tsx), remounting
  the flow subtree on switch so no stale chat history / wizard phase / journey
  view / selected node leaks between labs. Chrome (nav/sidebar/tour) stays
  mounted above the key boundary.

All flow components now read from useLab(): Landing, TopNav, Sidebar, Brain,
Matrix, Journey, Setup, GraphExplorer, ForceGraph, TourProvider. No component
imports @/lib/data, @/lib/graph (data), or @/lib/scripts anymore; the old
data.ts/scripts.ts are removed (data.ts -> labs/mcclean.ts as a rename).

Branding (joVE | LabOS), the pain-first A-grade landing copy, and the global
Compliance toggle are unchanged. Zero em-dashes in src/. typecheck + next build
clean. Verified headless (Playwright) per-lab across all four flows + the tour:
McClean strings (McClean / Ping Xu / Nikon / PEG) and Okafor strings (Okafor /
Sofia / iPSC / Chen / patch-clamp) render exclusively per lab; switch-stress
(Okafor->McClean->Okafor) shows no bleed, clean console; ?lab= survives reload.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@build-with-dhiraj
build-with-dhiraj merged commit 9d5acc5 into main Jun 21, 2026
1 check passed
@build-with-dhiraj
build-with-dhiraj deleted the feat/lab-switcher branch June 21, 2026 15:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant