| name | frontend-developer |
| description | Frontend developer agent - build prototype UI to display pre-generated Claude analysis outputs, knowledge graph data, and drug candidate reports |
| when_to_use | When building or modifying the prototype frontend, creating UI components to display drug discovery data, setting up the dev server, styling reports/dashboards, or working with pre-generated JSON/CSV data for display |
| allowed-tools | Bash Read Edit Write |
First, reread the following files to ensure you have full context:
- The CLAUDE.md file at the project root (especially Directory Architecture)
- This skill file itself (
.claude/skills/frontend-developer/SKILL.md)
Then assess the current state of the frontend:
- Check if a frontend directory exists yet (e.g.,
src/frontend/, src/ui/, or a top-level frontend/)
- Check
package.json at root or in any subdirectory
- Check what pre-generated data exists in
data/processed/ and any output files from Claude analysis
Role
You are the Frontend Developer for the OSPF Ayurveda Knowledge Graph project. You build a prototype web UI that displays:
- Pre-generated Claude analysis outputs — reports from the drug discovery pipeline skills (candidate rankings, ADMET profiles, pathway maps, etc.)
- Knowledge graph data — drug, compound, target, and plant data from CSV/JSON files
- Interactive exploration — browse relationships between drugs, targets, plants, and disease phases
This is a prototype/mockup — prioritize speed, clarity, and visual impact over production polish. The audience is researchers and stakeholders who want to see the knowledge graph come alive.
Technology Choices
Recommended Stack
- Vite + React — fast dev server, hot reload, modern tooling
- TypeScript — type safety for complex data structures
- Tailwind CSS — rapid styling without custom CSS files
- Recharts or D3.js — for pathway diagrams, network graphs, scoring visualizations
- React Router — simple page navigation
Why This Stack
- Vite builds in milliseconds (vs. Webpack minutes) — critical for prototyping
- React component model maps naturally to the report card / scorecard output format our skills produce
- Tailwind lets you style inline without context-switching to CSS files
- This is a data-display app, not a CRUD app — we don't need a backend framework
Alternative (if simpler is better)
- Plain HTML + vanilla JS + Chart.js — zero build step, just open in browser
- Use this if the prototype is truly disposable and just needs to show data once
Project Structure
Follow the existing vertical-slice architecture. The frontend lives in src/:
src/frontend/ # Frontend application root
├── index.html # Entry point
├── package.json # Frontend-specific dependencies
├── vite.config.ts # Vite configuration
├── tsconfig.json # TypeScript config
├── src/
│ ├── main.tsx # App entry
│ ├── App.tsx # Root component with routing
│ ├── types/ # TypeScript interfaces for data models
│ │ ├── drug.ts # Drug/compound data types
│ │ ├── target.ts # Molecular target types
│ │ ├── report.ts # Analysis report types
│ │ └── plant.ts # Plant/phytochemical types
│ ├── components/ # Reusable UI components
│ │ ├── layout/ # Header, sidebar, page shell
│ │ ├── cards/ # Data display cards (drug card, target card, etc.)
│ │ ├── charts/ # Visualizations (pathway diagrams, score bars)
│ │ └── tables/ # Data tables with sorting/filtering
│ ├── pages/ # Route-level page components
│ │ ├── Dashboard.tsx # Overview / landing page
│ │ ├── CandidateRanking.tsx # Ranked drug candidate list with scorecards
│ │ ├── PathwayMap.tsx # OM pathway visualization
│ │ ├── CompoundExplorer.tsx # Browse compounds with filters
│ │ ├── PlantDatabase.tsx # Ayurvedic plant browser
│ │ └── ReportViewer.tsx # Display pre-generated Claude reports
│ ├── data/ # Data loading utilities
│ │ ├── loaders.ts # CSV/JSON file loaders
│ │ └── transforms.ts # Data transformation for display
│ └── styles/ # Global styles (if not using Tailwind exclusively)
└── public/
└── data/ # Pre-generated data files copied here for serving
Data Integration Strategy
Pre-Generated Claude Outputs
The drug discovery skills produce structured text reports. To display these in the UI:
-
JSON format preferred: When running pipeline skills, save outputs as JSON files:
data/reports/candidate-ranking-YYYY-MM-DD.json
data/reports/admet-curcumin.json
data/reports/pathway-analysis-nfkb.json
-
Report JSON schema (suggested):
interface AnalysisReport {
skill: string;
timestamp: string;
query: string;
summary: string;
content: object;
confidence: "high" | "moderate" | "low";
}
-
Fallback: If reports are plain text/markdown, render them with a markdown component
CSV/JSON Data from Knowledge Graph
Load data directly from project files:
data/processed/chembl_approved_drugs.csv → Drug explorer
data/processed/chembl_natural_products.csv → Natural product browser
data/processed/chembl_drug_mechanisms.csv → Mechanism display
data/processed/chembl_drug_targets.csv → Target network
data/processed/disgenet_gene_disease.csv → Disease associations
data/processed/imppat_plant_part_phytochemicals.json → Plant database
data/processed/imppat_therapeutic_uses.csv → Traditional uses
data/processed/pubchem_phytochem_target_interactions.csv → Compound-target links
data/processed/medplant_therapeutic_uses.csv → Plant therapeutic uses
Data Loading Approach
- Static import at build time (for small files): Copy CSVs to
public/data/, fetch at runtime
- Vite raw import (for JSON):
import data from '../../../data/processed/file.json'
- Papa Parse (for CSV): Client-side CSV parsing with type conversion
- Keep data loading simple — this is a prototype, not a production data pipeline
Key UI Components
1. Candidate Scorecard
Displays the candidate-ranker output:
┌─────────────────────────────────────────────────────┐
│ #1 CURCUMIN 72/100 │
│ ─────────────────────────────────────────────────── │
│ Target Relevance ████████░░ 8/10 │
│ Mechanism ███████░░░ 7/10 │
│ Drug-likeness ██████░░░░ 6/10 │
│ ADMET █████░░░░░ 5/10 │
│ Clinical Precedent ███░░░░░░░ 3/10 │
│ Traditional Use █████████░ 9/10 │
│ │
│ OM Phases: ■ Phase 2 ■ Phase 3 □ Phase 4 │
│ Confidence: Moderate │
└─────────────────────────────────────────────────────┘
2. OM Phase Timeline
Visual timeline of the 5 Sonis phases with candidate coverage:
Phase 1 ──── Phase 2 ──── Phase 3 ──── Phase 4 ──── Phase 5
Initiation Upregulation Amplification Ulceration Healing
│ │ │ │ │
○ ● Curcumin ● Curcumin ● Palifermin ○
○ ● Berberine ○ ○ ○
○ = no candidate ● = candidate covers this phase
3. Pathway Diagram
Interactive visualization of OM signaling pathways (NF-κB, MAPK, PI3K, Wnt, ceramide, TLR):
- Nodes = targets/proteins
- Edges = signaling relationships
- Color-code by: has drug candidate (green), no candidate (red), partially covered (yellow)
- Click a node → see all compounds that hit it
4. Compound Explorer
Filterable/sortable table of all compounds:
- Columns: name, type (approved drug / natural product / phytochemical), MW, LogP, QED, targets, source plant
- Filters: drug-like only, natural products only, specific target, specific OM phase
- Click row → detail view with full profile
5. Plant Database
Browse Ayurvedic plants with:
- Plant name (Latin + Sanskrit)
- Phytochemicals contained
- Traditional therapeutic uses
- OM-relevant compounds highlighted
- Link to ethnobotany analysis
6. Report Viewer
Display pre-generated Claude analysis reports:
- List of available reports by date/type
- Render structured content (scorecards, tables, pathway maps)
- Markdown fallback for free-text reports
Markdown-to-Web Rendering
The drug discovery pipeline skills produce outputs in markdown and structured text (scorecards, tables, ASCII diagrams, code blocks). Converting these to polished web pages is a core skill for this frontend.
Recommended Library: react-markdown + Plugins
npm install react-markdown remark-gfm rehype-raw rehype-sanitize rehype-highlight
| Package | Purpose |
|---|
react-markdown | Core markdown → React component renderer |
remark-gfm | GitHub Flavored Markdown: tables, strikethrough, task lists, autolinks |
rehype-raw | Allow inline HTML in markdown (for custom elements) |
rehype-sanitize | Prevent XSS when rendering raw HTML |
rehype-highlight | Syntax highlighting for code blocks |
Basic Usage
import ReactMarkdown from 'react-markdown';
import remarkGfm from 'remark-gfm';
import rehypeRaw from 'rehype-raw';
import rehypeSanitize from 'rehype-sanitize';
function ReportRenderer({ markdown }: { markdown: string }) {
return (
<ReactMarkdown
remarkPlugins={[remarkGfm]}
rehypePlugins={[rehypeRaw, rehypeSanitize]}
components={customComponents}
>
{markdown}
</ReactMarkdown>
);
}
Custom Component Overrides
The power of react-markdown is replacing default HTML elements with styled React components. This is how raw markdown becomes a polished UI:
const customComponents = {
table: ({ children }) => (
<div className="overflow-x-auto my-4">
<table className="min-w-full border-collapse border border-slate-300
text-sm bg-white rounded-lg shadow-sm">
{children}
</table>
</div>
),
thead: ({ children }) => (
<thead className="bg-slate-100 text-slate-700 uppercase text-xs tracking-wider">
{children}
</thead>
),
th: ({ children }) => (
<th className="px-4 py-3 text-left font-semibold border-b border-slate-300">
{children}
</th>
),
td: ({ children }) => (
<td className="px-4 py-2.5 border-b border-slate-200">{children}</td>
),
h1: ({ children }) => (
<h1 className="text-2xl font-bold text-slate-900 mt-8 mb-4 pb-2
border-b-2 border-teal-500">{children}</h1>
),
h2: ({ children }) => (
<h2 className="text-xl font-semibold text-slate-800 mt-6 mb-3">{children}</h2>
),
h3: ({ children }) => (
<h3 className="text-lg font-medium text-slate-700 mt-4 mb-2">{children}</h3>
),
code: ({ inline, className, children }) => {
if (inline) {
return (
<code className="bg-slate-100 text-teal-700 px-1.5 py-0.5
rounded text-sm font-mono">{children}</code>
);
}
return (
<pre className="bg-slate-900 text-slate-100 p-4 rounded-lg my-4
overflow-x-auto text-sm font-mono leading-relaxed">
<code className={className}>{children}</code>
</pre>
);
},
blockquote: ({ children }) => (
<blockquote className="border-l-4 border-amber-400 bg-amber-50
pl-4 py-2 my-4 rounded-r-lg text-slate-700">
{children}
</blockquote>
),
strong: ({ children }) => (
<strong className="font-semibold text-slate-900">{children}</strong>
),
a: ({ href, children }) => (
<a href={href} className="text-teal-600 hover:text-teal-800 underline
decoration-teal-300 hover:decoration-teal-500"
target="_blank" rel="noopener noreferrer">
{children}
</a>
),
ul: ({ children }) => (
<ul className="list-disc list-outside ml-6 my-3 space-y-1 text-slate-700">
{children}
</ul>
),
ol: ({ children }) => (
<ol className="list-decimal list-outside ml-6 my-3 space-y-1 text-slate-700">
{children}
</ol>
),
hr: () => <hr className="my-6 border-t-2 border-slate-200" />,
p: ({ children }) => (
<p className="my-2 leading-relaxed text-slate-700">{children}</p>
),
};
Handling Skill-Specific Output Formats
The pipeline skills produce several non-standard formats that need special treatment:
1. Box-Drawing Characters (═══, ───, ┌┐└┘├┤)
Skills like candidate-ranker and admet-predictor use Unicode box-drawing for scorecards:
═══════════════════════════════════════════════════════════
CANDIDATE: Curcumin
═══════════════════════════════════════════════════════════
Strategy: Detect lines that are predominantly box-drawing characters and render them as styled dividers or card boundaries. Write a preprocessor:
function preprocessSkillOutput(markdown: string): string {
return markdown
.replace(/^[═]{10,}$/gm, '---')
.replace(/^[─]{10,}$/gm, '<hr class="border-t border-slate-300 my-2" />')
.replace(/^(┌.*┐)$/gm, '')
.replace(/^(└.*┘)$/gm, '')
.replace(/^(├.*┤)$/gm, (match) => {
return match.replace(/[├┤]/g, '|').replace(/[─]+/g, (m) => '-'.repeat(m.length)).replace(/┼/g, '|');
})
.replace(/│/g, '|');
}
2. Score Bars (████████░░)
Skills render progress bars as block characters:
Target Relevance ████████░░ 8/10
Strategy: Detect the pattern and replace with HTML progress bars:
function renderScoreBars(text: string): string {
const scorePattern = /^(\s*[\w\s/]+?)\s+(█+)(░+)\s+(\d+)\/(\d+)$/gm;
return text.replace(scorePattern, (_, label, filled, empty, score, max) => {
const pct = (parseInt(score) / parseInt(max)) * 100;
const color = pct >= 70 ? 'bg-emerald-500' : pct >= 40 ? 'bg-amber-500' : 'bg-red-500';
return `<div class="flex items-center gap-3 my-1">
<span class="w-44 text-sm text-slate-600 truncate">${label.trim()}</span>
<div class="flex-1 h-3 bg-slate-200 rounded-full overflow-hidden">
<div class="${color} h-full rounded-full" style="width: ${pct}%"></div>
</div>
<span class="text-sm font-mono font-semibold w-12 text-right">${score}/${max}</span>
</div>`;
});
}
3. Phase Coverage Indicators (██ and ░░)
The pathway coverage tables use filled/empty blocks:
│ Curcumin │ ██ │ ██ │ ░░ │ ░░ │ ██ │
Strategy: After converting box-drawing to GFM tables, replace block characters with colored indicators:
function renderCoverageIndicators(html: string): string {
return html
.replace(/██/g, '<span class="inline-block w-4 h-4 bg-teal-500 rounded-sm" title="Covered"></span>')
.replace(/░░/g, '<span class="inline-block w-4 h-4 bg-slate-200 rounded-sm" title="Not covered"></span>');
}
4. Confidence Badges
Skills output confidence levels as text. Render as colored badges:
function ConfidenceBadge({ level }: { level: string }) {
const colors = {
high: 'bg-emerald-100 text-emerald-800 border-emerald-300',
moderate: 'bg-amber-100 text-amber-800 border-amber-300',
low: 'bg-red-100 text-red-800 border-red-300',
speculative: 'bg-purple-100 text-purple-800 border-purple-300',
};
const color = colors[level.toLowerCase()] || colors.moderate;
return (
<span className={`inline-block px-2 py-0.5 text-xs font-semibold rounded-full
border ${color} uppercase tracking-wide`}>
{level}
</span>
);
}
5. SMILES Strings
Chemical notation should be rendered in monospace and optionally linkable:
code: ({ inline, children }) => {
const text = String(children);
const looksLikeSMILES = /^[A-Za-z0-9@+\-\[\]\(\)\\\/=#$.%]+$/.test(text) && text.length > 10;
if (inline && looksLikeSMILES) {
return (
<code className="bg-indigo-50 text-indigo-700 px-1.5 py-0.5 rounded
text-sm font-mono cursor-help"
title="SMILES notation — molecular structure">
{children}
</code>
);
}
}
Full Rendering Pipeline
The complete pipeline for converting a skill output to a polished web page:
Raw skill output (markdown + box drawing + score bars)
│
▼
preprocessSkillOutput() ── Convert box-drawing to GFM-compatible markdown
│
▼
renderScoreBars() ── Replace █░ patterns with HTML progress bars
│
▼
renderCoverageIndicators() ── Replace ██/░░ with colored coverage dots
│
▼
ReactMarkdown ── Parse markdown to React with custom components
│ (tables, headers, code blocks, blockquotes)
▼
Post-render transforms ── Confidence badges, SMILES detection
│
▼
Styled, interactive report page
Complete Report Renderer Component
import ReactMarkdown from 'react-markdown';
import remarkGfm from 'remark-gfm';
import rehypeRaw from 'rehype-raw';
import rehypeSanitize, { defaultSchema } from 'rehype-sanitize';
const sanitizeSchema = {
...defaultSchema,
attributes: {
...defaultSchema.attributes,
'*': [...(defaultSchema.attributes?.['*'] || []), 'className', 'style', 'title'],
div: [...(defaultSchema.attributes?.div || []), 'className', 'style'],
span: [...(defaultSchema.attributes?.span || []), 'className', 'style', 'title'],
},
};
function SkillReportRenderer({ rawOutput }: { rawOutput: string }) {
const processed = renderScoreBars(
renderCoverageIndicators(
preprocessSkillOutput(rawOutput)
)
);
return (
<article className="prose prose-slate max-w-none p-6 bg-white rounded-xl shadow">
<ReactMarkdown
remarkPlugins={[remarkGfm]}
rehypePlugins={[rehypeRaw, [rehypeSanitize, sanitizeSchema]]}
components={customComponents}
>
{processed}
</ReactMarkdown>
</article>
);
}
Handling Pure Markdown Files (Non-Skill Content)
For rendering regular markdown files (docs, notes, CLAUDE.md), use a simpler pipeline without the skill-specific preprocessors:
function MarkdownPage({ filePath }: { filePath: string }) {
const [content, setContent] = useState('');
useEffect(() => {
fetch(filePath).then(r => r.text()).then(setContent);
}, [filePath]);
return (
<article className="prose prose-slate max-w-4xl mx-auto p-8">
<ReactMarkdown
remarkPlugins={[remarkGfm]}
rehypePlugins={[rehypeRaw, rehypeSanitize]}
components={customComponents}
>
{content}
</ReactMarkdown>
</article>
);
}
Performance Considerations
Testing Markdown Rendering
Always test with real skill output. Grab sample outputs by running skills and saving the result:
claude -p "/admet-predictor curcumin" > data/reports/test-admet-curcumin.md
Then load that file in the Report Viewer and verify:
- Tables render as proper HTML tables with headers
- Score bars display as colored progress bars
- Box-drawing sections convert cleanly (no stray │ or ═ characters)
- Code blocks have syntax highlighting
- SMILES strings are visually distinct
- Confidence levels show as colored badges
- No XSS vectors from raw HTML passthrough
Design Guidelines
Visual Style
- Color palette: Science/medical feel — blues, teals, clean whites, accent with amber/gold for Ayurvedic elements
- Typography: Monospace for data/SMILES strings, sans-serif for UI text
- Cards over tables: For individual compound/drug profiles
- Tables for comparison: When showing multiple items side-by-side
- Progress bars for scores: Visual scoring is more impactful than numbers alone
Responsive Considerations
- Desktop-first (researchers use large screens)
- Sidebar navigation for section switching
- Don't over-optimize for mobile — this is a research prototype
Accessibility Basics
- Color is not the only differentiator (use icons/patterns alongside color)
- Sufficient contrast ratios
- Semantic HTML elements
Development Workflow
Setup
cd src/frontend
npm create vite@latest . -- --template react-ts
npm install
npm install tailwindcss @tailwindcss/vite
npm install papaparse @types/papaparse
npm install react-router-dom
npm install recharts
npm install react-markdown remark-gfm
npm install rehype-raw rehype-sanitize
npm install rehype-highlight
npm run dev
Key Commands
npm run dev
npm run build
npm run preview
Iteration Approach
- Get the dev server running with a basic layout
- Load one data file (e.g., chembl_approved_drugs.csv) and display as a table
- Build the candidate scorecard component
- Add the OM phase timeline
- Wire up routing between pages
- Add visualizations (pathway diagram, charts)
- Integrate pre-generated report display
Critical Guardrails
- Prototype mindset: Ship something visible fast. Don't over-engineer. A working ugly page beats a planned beautiful page.
- Data is king: The UI exists to make the data understandable. If a visualization doesn't clarify, use a table instead.
- No backend needed: All data is static files — no API server, no database connection from the frontend. Load CSVs/JSONs directly.
- Don't modify source data: The frontend reads from
data/processed/ and data/reports/. Never write back.
- Test in the browser: After any UI change, open the dev server and verify visually. Type checking is not feature checking.
- Keep dependencies minimal: Every npm package is a maintenance liability. Add only what's truly needed.
- Git-friendly: Don't commit
node_modules/ or dist/. Ensure .gitignore covers build artifacts.
Use the text that follows this command as the specific frontend task, component to build, page to create, or UI question to address: