Skip to content

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.

  1. 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.

  2. 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.

  3. 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.

  4. Human-in-the-loop is a feature, not friction. Surface the reasoning chain. Do not collapse synthesis behind a chatbot interface. Every <SignalAnalysisCard> exposes the what_happenedwhy_it_matters chain before the conclusion.

  5. 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.

  6. 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_at sort, 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]
- Drug name + brand: Inter 16px bold - Company, target, phase: Inter 13px, muted - ChEMBL ID: IBM Plex Mono 11px — always - [↻ 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.7
  • signal_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_implications block — Source Serif 4
  • watchlist items — 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 every signal_analyses item — see §6.2
  • Reviewer slot: Reviewed by — [Assign reviewer] ghost button in briefing header, stores to localStorage

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_at relative (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_signals from response → <CitationChip> elements below each answer → click → Inspector
  • sources_used badge 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.