OGUR — UX Design Specification¶
Canonical reference for all frontend work. Before writing any UI code, read the section that covers your area. If a task conflicts with this document, stop and flag it — do not silently deviate. This file is the authoritative source. Build plans describe intent; this file describes constraint.
§1 Purpose¶
This document specifies the information architecture, visual language, component contracts, and entity-page structure for OGUR's frontend. It is read by Claude Code before any frontend task and by human developers before any component work. Decisions here override developer intuition — if something is unspecified, ask before inventing. If something conflicts with this file, this file wins.
Target audience: pharma CI analysts, BD teams, strategy teams at mid-cap pharma/biotech. Primary use context: 1440px+ analyst workstation, single-tenant, no mobile.
§2 Core UX Principles¶
These are non-negotiable. They apply to every component, every tab, every screen.
-
Provenance is non-negotiable. Every claim the product displays must carry a
<SourceChip>and be one click from the raw source. No orphan text. Sources are inline — there is no separate "References" tab. -
The knowledge graph is infrastructure, never UI. No node-edge visualizations, no graph views, no force-directed layouts. Entity relationships surface as typed cards,
<EntityChip>inline chips, and structured triples only. See §6.4. -
Confidence is a first-class output. Every AI-synthesized block carries a
<ConfidenceBadge>. Freshness-weighted decay is architecturally designed in (M3 implementation), but the badge placeholder must be present from M1. See §6.2. -
Human-in-the-loop is a feature, not friction. Surface the reasoning chain. Do not collapse synthesis behind a chatbot interface. Every
<SignalAnalysisCard>exposes thewhat_happened→why_it_matterschain before the conclusion. -
Do not promise what public sources cannot deliver. COGS, primary-research surrogates, KOL sentiment, internal clinical data, partner deal terms — these are out of scope. If a user question touches these, the Ask panel should say so, not hallucinate.
-
Temporal signals matter more than static facts. Change detection, signal velocity, and recency are the core value proposition. Architectures that make temporal queries cheap (timeline scrubber,
detected_atsort, velocity indicators) take priority over completeness of static profile data.
§3 Visual Language¶
Design tokens¶
| Token | Value | Use |
|---|---|---|
canvas |
#FAFAFA |
App background — cold clinical white |
surface |
#FFFFFF |
Cards, panels, modals |
text-primary |
#0A0A0A |
Graphite, not pure black |
border |
#E5E5E5 |
1px hairline only |
accent |
#3B4CCA |
Interactive elements, links, active states |
severity-critical |
#C0392B |
CRITICAL severity badge |
severity-high |
#E67E22 |
HIGH severity badge |
severity-medium |
#2980B9 |
MEDIUM severity badge (not yellow — unreadable) |
severity-low |
#8C9099 |
LOW severity badge |
Box shadow: 0 1px 2px rgba(0,0,0,0.04) only — no elevation system beyond this.
Border radius: 2–4px max. Never rounded-2xl.
Typography (three semantic registers)¶
| Register | Font | Size | Line-height | Used for |
|---|---|---|---|---|
| UI chrome | Inter or IBM Plex Sans | 13px base, 12px metadata | 1.4 | Nav, labels, table rows, filters |
| Identifiers | IBM Plex Mono | 11px | 1.3 | ChEMBL IDs, NCT IDs, timestamps, model_used, UUIDs |
| Prose | Source Serif 4 or Literata | 14px | 1.7 | executive_summary, strategic_implications, Ask answers |
The three registers must never bleed into each other. Prose in a monospaced font, or IDs in a serif, is a spec violation.
Density¶
High. Enterprise analysts resent whitespace. 13px base, 12px metadata, 11px mono for identifiers. No padding above 16px inside cards.
Motion¶
120ms ease-out on hover states and the Inspector slide-in. Nothing else. No bounce, no fade-in-on-scroll theater, no staggered mount animations.
§4 Shell Layout¶
┌─ Left rail (56px collapsed / 220px expanded) ─┬─ Main workspace ──────────────────┬─ Inspector (380px, toggle) ─┐
│ OGUR │ │ │
│ ▸ Portfolio │ [context-dependent view] │ [selected object detail] │
│ ▸ Signals │ │ │
│ ▸ Ask │ │ │
│ ▸ Settings │ │ │
└────────────────────────────────────────────────┴───────────────────────────────────┴─────────────────────────────┘
Four primary views (all share the Inspector and shell):
| View | Route | Primary data source |
|---|---|---|
| Portfolio | / |
Signals aggregate + DrugProfile |
| Asset Detail | /asset/:drugName |
Briefing + Signals + DrugProfile |
| Signals | /signals |
GET /api/signals |
| Ask | /ask |
POST /api/ask |
Tab state lives in URL hash, not component state. Entity URLs are stable and shareable.
TopBar carries: breadcrumb, ⌘K trigger, health indicator dot, watchlist strip (pulses on new HIGH signal for watchlisted drugs).
§5 Entity Pages¶
§5.1 Asset Page (hero view — 70% of demo time)¶
Header strip:
pembrolizumab ∙ KEYTRUDA [Merck] Target: PD-1 Phase: Approved
ChEMBL1201585 Last briefing: 2h ago [↻ Refresh]
[↻ Refresh] button: triggers POST /api/briefing/nsclc-001 → 202 → poll GET /api/briefing/nsclc-001 every 3s → skeleton state → new briefing renders
Tab bar: Overview · Briefing · Signals · Trials · Internal Knowledge · Ask
Overview tab¶
| Zone | Content | Data source |
|---|---|---|
| Top-left | Fact sheet: target, MoA, chemistry class, pathway, indication | DrugProfile |
| Top-right | Biology/pathway card — inline SVG diagram for 5 hero drugs (PD-1/PD-L1, EGFR, ALK, KRAS G12C, MET exon 14); text card fallback | src/mocks/pathway-diagrams.ts |
| Bottom-left | Active trials count + phase breakdown bar | Signal filter: signal_type ∈ {trial_registered, trial_status_change} |
| Bottom-right | Signal counts last 30d by source | GET /api/signals aggregated client-side |
Briefing tab¶
The full latest briefing as a document. Read-path: GET /api/briefing/nsclc-001.
executive_summary— Source Serif 4, 14px, line-height 1.7signal_analyses— expandable<SignalAnalysisCard>list (headline → what-happened → why-it-matters → confidence chip → provenance citations)- Entity parsing: drug/target/company names in prose are parsed client-side (
entity-parser.ts) and rendered as<EntityChip>. Click → Inspector. This is the ontology moment; it is the highest-priority demo beat. - Citation chips: each
<SignalAnalysisCard>shows which signals it draws from as footnote-style<CitationChip>elements. Hover → highlights those signals in the Signals tab feed. strategic_implicationsblock — Source Serif 4watchlistitems — persistent strip in TopBar (not a section in the briefing document)- Metadata footer:
generated_at,signals_count,model_used— IBM Plex Mono 11px <ConfidenceBadge>on everysignal_analysesitem — see §6.2- Reviewer slot:
Reviewed by — [Assign reviewer]ghost button in briefing header, stores tolocalStorage
Signals tab¶
Feed for this asset, filtered client-side by drug_name from GET /api/signals?landscape_id=nsclc-001.
- Timeline scrubber (
<TimelineScrubber>): horizontal strip, last 180 days, each signal a colored dot (severity color). Hover → tooltip. Click → scrolls feed to that signal. Hand-rolled SVG (~80 lines) — do not use Recharts for this. - Feed (
<SignalRow>list): virtualized (TanStack Virtual). Each row: severity dot,<SourceChip>, title, company,detected_atrelative (absolute on hover). - Source filter chips (
<SourceFilterChips>) above the timeline. - Row click → Inspector (Signal schema).
Trials tab¶
Clinical trial signals grouped by phase. Source: signals where signal_type ∈ {trial_registered, trial_amendment, trial_status_change}.
Labeled: "Trial context for this asset — sourced from ClinicalTrials.gov signals."
Each trial is expandable to show amendments and status changes as a sub-timeline. This is derived from real signal data (not mock).
Internal Knowledge tab¶
Simulated. Mandatory SIMULATED corner badge in top-right of the panel (amber, cannot be dismissed).
5–8 fake internal documents per hero asset (hand-authored, src/mocks/internal-knowledge.ts):
- "2024 Due Diligence Memo — [drug] combination strategy" (PDF icon)
- "Q3 2025 Competitive Briefing — landscape"
- "Clinical Ops Update — trial enrollment"
- "Investment memo — LOE scenarios"
- "BD scan — combo partners Q4 2025"
Click → modal with 2–3 paragraphs of plausible hand-authored text. The SIMULATED badge is visible inside the modal too.
Ask tab¶
POST /api/ask scoped to landscape_id: nsclc-001.
- Messages styled as a document, not bubble chat — left-aligned
- User prompts: IBM Plex Mono
- Answers: Source Serif 4 prose
key_signalsfrom response →<CitationChip>elements below each answer → click → Inspectorsources_usedbadge on each answer card- Suggested prompts on first load (3 examples)
- Question history in collapsible accordion above input, persisted in
localStorage
Component: <AskPanel landscapeId="nsclc-001" /> — same component used in global Ask view.
§5.2 Company Page (M2)¶
Stub for M1 — display company name and a "Full company profile coming soon" placeholder.
M2 spec: OVERVIEW tab (headquarters, therapeutic focus, pipeline count) + PIPELINE tab (asset table filtered by company) + EVIDENCE tab (publications, press releases).
§5.3 Trial Page (M2)¶
Stub for M1 — display NCT ID and trial title, "Full trial view coming soon."
M2 spec: trial metadata, enrollment timeline, amendment history, cross-asset signal connections.
§6 System Components¶
All components use PascalCase and must match the names in this section exactly. Name drift is a spec violation.
§6.1 Inspector Panel (<Inspector>)¶
Universal right-panel detail surface. Width: 380px. Animation: Framer Motion, 120ms ease-out slide from right.
Four object schemas — context-switched by selected object type:
| Object | Inspector contents |
|---|---|
| Signal | Full record (all fields except raw_data); "Cross-source connections" (related signals as clickable rows); "Appears in briefing" link if referenced |
| Asset | Mini fact sheet (target, MoA, phase, company); open full view button |
| Target | Target name; list of associated assets in landscape; signal count last 30d |
| Company | Company name; assets in current landscape; recent signal activity summary |
Pin button (isPinned guard in Zustand store) keeps Inspector open across navigations. Header always shows: object type badge + object name.
§6.2 <ConfidenceBadge>¶
Variants: HIGH CONFIDENCE / MEDIUM CONFIDENCE / LOW CONFIDENCE
Style: muted label, no bright color fill — not a severity badge. Uses source data field confidence: "high" | "medium" | "low" from signal_analyses items.
Rules:
- Appears on every <SignalAnalysisCard> (one per AI-synthesized analysis block)
- Appears on every Ask answer card
- Must not appear on raw signal rows (those use severity, not confidence)
- In M3: badge will carry a freshness-decay timestamp. Design the component to accept an optional decayedAt prop from day one.
Micro-actions: ⚑ Flag and ✓ Confirm — toggle component state only (non-persisted, UI-only). Required for M1 Sanofi demo.
§6.3 <SourceChip>¶
Inline chip showing which source a signal comes from.
| Source key | Display label | Icon |
|---|---|---|
clinicaltrials |
ClinicalTrials.gov | flask icon |
openfda |
FDA | shield icon |
pubmed |
PubMed | book icon |
opentargets |
OpenTargets | dna icon |
conferences |
Conference | calendar icon |
Rules:
- Appears on every <SignalRow> (always inline, never in a tooltip)
- Appears in Inspector Signal schema
- One click on a SourceChip opens the external URL for that signal (from Signal.raw_data.url if present, else source homepage)
- Never appears without the source label — icon alone is insufficient for provenance
§6.4 <EntityChip>¶
Interactive inline chip for drug/target/company names parsed from briefing prose.
- Produced by
entity-parser.ts(regex + known-entity list → chip positions) - Renders as a subtle highlighted span within Source Serif prose
- Click → Inspector (context-switches to Asset, Target, or Company schema based on entity type)
- Do not use node-edge graphs to represent entity relationships. EntityChip is the only UI surface for entity traversal in M1.
Known entities list lives in entity-parser.ts. It must cover all NSCLC assets and targets (PD-1, PD-L1, EGFR, ALK, KRAS, ROS1, MET, HER2, BRAF, RET, NTRK, VEGF, VEGFR) plus the key companies (Merck, AstraZeneca, Roche, Pfizer, Novartis, BMS, Lilly, Amgen, J&J).
§6.5 Severity Badge (dot + label)¶
Used on <SignalRow> and Timeline scrubber dots. Color map lives in src/lib/severity.ts.
| Value | Dot color | Label |
|---|---|---|
high |
#E67E22 |
HIGH |
medium |
#2980B9 |
MEDIUM |
low |
#8C9099 |
LOW |
§6.6 <Badge> (general primitive)¶
General-purpose label badge. Two special variants:
| Variant | Style | When used |
|---|---|---|
[DEMO] |
Grey, 11px, low contrast | Simulated portfolio assets in the asset table |
SIMULATED |
Amber, corner badge, persistent | Internal Knowledge tab panel and modal |
The SIMULATED badge cannot be dismissed or hidden. Its purpose is to prevent demo confusion with real data.
§7 Simulation Strategy¶
Three explicit buckets. Never mix. Real API hooks must never import from src/mocks/.
| Bucket | Source | Label |
|---|---|---|
| Real | API endpoints (GET /api/signals, GET /api/briefing, POST /api/ask) |
No label |
| Derived | Client-side aggregation from real signal data (phase breakdown bar, signal counts, trial grouping) | No label |
| Simulated | Hand-authored in src/mocks/ (non-NSCLC portfolio assets, internal documents, pathway SVGs) |
[DEMO] or SIMULATED badge |
src/mocks/ is a single directory. A SIMULATED: true boolean constant gates mock data. If SIMULATED is false, no mock data is loaded anywhere.
§8 Component Naming Convention¶
All component names are PascalCase. They must match the names in §6 and the inventory below exactly. If a component is renamed, update this file in the same commit.
Component inventory¶
| Source path | Component | Used in |
|---|---|---|
components/shell/LeftRail.tsx |
<LeftRail> |
App.tsx |
components/shell/TopBar.tsx |
<TopBar> |
App.tsx |
components/shell/Inspector.tsx |
<Inspector> |
App.tsx (global, context-switched) |
components/shell/CommandPalette.tsx |
<CommandPalette> |
<TopBar> |
components/portfolio/AssetTable.tsx |
<AssetTable> |
views/Portfolio.tsx |
components/portfolio/AssetRow.tsx |
<AssetRow> |
<AssetTable> |
components/portfolio/PhaseHeatmap.tsx |
<PhaseHeatmap> |
views/Portfolio.tsx (toggle view) |
components/asset/AssetHeader.tsx |
<AssetHeader> |
views/AssetDetail.tsx |
components/asset/OverviewTab.tsx |
<OverviewTab> |
views/AssetDetail.tsx |
components/asset/BriefingTab.tsx |
<BriefingTab> |
views/AssetDetail.tsx |
components/asset/SignalsTab.tsx |
<SignalsTab> |
views/AssetDetail.tsx |
components/asset/TrialsTab.tsx |
<TrialsTab> |
views/AssetDetail.tsx |
components/asset/InternalKnowledgeTab.tsx |
<InternalKnowledgeTab> |
views/AssetDetail.tsx |
components/asset/AskTab.tsx |
<AskTab> |
views/AssetDetail.tsx |
components/briefing/BriefingDocument.tsx |
<BriefingDocument> |
<BriefingTab> |
components/briefing/SignalAnalysisCard.tsx |
<SignalAnalysisCard> |
<BriefingDocument> |
components/briefing/EntityChip.tsx |
<EntityChip> |
<ParsedProse> |
components/briefing/ParsedProse.tsx |
<ParsedProse> |
<BriefingDocument> |
components/briefing/WatchlistStrip.tsx |
<WatchlistStrip> |
<TopBar> |
components/signals/SignalRow.tsx |
<SignalRow> |
<SignalsTab>, views/GlobalSignals.tsx |
components/signals/TimelineScrubber.tsx |
<TimelineScrubber> |
<SignalsTab> |
components/signals/SourceFilterChips.tsx |
<SourceFilterChips> |
<SignalsTab>, views/GlobalSignals.tsx |
components/ask/AskPanel.tsx |
<AskPanel> |
<AskTab>, views/GlobalAsk.tsx |
components/ask/CitationChip.tsx |
<CitationChip> |
<AskPanel>, <SignalAnalysisCard> |
<SourceChip>, <ConfidenceBadge>, <Badge> — shared primitives, live in components/shared/ or inline in the components that use them.
Inspector sub-components: <SignalInspector>, <AssetInspector>, <TargetInspector>, <CompanyInspector> — all rendered inside <Inspector> via context switch.
§9 Sequencing Milestones¶
M1 — Sanofi Demo (target: May/June 2026)¶
In scope:
- Asset page: OVERVIEW + BRIEFING + SIGNALS + TRIALS + INTERNAL KNOWLEDGE + ASK (all 6 tabs)
- Portfolio view (asset table + phase heatmap toggle + [DEMO] badges)
- Global Signals view (cross-asset feed)
- Global Ask view
- Inspector (all 4 schemas: Signal, Asset, Target, Company)
- <ConfidenceBadge>, <SourceChip>, <EntityChip>, severity badges
- Human-in-the-loop hooks: Flag/Confirm micro-actions, reviewer slot
- <TimelineScrubber> (hand-rolled SVG)
- <CommandPalette> (⌘K — asset + target + signal jump)
- Watchlist strip (TopBar)
- Briefing refresh flow (trigger → 202 → poll → render)
- Company page: stub only
- Trial page: stub only
Out of scope for M1: - Auth, user management, multi-tenant - Timeline tab (not in asset detail tabs for M1) - Threat-flag lane - Briefing tab (standalone — exists as Asset Detail tab only) - Amendment detection UI - Freshness-weighted decay display on ConfidenceBadge - Real non-NSCLC data (landscape selector visible but disabled) - Writeback persistence (flagging/confirming is component state only) - WebSockets (polling is sufficient) - Mobile layout - People tab - Collaboration features
M2 — Post-Sanofi¶
- Company page full spec (OVERVIEW + PIPELINE + EVIDENCE)
- Trial page full spec (enrollment timeline, amendment history, cross-asset connections)
- Timeline tab in Asset Detail
- Threat-flag lane
- Freshness-weighted decay on
<ConfidenceBadge>(Phase 3 backend prerequisite) - Vector search backend feeding the Ask panel (Phase 3 backend prerequisite)
- People tab
- Multi-landscape selector (non-NSCLC landscapes)
§10 Demo Script (5 minutes, 8 beats)¶
Design every screen the script visits before anything else. These 8 beats are the acceptance criteria for M1.
| Time | Action | What it demonstrates |
|---|---|---|
| 0:00–0:30 | Land on Portfolio → filter Oncology → NSCLC. Hover an asset row → Inspector shows quick stats. | Grid, filtering, Inspector |
| 0:30–1:00 | Click pembrolizumab → Overview tab. Pathway diagram, fact sheet. | Asset detail, visual anchor |
| 1:00–2:30 | Switch to Briefing tab → read executive summary → hover a drug name in prose → Inspector opens. Click a citation chip → Inspector shows underlying signal. | The ontology moment — load-bearing demo beat |
| 2:30–3:15 | Switch to Signals tab → scrub the timeline → click a HIGH dot → Inspector. | Timeline, severity system |
| 3:15–3:45 | Switch to Internal Knowledge → open due diligence memo modal. Note SIMULATED badge. | 360° view story |
| 3:45–4:45 | Switch to Ask → type "What's the main competitive risk to this asset in the next 6 months?" → answer renders → citation chips appear → click one → Inspector. | AI + provenance chain |
| 4:45–5:15 | Hit Refresh Briefing → progress pill → new briefing renders. ⌘K → jump to osimertinib. | Freshness, keyboard navigation |
The briefing tab entity parsing → Inspector jump (beat 3) is the highest-risk beat technically. It must be implemented and polished before any other beat is considered done.
§11 API Surface (reference)¶
Frontend calls these endpoints only. No other backend calls.
| Endpoint | Used by |
|---|---|
GET /api/signals?landscape_id=nsclc-001&limit=500 |
Signals tab, Portfolio derived stats |
GET /api/briefing/nsclc-001 |
Briefing tab, Overview tab, WatchlistStrip |
POST /api/briefing/nsclc-001 |
Refresh button |
POST /api/ask body: {question, landscape_id} |
AskPanel (asset + global) |
GET /health |
TopBar status indicator dot |
Base URL: http://localhost:8000. Vite proxies /api to :8000 in dev.