This commit implements all features specified in the eight-features design doc: Features Added: - Temperature Log: Track body temperature with fever alerts and trend charts - Contact Directory: Manage healthcare contacts with categories and roles - Weight Log: Monitor weight changes with BMI calculation and alerts - Treatment Timeline: Track treatment milestones and visualize progress - Caregiver Tasks: Manage delegated care tasks with completion tracking - Lab Results: Record lab tests with reference ranges and trend analysis - Medical Documents: Upload and organize medical documents - Drug Interactions: Check for interactions between medications Technical Changes: - Added 8 new Prisma models (TemperatureLog, Contact, WeightLog, TreatmentMilestone, CaregiverTask, LabResult, MedicalDocument, DrugInteraction) - Created 56 new components across 8 feature domains - Implemented 23 new API routes with full CRUD operations - Added comprehensive Zod schemas for type validation - Extended Dexie DB (v3) for offline-first sync support - Created lab panel templates (CBC, CMP, Liver, Tumor Markers) with flag computation - Built drug interaction checker with curated interaction database - Added 76 new tests (99 total) covering all new functionality Bug Fixes: - Fixed operator precedence bug in interaction checker - Fixed timezone handling in calculator tests - Aligned test expectations with grace window behavior All 99 tests pass and build completes successfully.
36 KiB
Design: 8 New Features for Next Step
Date: 2026-03-01 Priority: Urgent Scope: Medium (3-5 days) Target Users: End users (patients & family caregivers)
Established Patterns (must follow)
All new features must follow these exact conventions from the codebase:
File Patterns
- Pages:
src/app/(app)/<feature>/page.tsx—'use client', usesuseApp()for workspace,Header+PageContainerlayout - API routes:
src/app/api/workspaces/[id]/<feature>/route.ts— useswithAuth,checkWorkspaceAccess,canEdit, Zod validation, audit log on writes - Components:
src/components/<feature>/ComponentName.tsx—'use client', uses UI kit (Card,Button,showToast) - Validation:
src/lib/validation/schemas.ts— Zod schemas with type exports - Dexie tables:
src/lib/sync/db.ts— interface + table definition, bump version - Sync ops:
src/lib/sync/manager.ts— add entity types and op handlers
UI Patterns
- Colors:
primary-*(sage green),secondary-*(warm stone),accent-*(terracotta),alert-*(soft red),cream-*(warm neutral) - Semantic:
bg-background,bg-surface,bg-muted,border-border - Cards:
<Card>withshadow-card,rounded-card(20px) - Touch:
min-h-touch(48px), large tap targets - Typography:
font-displayfor headings,text-secondary-900for titles,text-secondary-500for meta - Icons:
lucide-react, 6x6 default, stroke color matching text - States:
LoadingState,EmptyState,ErrorStatefrom@/components/ui - Toast:
showToast('message', 'success'|'error') - Page structure:
<Header title="X" />then<PageContainer className="pt-4 space-y-6">
API Patterns
export const GET = withAuth(async (req: AuthenticatedRequest, { params }) => {
const { id: workspaceId } = await params
const access = await checkWorkspaceAccess(workspaceId, req.session.user.id)
if (!access) return NextResponse.json({ error: 'Access denied' }, { status: 403 })
// ... logic
})
export const POST = withAuth(async (req: AuthenticatedRequest, { params }) => {
const { id: workspaceId } = await params
const access = await checkWorkspaceAccess(workspaceId, req.session.user.id)
if (!access || !canEdit(access.role)) return NextResponse.json({ error: 'Access denied' }, { status: 403 })
const body = await req.json()
const result = schema.safeParse(body)
if (!result.success) return NextResponse.json({ error: 'Invalid input', details: result.error.flatten() }, { status: 400 })
// ... create + audit log
})
Data fetch pattern (pages)
// 1. useLiveQuery from Dexie for offline-first
const localData = useLiveQuery(() => db.table.where('workspaceId').equals(id)..., [id])
// 2. Also fetch from server
const fetchData = useCallback(async () => { ... }, [currentWorkspace.id])
// 3. Combine: prefer server, fallback to local
const data = serverData.length > 0 ? serverData : localData || []
Prisma Schema Additions
All 8 features in a single migration:
// ============================================
// TEMPERATURE LOG
// ============================================
model TemperatureLog {
id String @id @default(cuid())
workspaceId String
recordedAt DateTime @default(now())
tempCelsius Float
method String? // "oral", "forehead", "ear", "armpit"
notes String?
createdById String
deletedAt DateTime?
// Sync
version Int @default(1)
syncedAt DateTime @default(now())
// Relations
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
createdBy User @relation("TempLogCreatedBy", fields: [createdById], references: [id])
@@index([workspaceId, recordedAt])
@@index([workspaceId, deletedAt])
@@index([syncedAt])
}
// ============================================
// CONTACT DIRECTORY
// ============================================
model Contact {
id String @id @default(cuid())
workspaceId String
name String
role String // "Oncologist", "Pharmacist", etc.
category String // "ONCOLOGY", "HOSPITAL", "PHARMACY", "INSURANCE", "FAMILY", "OTHER"
phone String
phone2 String?
email String?
address String?
hours String? // "Mon-Fri 8am-5pm"
notes String?
isEmergency Boolean @default(false)
sortOrder Int @default(0)
deletedAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
createdById String
updatedById String
// Sync
version Int @default(1)
syncedAt DateTime @default(now())
// Relations
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
createdBy User @relation("ContactCreatedBy", fields: [createdById], references: [id])
updatedBy User @relation("ContactUpdatedBy", fields: [updatedById], references: [id])
@@index([workspaceId, category])
@@index([workspaceId, deletedAt])
@@index([syncedAt])
}
// ============================================
// WEIGHT LOG
// ============================================
model WeightLog {
id String @id @default(cuid())
workspaceId String
recordedAt DateTime @default(now())
weightKg Float
notes String?
createdById String
deletedAt DateTime?
// Sync
version Int @default(1)
syncedAt DateTime @default(now())
// Relations
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
createdBy User @relation("WeightLogCreatedBy", fields: [createdById], references: [id])
@@index([workspaceId, recordedAt])
@@index([workspaceId, deletedAt])
@@index([syncedAt])
}
// ============================================
// TREATMENT TIMELINE
// ============================================
enum MilestoneStatus {
SCHEDULED
COMPLETED
DELAYED
CANCELLED
}
model TreatmentMilestone {
id String @id @default(cuid())
workspaceId String
type String // "CHEMO_CYCLE", "SURGERY", "RADIATION", "SCAN", "CONSULTATION", "OTHER"
title String
description String?
plannedDate DateTime
actualDate DateTime?
status MilestoneStatus @default(SCHEDULED)
sortOrder Int @default(0)
notes String?
deletedAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
createdById String
updatedById String
// Sync
version Int @default(1)
syncedAt DateTime @default(now())
// Relations
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
createdBy User @relation("MilestoneCreatedBy", fields: [createdById], references: [id])
updatedBy User @relation("MilestoneUpdatedBy", fields: [updatedById], references: [id])
@@index([workspaceId, plannedDate])
@@index([workspaceId, status])
@@index([workspaceId, deletedAt])
@@index([syncedAt])
}
// ============================================
// CAREGIVER TASKS
// ============================================
enum TaskStatus {
TODO
IN_PROGRESS
DONE
CANCELLED
}
enum TaskPriority {
URGENT
HIGH
NORMAL
LOW
}
model CaregiverTask {
id String @id @default(cuid())
workspaceId String
title String
description String?
category String // "MEDICAL", "ERRANDS", "MEALS", "EMOTIONAL", "OTHER"
priority TaskPriority @default(NORMAL)
status TaskStatus @default(TODO)
assignedToId String?
dueDate DateTime?
completedAt DateTime?
completedById String?
deletedAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
createdById String
updatedById String
// Sync
version Int @default(1)
syncedAt DateTime @default(now())
// Relations
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
createdBy User @relation("TaskCreatedBy", fields: [createdById], references: [id])
updatedBy User @relation("TaskUpdatedBy", fields: [updatedById], references: [id])
assignedTo User? @relation("TaskAssignedTo", fields: [assignedToId], references: [id])
completedBy User? @relation("TaskCompletedBy", fields: [completedById], references: [id])
@@index([workspaceId, status])
@@index([workspaceId, assignedToId])
@@index([workspaceId, dueDate])
@@index([workspaceId, deletedAt])
@@index([syncedAt])
}
// ============================================
// LAB RESULTS
// ============================================
model LabResult {
id String @id @default(cuid())
workspaceId String
testDate DateTime
panelName String // "Complete Blood Count", "Comprehensive Metabolic", etc.
labName String? // "Quest", "Hospital Lab"
results Json // Array of { marker, value, unit, refMin, refMax, flag }
notes String?
deletedAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
createdById String
updatedById String
// Sync
version Int @default(1)
syncedAt DateTime @default(now())
// Relations
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
createdBy User @relation("LabResultCreatedBy", fields: [createdById], references: [id])
updatedBy User @relation("LabResultUpdatedBy", fields: [updatedById], references: [id])
@@index([workspaceId, testDate])
@@index([workspaceId, deletedAt])
@@index([syncedAt])
}
// ============================================
// MEDICAL DOCUMENTS
// ============================================
model MedicalDocument {
id String @id @default(cuid())
workspaceId String
title String
category String // "LAB_REPORT", "SCAN", "INSURANCE", "ID_CARD", "PRESCRIPTION", "OTHER"
fileName String
fileSize Int // bytes
mimeType String // "application/pdf", "image/jpeg"
fileData Bytes // Store in DB as bytes (self-hosted, no S3)
dateTaken DateTime?
expiryDate DateTime?
notes String?
deletedAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
createdById String
// Sync (no offline sync for file blobs — too large)
version Int @default(1)
syncedAt DateTime @default(now())
// Relations
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
createdBy User @relation("DocCreatedBy", fields: [createdById], references: [id])
@@index([workspaceId, category])
@@index([workspaceId, deletedAt])
@@index([syncedAt])
}
// ============================================
// DRUG INTERACTIONS (cached lookups)
// ============================================
model DrugInteraction {
id String @id @default(cuid())
workspaceId String
medication1Id String
medication2Id String
severity String // "MINOR", "MODERATE", "MAJOR", "CONTRAINDICATED"
description String
checkedAt DateTime @default(now())
// Relations
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
medication1 Medication @relation("Interaction1", fields: [medication1Id], references: [id], onDelete: Cascade)
medication2 Medication @relation("Interaction2", fields: [medication2Id], references: [id], onDelete: Cascade)
@@unique([workspaceId, medication1Id, medication2Id])
@@index([workspaceId])
}
User model additions (relations)
// Add to User model:
temperatureLogs TemperatureLog[] @relation("TempLogCreatedBy")
createdContacts Contact[] @relation("ContactCreatedBy")
updatedContacts Contact[] @relation("ContactUpdatedBy")
weightLogs WeightLog[] @relation("WeightLogCreatedBy")
createdMilestones TreatmentMilestone[] @relation("MilestoneCreatedBy")
updatedMilestones TreatmentMilestone[] @relation("MilestoneUpdatedBy")
createdTasks CaregiverTask[] @relation("TaskCreatedBy")
updatedTasks CaregiverTask[] @relation("TaskUpdatedBy")
assignedTasks CaregiverTask[] @relation("TaskAssignedTo")
completedTasks CaregiverTask[] @relation("TaskCompletedBy")
createdLabResults LabResult[] @relation("LabResultCreatedBy")
updatedLabResults LabResult[] @relation("LabResultUpdatedBy")
createdDocuments MedicalDocument[] @relation("DocCreatedBy")
// Add to Workspace model:
temperatureLogs TemperatureLog[]
contacts Contact[]
weightLogs WeightLog[]
milestones TreatmentMilestone[]
caregiverTasks CaregiverTask[]
labResults LabResult[]
medicalDocuments MedicalDocument[]
drugInteractions DrugInteraction[]
// Add to Medication model:
interactions1 DrugInteraction[] @relation("Interaction1")
interactions2 DrugInteraction[] @relation("Interaction2")
Dexie DB (Version 3)
Add to src/lib/sync/db.ts:
// New interfaces
export interface LocalTemperatureLog {
id: string
workspaceId: string
recordedAt: string
tempCelsius: number
method: string | null
notes: string | null
deletedAt: string | null
version: number
syncedAt: string
createdBy?: { id: string; name: string }
}
export interface LocalContact {
id: string
workspaceId: string
name: string
role: string
category: string
phone: string
phone2: string | null
email: string | null
address: string | null
hours: string | null
notes: string | null
isEmergency: boolean
sortOrder: number
deletedAt: string | null
version: number
syncedAt: string
}
export interface LocalWeightLog {
id: string
workspaceId: string
recordedAt: string
weightKg: number
notes: string | null
deletedAt: string | null
version: number
syncedAt: string
createdBy?: { id: string; name: string }
}
export interface LocalMilestone {
id: string
workspaceId: string
type: string
title: string
description: string | null
plannedDate: string
actualDate: string | null
status: string
sortOrder: number
notes: string | null
deletedAt: string | null
version: number
syncedAt: string
}
export interface LocalCaregiverTask {
id: string
workspaceId: string
title: string
description: string | null
category: string
priority: string
status: string
assignedToId: string | null
dueDate: string | null
completedAt: string | null
deletedAt: string | null
version: number
syncedAt: string
assignedTo?: { id: string; name: string }
createdBy?: { id: string; name: string }
}
export interface LocalLabResult {
id: string
workspaceId: string
testDate: string
panelName: string
labName: string | null
results: Array<{
marker: string
value: number
unit: string
refMin: number | null
refMax: number | null
flag: string | null // "LOW", "HIGH", "CRITICAL_LOW", "CRITICAL_HIGH", null
}>
notes: string | null
deletedAt: string | null
version: number
syncedAt: string
}
// Version 3 stores
this.version(3).stores({
appointments: 'id, workspaceId, datetime, deletedAt',
medications: 'id, workspaceId, active, deletedAt',
notes: 'id, workspaceId, type, deletedAt',
doseLogs: 'id, medicationId, workspaceId, takenAt',
workspaces: 'id',
symptoms: 'id, workspaceId, type, recordedAt, deletedAt',
temperatureLogs: 'id, workspaceId, recordedAt, deletedAt',
contacts: 'id, workspaceId, category, deletedAt',
weightLogs: 'id, workspaceId, recordedAt, deletedAt',
milestones: 'id, workspaceId, plannedDate, status, deletedAt',
caregiverTasks: 'id, workspaceId, status, assignedToId, deletedAt',
labResults: 'id, workspaceId, testDate, deletedAt',
outbox: 'id, workspaceId, timestamp',
syncMeta: 'id, workspaceId',
})
Note: Medical documents are NOT stored in Dexie (too large for IndexedDB). They are server-only.
Zod Validation Schemas
Add to src/lib/validation/schemas.ts:
// Temperature Log
export const temperatureLogSchema = z.object({
tempCelsius: z.number().min(30).max(45),
method: z.enum(['oral', 'forehead', 'ear', 'armpit']).nullable().optional(),
notes: z.string().max(500).nullable().optional(),
recordedAt: z.string().datetime().optional(),
})
// Contact
export const contactSchema = z.object({
name: z.string().min(1, 'Name is required').max(200),
role: z.string().min(1, 'Role is required').max(100),
category: z.enum(['ONCOLOGY', 'HOSPITAL', 'PHARMACY', 'INSURANCE', 'FAMILY', 'OTHER']),
phone: z.string().min(1, 'Phone is required').max(50),
phone2: z.string().max(50).nullable().optional(),
email: z.string().email().max(200).nullable().optional(),
address: z.string().max(500).nullable().optional(),
hours: z.string().max(200).nullable().optional(),
notes: z.string().max(1000).nullable().optional(),
isEmergency: z.boolean().default(false),
sortOrder: z.number().int().min(0).default(0),
})
// Weight Log
export const weightLogSchema = z.object({
weightKg: z.number().min(1).max(500),
notes: z.string().max(500).nullable().optional(),
recordedAt: z.string().datetime().optional(),
})
// Treatment Milestone
export const milestoneSchema = z.object({
type: z.enum(['CHEMO_CYCLE', 'SURGERY', 'RADIATION', 'SCAN', 'CONSULTATION', 'OTHER']),
title: z.string().min(1, 'Title is required').max(200),
description: z.string().max(1000).nullable().optional(),
plannedDate: z.string().datetime(),
actualDate: z.string().datetime().nullable().optional(),
status: z.enum(['SCHEDULED', 'COMPLETED', 'DELAYED', 'CANCELLED']).default('SCHEDULED'),
notes: z.string().max(2000).nullable().optional(),
})
// Caregiver Task
export const caregiverTaskSchema = z.object({
title: z.string().min(1, 'Title is required').max(200),
description: z.string().max(2000).nullable().optional(),
category: z.enum(['MEDICAL', 'ERRANDS', 'MEALS', 'EMOTIONAL', 'OTHER']),
priority: z.enum(['URGENT', 'HIGH', 'NORMAL', 'LOW']).default('NORMAL'),
status: z.enum(['TODO', 'IN_PROGRESS', 'DONE', 'CANCELLED']).default('TODO'),
assignedToId: z.string().cuid().nullable().optional(),
dueDate: z.string().datetime().nullable().optional(),
})
// Lab Result
const labMarkerSchema = z.object({
marker: z.string().min(1).max(50),
value: z.number(),
unit: z.string().max(20),
refMin: z.number().nullable().optional(),
refMax: z.number().nullable().optional(),
flag: z.enum(['LOW', 'HIGH', 'CRITICAL_LOW', 'CRITICAL_HIGH']).nullable().optional(),
})
export const labResultSchema = z.object({
testDate: z.string().datetime(),
panelName: z.string().min(1).max(200),
labName: z.string().max(200).nullable().optional(),
results: z.array(labMarkerSchema).min(1),
notes: z.string().max(2000).nullable().optional(),
})
// Medical Document (metadata only — file sent as multipart)
export const medicalDocumentSchema = z.object({
title: z.string().min(1, 'Title is required').max(200),
category: z.enum(['LAB_REPORT', 'SCAN', 'INSURANCE', 'ID_CARD', 'PRESCRIPTION', 'OTHER']),
dateTaken: z.string().datetime().nullable().optional(),
expiryDate: z.string().datetime().nullable().optional(),
notes: z.string().max(1000).nullable().optional(),
})
// Drug Interaction Check
export const interactionCheckSchema = z.object({
medicationIds: z.array(z.string().cuid()).min(2).max(20),
})
// Type exports
export type TemperatureLogInput = z.infer<typeof temperatureLogSchema>
export type ContactInput = z.infer<typeof contactSchema>
export type WeightLogInput = z.infer<typeof weightLogSchema>
export type MilestoneInput = z.infer<typeof milestoneSchema>
export type CaregiverTaskInput = z.infer<typeof caregiverTaskSchema>
export type LabMarker = z.infer<typeof labMarkerSchema>
export type LabResultInput = z.infer<typeof labResultSchema>
export type MedicalDocumentInput = z.infer<typeof medicalDocumentSchema>
export type InteractionCheckInput = z.infer<typeof interactionCheckSchema>
Sync Ops Extensions
Add to syncOpSchema.type:
'LOG_TEMP', 'DELETE_TEMP',
'CREATE_CONTACT', 'UPDATE_CONTACT', 'DELETE_CONTACT',
'LOG_WEIGHT', 'DELETE_WEIGHT',
'CREATE_MILESTONE', 'UPDATE_MILESTONE', 'DELETE_MILESTONE',
'CREATE_TASK', 'UPDATE_TASK', 'DELETE_TASK', 'COMPLETE_TASK',
'CREATE_LAB', 'UPDATE_LAB', 'DELETE_LAB'
Add to syncOpSchema.entityType:
'TEMPERATURE_LOG', 'CONTACT', 'WEIGHT_LOG', 'MILESTONE', 'CAREGIVER_TASK', 'LAB_RESULT'
API Endpoints
Feature 1: Temperature Log
| Method | Path | Description |
|---|---|---|
| GET | /api/workspaces/[id]/temperature |
List logs (query: from, to, limit) |
| POST | /api/workspaces/[id]/temperature |
Create log |
| DELETE | /api/workspaces/[id]/temperature/[tempId] |
Soft delete |
Feature 2: Contact Directory
| Method | Path | Description |
|---|---|---|
| GET | /api/workspaces/[id]/contacts |
List contacts (query: category) |
| POST | /api/workspaces/[id]/contacts |
Create contact |
| PATCH | /api/workspaces/[id]/contacts/[contactId] |
Update contact |
| DELETE | /api/workspaces/[id]/contacts/[contactId] |
Soft delete |
Feature 3: Weight Log
| Method | Path | Description |
|---|---|---|
| GET | /api/workspaces/[id]/weight |
List logs (query: from, to, limit) |
| POST | /api/workspaces/[id]/weight |
Create log |
| DELETE | /api/workspaces/[id]/weight/[weightId] |
Soft delete |
Feature 4: Treatment Timeline
| Method | Path | Description |
|---|---|---|
| GET | /api/workspaces/[id]/milestones |
List milestones |
| POST | /api/workspaces/[id]/milestones |
Create milestone |
| PATCH | /api/workspaces/[id]/milestones/[milestoneId] |
Update (inc. status) |
| DELETE | /api/workspaces/[id]/milestones/[milestoneId] |
Soft delete |
Feature 5: Caregiver Tasks
| Method | Path | Description |
|---|---|---|
| GET | /api/workspaces/[id]/tasks |
List tasks (query: status, assignedTo) |
| POST | /api/workspaces/[id]/tasks |
Create task |
| PATCH | /api/workspaces/[id]/tasks/[taskId] |
Update task |
| POST | /api/workspaces/[id]/tasks/[taskId]/complete |
Mark complete |
| DELETE | /api/workspaces/[id]/tasks/[taskId] |
Soft delete |
Feature 6: Lab Results
| Method | Path | Description |
|---|---|---|
| GET | /api/workspaces/[id]/lab-results |
List results (query: from, to) |
| GET | /api/workspaces/[id]/lab-results/trends |
Trend data for specific marker |
| POST | /api/workspaces/[id]/lab-results |
Create result |
| PATCH | /api/workspaces/[id]/lab-results/[labId] |
Update result |
| DELETE | /api/workspaces/[id]/lab-results/[labId] |
Soft delete |
Feature 7: Medical Documents
| Method | Path | Description |
|---|---|---|
| GET | /api/workspaces/[id]/documents |
List documents (metadata only) |
| POST | /api/workspaces/[id]/documents |
Upload (multipart/form-data) |
| GET | /api/workspaces/[id]/documents/[docId] |
Download file |
| DELETE | /api/workspaces/[id]/documents/[docId] |
Soft delete |
Feature 8: Drug Interactions
| Method | Path | Description |
|---|---|---|
| POST | /api/workspaces/[id]/medications/check-interactions |
Check all meds |
| GET | /api/workspaces/[id]/medications/interactions |
Get cached results |
UI/Page Design
Navigation Change
The bottom nav currently has 5 items. With 8 new features, the "More" (Settings) tab becomes a hub. The new features are accessed via:
- Today page surfaces: Temperature, Weight (quick log cards), upcoming tasks, next milestone
- Bottom nav stays as-is (Today, Appts, Meds, Symptoms, More)
- "More" page (
/settings) becomes a menu with sections:- Account & Settings (existing)
- Health Tracking: Temperature, Weight, Lab Results
- Care Team: Contact Directory, Caregiver Tasks
- Treatment: Timeline, Medical Documents
- Safety: Drug Interactions
Feature 1: Temperature Log — /temperature
Page structure:
- Header: "Temperature" with History icon (right)
- Quick log card: big number input (keyboard type=decimal), method selector (4 pill buttons: Oral/Forehead/Ear/Armpit), optional notes, "Log Temperature" button
- Fever alert banner: if last reading >= 38.0°C, show red alert card: "FEVER DETECTED — 38.3°C" with "Call Clinic" button using
tel:link from workspace.clinicPhone - Last 7 days: mini chart (horizontal bar or sparkline showing daily temps)
- Recent readings: list of TemperatureCard components
Components:
src/components/temperature/TempQuickLog.tsx— number input, method pills, submitsrc/components/temperature/TempCard.tsx— single reading displaysrc/components/temperature/TempChart.tsx— 7-day chart (pure CSS bars, no lib needed)src/components/temperature/FeverAlert.tsx— red alert banner with call button
Key UX:
- Default unit based on locale (°C for AU). Display toggle °C/°F.
- 38.0°C threshold → yellow warning. 38.5°C → red emergency.
- Number input: show decimal keyboard on mobile via
inputMode="decimal"
Feature 2: Contact Directory — /contacts
Page structure:
- Header: "Care Team" with Plus icon (right)
- Category filter tabs: All / Oncology / Hospital / Pharmacy / Insurance / Family
- Emergency contacts section at top (if any marked isEmergency)
- Contact cards: avatar circle (first letter), name, role, big green CALL button
- Tap card → expand to show all details
Components:
src/components/contacts/ContactCard.tsx— name, role, call button, expandablesrc/components/contacts/ContactForm.tsx— modal form for create/editsrc/components/contacts/CategoryTabs.tsx— horizontal scroll filter
Feature 3: Weight Log — /weight
Page structure:
- Header: "Weight" with History icon
- Quick log: large number input (kg), small toggle for kg/lbs, notes, "Log Weight" button
- Trend card: 30-day line chart (CSS-based or simple SVG)
- Alert card: if weight changed >2kg in 24hrs, show warning
- Recent readings: list
Components:
src/components/weight/WeightQuickLog.tsxsrc/components/weight/WeightCard.tsxsrc/components/weight/WeightChart.tsx— simple SVG line chartsrc/components/weight/WeightAlert.tsx— rapid change warning
Feature 4: Treatment Timeline — /timeline
Page structure:
- Header: "Treatment Journey" with Plus icon
- Progress bar at top: "Cycle 4 of 6 — 67% Complete" (calculated from completed/total milestones)
- Vertical timeline: milestones sorted by plannedDate
- Left: date
- Center: dot (green=completed, blue=scheduled, orange=delayed, gray=cancelled)
- Right: title, type badge, notes
- Bottom: "Add Milestone" button
Components:
src/components/timeline/TimelineView.tsx— vertical timeline layoutsrc/components/timeline/MilestoneCard.tsx— single milestonesrc/components/timeline/ProgressBar.tsx— overall progresssrc/components/timeline/MilestoneForm.tsx— modal for create/edit
Key UX:
- Completed milestones have a subtle celebration effect (checkmark)
- Auto-scroll to "now" position in timeline
- Color by type: blue=chemo, orange=surgery, purple=radiation, green=scan
Feature 5: Caregiver Tasks — /tasks
Page structure:
- Header: "Tasks" with Plus icon
- Filter tabs: My Tasks / All / Done
- Task list grouped by priority (Urgent at top)
- Each task: title, assignee avatar, due date, category chip, priority indicator
- Swipe right to complete (or tap checkbox)
- FAB or bottom "Add Task" button
Components:
src/components/tasks/TaskCard.tsx— task with checkbox, assignee, due datesrc/components/tasks/TaskForm.tsx— modal with assignee picker (workspace members)src/components/tasks/TaskFilters.tsx— status/assignee filter
Key UX:
- "Quick add" templates: "Pick up prescription", "Drive to appointment", "Prepare meals"
- Overdue tasks highlighted in accent/red
- Completion shows brief success animation
Feature 6: Lab Results — /lab-results
Page structure:
- Header: "Lab Results" with Plus icon
- Tab: Recent / Trends
- Recent tab: List of lab result cards sorted by date, showing panel name, date, flag count
- Trends tab: Marker selector (dropdown: WBC, RBC, Platelets, Hemoglobin, etc.) → SVG line chart with reference range shaded
- Add result: modal with panel template selector (CBC template pre-fills common markers)
Components:
src/components/labs/LabResultCard.tsx— panel summary with flagged values highlightedsrc/components/labs/LabResultForm.tsx— panel selector + marker rows (marker/value/unit/range)src/components/labs/LabTrendChart.tsx— SVG chart with ref range shadingsrc/components/labs/MarkerRow.tsx— single marker with flag coloring
Key UX:
- Pre-built panel templates: CBC (WBC, RBC, Hemoglobin, Hematocrit, Platelets), CMP, Liver, Tumor Markers
- Flag colors: green=normal, yellow=borderline, red=out of range, dark red=critical
- "Share with doctor" → links to print page
Feature 7: Medical Documents — /documents
Page structure:
- Header: "Documents" with Plus icon (upload)
- Category filter: All / Lab Reports / Scans / Insurance / Prescriptions
- Document grid: 2 columns, thumbnail (icon by type), title, date, category badge
- Tap → full-screen viewer (PDF in iframe, images native)
- Upload: file picker, category select, title, date, notes
Components:
src/components/documents/DocumentCard.tsx— thumbnail, title, category badgesrc/components/documents/DocumentUpload.tsx— file picker modalsrc/components/documents/DocumentViewer.tsx— full-screen view
Key UX:
- Max file size: 10MB
- Accepted types: PDF, JPG, PNG
- Expiry badge on insurance cards approaching expiry
- No offline sync for documents (too large) — show "Requires internet" badge
Feature 8: Drug Interaction Checker — /meds (integrated)
Not a separate page. Integrated into the medications section:
- "Check Interactions" button on meds list page
- Results shown as a modal/sheet with severity-colored cards
- Warning banner on individual medication detail pages if interactions exist
Implementation approach (simplified, no external API for v1):
- Ship with a local lookup table of ~200 common chemo drug interactions (JSON file)
src/lib/interactions/checker.ts— pure function that takes med names, returns known interactionssrc/lib/interactions/data.ts— curated interaction database- Can upgrade to external API (OpenFDA/RxNorm) later
Components:
src/components/medications/InteractionCheck.tsx— button + results modalsrc/components/medications/InteractionCard.tsx— severity badge, descriptionsrc/components/medications/InteractionBanner.tsx— warning on med detail
Implementation Order & Tasks
Batch 1: Schema & Infrastructure (do first)
-
Prisma schema migration
priority:1time:30min- files:
prisma/schema.prisma - Add all 8 models + User/Workspace/Medication relation updates
- Run
npx prisma migrate dev --name add-eight-features - Verify migration succeeds
- files:
-
Zod validation schemas
priority:1time:20min- files:
src/lib/validation/schemas.ts - Add all 8 schema definitions and type exports
- files:
-
Dexie DB version 3
priority:1time:20min- files:
src/lib/sync/db.ts - Add interfaces and version 3 stores
- files:
-
Sync ops expansion
priority:1time:15min- files:
src/lib/sync/manager.ts,src/lib/validation/schemas.ts - Add new entity types and op types to sync schema
- files:
Batch 2: Low Complexity Features (build fast)
-
Feature 1: Temperature Log
priority:2deps:Batch 1time:3hr- API:
src/app/api/workspaces/[id]/temperature/route.ts - API:
src/app/api/workspaces/[id]/temperature/[tempId]/route.ts - Components:
src/components/temperature/TempQuickLog.tsx - Components:
src/components/temperature/TempCard.tsx - Components:
src/components/temperature/TempChart.tsx - Components:
src/components/temperature/FeverAlert.tsx - Page:
src/app/(app)/temperature/page.tsx - Page:
src/app/(app)/temperature/history/page.tsx
- API:
-
Feature 2: Contact Directory
priority:2deps:Batch 1time:3hr- API:
src/app/api/workspaces/[id]/contacts/route.ts - API:
src/app/api/workspaces/[id]/contacts/[contactId]/route.ts - Components:
src/components/contacts/ContactCard.tsx - Components:
src/components/contacts/ContactForm.tsx - Components:
src/components/contacts/CategoryTabs.tsx - Page:
src/app/(app)/contacts/page.tsx
- API:
-
Feature 3: Weight Log
priority:2deps:Batch 1time:2.5hr- API:
src/app/api/workspaces/[id]/weight/route.ts - API:
src/app/api/workspaces/[id]/weight/[weightId]/route.ts - Components:
src/components/weight/WeightQuickLog.tsx - Components:
src/components/weight/WeightCard.tsx - Components:
src/components/weight/WeightChart.tsx - Components:
src/components/weight/WeightAlert.tsx - Page:
src/app/(app)/weight/page.tsx - Page:
src/app/(app)/weight/history/page.tsx
- API:
Batch 3: Medium Complexity Features
-
Feature 4: Treatment Timeline
priority:3deps:Batch 1time:4hr- API:
src/app/api/workspaces/[id]/milestones/route.ts - API:
src/app/api/workspaces/[id]/milestones/[milestoneId]/route.ts - Components:
src/components/timeline/TimelineView.tsx - Components:
src/components/timeline/MilestoneCard.tsx - Components:
src/components/timeline/ProgressBar.tsx - Components:
src/components/timeline/MilestoneForm.tsx - Page:
src/app/(app)/timeline/page.tsx
- API:
-
Feature 5: Caregiver Tasks
priority:3deps:Batch 1time:4hr- API:
src/app/api/workspaces/[id]/tasks/route.ts - API:
src/app/api/workspaces/[id]/tasks/[taskId]/route.ts - API:
src/app/api/workspaces/[id]/tasks/[taskId]/complete/route.ts - Components:
src/components/tasks/TaskCard.tsx - Components:
src/components/tasks/TaskForm.tsx - Components:
src/components/tasks/TaskFilters.tsx - Page:
src/app/(app)/tasks/page.tsx
- API:
Batch 4: High Complexity Features
-
Feature 6: Lab Results
priority:4deps:Batch 1time:5hr- Lib:
src/lib/labs/panels.ts(CBC, CMP, Liver panel templates) - API:
src/app/api/workspaces/[id]/lab-results/route.ts - API:
src/app/api/workspaces/[id]/lab-results/trends/route.ts - API:
src/app/api/workspaces/[id]/lab-results/[labId]/route.ts - Components:
src/components/labs/LabResultCard.tsx - Components:
src/components/labs/LabResultForm.tsx - Components:
src/components/labs/LabTrendChart.tsx - Components:
src/components/labs/MarkerRow.tsx - Page:
src/app/(app)/lab-results/page.tsx
- Lib:
-
Feature 7: Medical Documents
priority:4deps:Batch 1time:4hr- API:
src/app/api/workspaces/[id]/documents/route.ts(multipart upload) - API:
src/app/api/workspaces/[id]/documents/[docId]/route.ts(download + delete) - Components:
src/components/documents/DocumentCard.tsx - Components:
src/components/documents/DocumentUpload.tsx - Components:
src/components/documents/DocumentViewer.tsx - Page:
src/app/(app)/documents/page.tsx
- API:
-
Feature 8: Drug Interactions
priority:4deps:Batch 1time:3hr- Lib:
src/lib/interactions/data.ts(curated interaction database) - Lib:
src/lib/interactions/checker.ts(lookup logic) - API:
src/app/api/workspaces/[id]/medications/check-interactions/route.ts - Components:
src/components/medications/InteractionCheck.tsx - Components:
src/components/medications/InteractionCard.tsx - Components:
src/components/medications/InteractionBanner.tsx
- Lib:
Batch 5: Integration & Polish
-
Update Settings/More page
priority:5time:1hr- files:
src/app/(app)/settings/page.tsx - Add navigation links to all new features grouped by section
- files:
-
Update Today dashboard
priority:5time:2hr- files:
src/app/(app)/today/page.tsx - Add cards: latest temp, pending tasks, next milestone, weight trend
- Fever alert banner at top if applicable
- files:
-
Update EmptyState component
priority:5time:15min- files:
src/components/ui/states.tsx - Add new icon types: temperature, contacts, weight, timeline, tasks, labs, documents
- files:
-
Tests
priority:5time:2hr- Temperature threshold logic
- Weight change alert calculations
- Lab result flag detection
- Drug interaction checker
- All Zod schemas validation
Total Estimated Time
| Batch | Time |
|---|---|
| Batch 1: Schema & Infrastructure | ~1.5hr |
| Batch 2: Temperature + Contacts + Weight | ~8.5hr |
| Batch 3: Timeline + Tasks | ~8hr |
| Batch 4: Labs + Documents + Interactions | ~12hr |
| Batch 5: Integration & Polish | ~5hr |
| Total | ~35hr |
With parallel work on independent features, achievable in 4-5 focused days.