|
| 1 | +# IRIS — Claude Code handoff |
| 2 | + |
| 3 | +Design exploration is settled. This document is the bridge from the Claude.ai prototypes to the live `johnoooh/iris` codebase. Hand it to Claude Code along with the three artifact files. |
| 4 | + |
| 5 | +--- |
| 6 | + |
| 7 | +## What's settled |
| 8 | + |
| 9 | +**Direction:** Triage two-pane layout (list + detail). Mobile collapses to list + tap-to-sheet. |
| 10 | + |
| 11 | +**Visual system:** Evolved warm-parchment palette + new iris-violet accent. Source Serif 4 (display), Inter Tight (UI), JetBrains Mono (technical). Tokens live in `styles/tokens.css`. |
| 12 | + |
| 13 | +**Locked tweak values from the prototype:** |
| 14 | +- Accent: `iris` (the violet) |
| 15 | +- Density: `comfy` |
| 16 | +- List width: `400px` |
| 17 | + |
| 18 | +**New patterns introduced:** |
| 19 | +- **Fit meter** — three-bar visualization (Likely / May / Unclear fit) shown per row and per detail pane |
| 20 | +- **Two-stage AI pipeline** — fast classify pass, then on-demand full simplification (still being validated, see harness) |
| 21 | +- **Local-AI badge** — always-visible mono pill in header announcing on-device model |
| 22 | +- **Compare** — pin up to 3 trials; sticky bar; compare-view page is later work |
| 23 | +- **Streaming shimmer** — placeholder lines shimmer until tokens land, then fade in |
| 24 | +- **Unified search** — NL + structured form become one input with a mode toggle |
| 25 | + |
| 26 | +--- |
| 27 | + |
| 28 | +## Files to reference |
| 29 | + |
| 30 | +| File | Role | |
| 31 | +|---|---| |
| 32 | +| `IRIS Triage.html` | Final chosen direction. Reference for layout, spacing, microcopy. | |
| 33 | +| `styles/tokens.css` | Drop-in CSS variables. Colors, fonts, shadows, shimmer keyframes. | |
| 34 | +| `shared/iris-shared.jsx` | Reference implementations of header, search bar, fit meter, status pill, streaming text, action row. **Translate to your stack** (vanilla JS / whatever the live app uses) — don't copy React if the app isn't React. | |
| 35 | +| `Classification Harness.html` | Standalone rig for validating the two-stage classify before wiring it in. | |
| 36 | + |
| 37 | +--- |
| 38 | + |
| 39 | +## Integration plan (recommended order) |
| 40 | + |
| 41 | +### Phase 1 — Visual system (low risk, ship first) |
| 42 | + |
| 43 | +1. **Add Google Fonts link** to `<head>`: |
| 44 | + ```html |
| 45 | + <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Source+Serif+4:opsz,wght@8..60,400;8..60,500;8..60,600;8..60,700&family=Inter+Tight:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap"> |
| 46 | + ``` |
| 47 | +2. **Drop in `styles/tokens.css`** as new variables alongside existing ones; gradually replace the old palette. |
| 48 | +3. **Replace header markup** with the new dense header + privacy chip + local-AI badge. |
| 49 | +4. **Demote dedication banner** to a footer or "About IRIS" disclosure. Don't lose it — it's part of the project's soul, just not above-the-fold task-blocking. |
| 50 | +5. **Compress the privacy paragraph** into the on-device chip + an expandable details element with the long form. |
| 51 | + |
| 52 | +Ship after Phase 1 — already a meaningful UX improvement, no logic changes. |
| 53 | + |
| 54 | +### Phase 2 — Row format + accordion or two-pane |
| 55 | + |
| 56 | +The current cards are full-width prose blocks. Swap for compact rows: |
| 57 | + |
| 58 | +``` |
| 59 | +[ ☐ ] Phase IIIb Study of Ribociclib + ET in Early Breast Cancer |
| 60 | + ▌▌▌ Likely fit · 0.1 mi · Phase 3 |
| 61 | +``` |
| 62 | + |
| 63 | +Two implementation paths — pick one based on engineering appetite: |
| 64 | + |
| 65 | +- **(a) Accordion in place** — easier. Click a row, it expands inline with the detail content. Works at any width. No layout fork. |
| 66 | +- **(b) Two-pane** — matches the prototype. CSS grid `grid-template-columns: 400px 1fr`, collapses to single-column under 820px (`@media` query + state-driven sheet on mobile). |
| 67 | + |
| 68 | +Recommend **(a) for first pass**, **(b) when you're ready to invest in the layout fork.** |
| 69 | + |
| 70 | +### Phase 3 — Two-stage classification |
| 71 | + |
| 72 | +**Don't ship this until the harness validates it.** See [Classification harness](#classification-harness) below. |
| 73 | + |
| 74 | +When ready: |
| 75 | +1. After search returns trial list, immediately render rows with title/distance/phase only — no fit meter yet. |
| 76 | +2. Kick off `classifyAll(trials, userDesc)` with concurrency 2–3. |
| 77 | +3. As each verdict returns, update that row's fit meter in place. |
| 78 | +4. Show `evaluating fit · 7 of 20` indicator in the toolbar while running. |
| 79 | +5. Once stage 1 is complete, default sort flips to "Best fit"; collapse UNLIKELY trials under a `12 less likely matches` disclosure. |
| 80 | +6. Stage 2 (full simplification) only fires for the currently-selected trial in the detail pane, or top N likely matches as the user scrolls. |
| 81 | + |
| 82 | +### Phase 4 — Compare |
| 83 | + |
| 84 | +1. `Set<nctId>` in memory, max size 3. |
| 85 | +2. Checkbox on each row. |
| 86 | +3. Sticky bar appears when set is non-empty: `[ 2 in compare ] [ Compare → ]`. |
| 87 | +4. Compare view itself is later — start with a placeholder route. |
| 88 | + |
| 89 | +### Phase 5 — Mobile polish |
| 90 | + |
| 91 | +1. Bottom-sheet pattern: tap row → sheet slides up with full detail. Backdrop dismiss + close button + drag handle. |
| 92 | +2. Sticky compare bar at bottom. |
| 93 | +3. Compact search summary chip replaces the full search bar on mobile (tap to expand). |
| 94 | + |
| 95 | +### Phase 6 — Persistence (optional, session-only) |
| 96 | + |
| 97 | +1. `sessionStorage` only — no PII to disk, in keeping with the privacy story. |
| 98 | +2. Save: search query, comparing set, currently-selected trial. |
| 99 | +3. Clear on a "Start over" button. |
| 100 | + |
| 101 | +--- |
| 102 | + |
| 103 | +## Classification harness |
| 104 | + |
| 105 | +`Classification Harness.html` is a standalone page with a mocked `classifyOne()` that simulates 200–1500ms latency and ~85% parse success. |
| 106 | + |
| 107 | +**To validate the real model:** |
| 108 | + |
| 109 | +1. Open the harness. |
| 110 | +2. Replace the body of `classifyOne()` with your live on-device call — the function signature is `(prompt, trial) => Promise<{ verdict, reason, raw, latencyMs }>`. |
| 111 | +3. Run with the included fixture (6 trials, with expected verdicts). |
| 112 | +4. Check the stats row: parse rate, avg latency, max latency, agreement with expected. |
| 113 | + |
| 114 | +**Pass criteria for moving to Phase 3:** |
| 115 | +- Parse rate ≥ 90% on 50+ real trials |
| 116 | +- Avg latency < 1.5s per trial on a mid-range laptop |
| 117 | +- Agreement ≥ 80% on a labeled held-out set |
| 118 | +- No catastrophic UNLIKELY false-negatives (a viable trial ranked as UNLIKELY) |
| 119 | + |
| 120 | +**If parse rate is low:** try constrained decoding, or tighten the prompt to demand a single token first (`Output a single token: LIKELY, POSSIBLE, or UNLIKELY. Then on a new line, one sentence of reasoning.`). |
| 121 | + |
| 122 | +**If latency is high:** drop concurrency to 1 (avoid model thrashing on small WebGPU buffers), truncate eligibility more aggressively (1500 → 800 chars), or run only on the top 10 by simple keyword pre-filter. |
| 123 | + |
| 124 | +--- |
| 125 | + |
| 126 | +## Things explicitly out of scope for this pass |
| 127 | + |
| 128 | +- Compare view (3-up side-by-side) — deferred |
| 129 | +- Account / login / save across sessions — deferred, conflicts with privacy story |
| 130 | +- Server-side fallback for the model — not consistent with on-device promise |
| 131 | +- Distance map view — nice-to-have, not on the critical path |
| 132 | +- Question-prep checklist — separate feature, separate PRD |
| 133 | + |
| 134 | +--- |
| 135 | + |
| 136 | +## Open questions for product |
| 137 | + |
| 138 | +1. **Fit meter wording** — "Likely fit / May fit / Unclear fit" is the current draft. Does that read right, or do we want softer phrasing ("Worth a look / Maybe / Probably not")? |
| 139 | +2. **UNLIKELY default behavior** — collapse them, or just sort to bottom? Risk of hiding viable trials if the model is wrong. |
| 140 | +3. **Compare view** — which dimensions matter most? Probably: phase, distance, drug/intervention, eligibility deltas, contact info. |
| 141 | +4. **Fit meter on mobile rows** — keep at full size or shrink to just the bars? Currently same component, both contexts. |
| 142 | + |
| 143 | +--- |
| 144 | + |
| 145 | +## Microcopy already drafted |
| 146 | + |
| 147 | +- Header sub: `clinical trial finder` |
| 148 | +- On-device chip: `on-device only` |
| 149 | +- Local-AI badge: `Gemma 2 2B · on-device` |
| 150 | +- Mode toggle: `Describe in your words` / `Structured form` |
| 151 | +- Mode toggle pill: `AI · on-device` |
| 152 | +- Understood section: `understood:` (mono, lowercase) |
| 153 | +- Section labels in detail: `What this study is testing`, `Who can join` |
| 154 | +- Fit panel caption: `based on what you described` |
| 155 | +- Toolbar count: `20 trials · near Boston · within 50 mi · recruiting` |
| 156 | +- Sort options: `Best fit`, `Distance`, `Phase`, `Most recent` |
| 157 | +- Compare bar (mobile): `**N** in compare` / `Compare →` |
| 158 | +- Sheet handle: drag affordance only, no label |
| 159 | +- Fit verdicts: `Likely fit`, `May fit`, `Unclear fit` |
0 commit comments