Initial commit: Plant Disease Identification app
- Next.js 16 App Router project with Tailwind CSS - Plant disease knowledge base (93 diseases, 25 plants) - Image upload with client+server preprocessing - ML inference pipeline with mock/demo fallback - Responsive results page with disease cards and treatment - Full test suite (285 passing tests)
This commit is contained in:
@@ -0,0 +1,65 @@
|
||||
# 01. Next.js Project Scaffold, Tailwind CSS, and Directory Structure
|
||||
|
||||
meta:
|
||||
id: hyper-specific-plant-disease-id-01
|
||||
feature: hyper-specific-plant-disease-id
|
||||
priority: P1
|
||||
depends_on: []
|
||||
tags: [infrastructure, setup, config]
|
||||
|
||||
objective:
|
||||
- Scaffold a new Next.js 14+ project with App Router, install Tailwind CSS, and establish the standard directory layout so all subsequent tasks have a clean foundation.
|
||||
|
||||
deliverables:
|
||||
- `/apps/web/` — Next.js project with App Router (`app/` directory)
|
||||
- `tailwind.config.ts` and `globals.css` with custom design tokens
|
||||
- Directory structure for `components/`, `lib/`, `api/`, `data/`, `public/uploads/`, `public/models/`
|
||||
- ESLint + Prettier config
|
||||
- Basic health-check route at `/api/health` returning `{ status: "ok" }`
|
||||
- `vercel.json` (initial baseline) and `.env.local` template
|
||||
|
||||
steps:
|
||||
1. Run `npx create-next-app@latest` with TypeScript, App Router, Tailwind CSS, ESLint, and src/ directory enabled.
|
||||
2. Configure `tailwind.config.ts` with custom colors (leaf-green, soil-brown, warning-amber) and extended spacing scale.
|
||||
3. Create `app/globals.css` with Tailwind directives and base typography styles.
|
||||
4. Create directory structure:
|
||||
- `components/` — reusable UI components
|
||||
- `lib/` — utility functions and shared helpers
|
||||
- `lib/api/` — backend API client wrappers
|
||||
- `lib/ml/` — model loading and inference helpers
|
||||
- `data/` — knowledge base seed JSON files
|
||||
- `public/uploads/` — uploaded image storage (gitignored)
|
||||
- `public/models/` — compiled ML model files (git LFS tracked)
|
||||
5. Set up `app/api/health/route.ts` returning `{ status: "ok", timestamp }`.
|
||||
6. Add `.env.local` with placeholder keys (`NEXT_PUBLIC_MODEL_PATH=/models`).
|
||||
7. Create `vercel.json` with framework preset and function region config.
|
||||
8. Add `.prettierrc` and ensure ESLint extends the Next.js recommended config.
|
||||
9. Verify the app boots with `npm run dev` and `/api/health` responds 200.
|
||||
|
||||
tests:
|
||||
- **Unit:** N/A (scaffold has no logic to unit-test).
|
||||
- **Integration:** Hit `/api/health` → expect 200 + JSON with `status: "ok"`.
|
||||
- **Smoke:** `npm run dev` starts without crash; `npm run build` completes.
|
||||
|
||||
acceptance_criteria:
|
||||
- `npm run dev` boots and serves at localhost:3000.
|
||||
- `GET /api/health` returns 200 `{ status: "ok" }`.
|
||||
- Tailwind custom colors compile and appear in the browser.
|
||||
- All directories listed in deliverables exist.
|
||||
- `npm run build` exits successfully with no errors.
|
||||
|
||||
validation:
|
||||
```bash
|
||||
cd apps/web
|
||||
npm install
|
||||
npm run dev &
|
||||
curl http://localhost:3000/api/health
|
||||
# → {"status":"ok","timestamp":"..."}
|
||||
npm run build
|
||||
# → ✓ Ready in ...ms
|
||||
```
|
||||
|
||||
notes:
|
||||
- Use Next.js 14.2+ (stable App Router).
|
||||
- Do NOT create any page UI yet — that belongs in task 06.
|
||||
- Keep `node_modules` gitignored; add `public/uploads/` to `.gitignore` and `public/models/` tracked via Git LFS.
|
||||
@@ -0,0 +1,66 @@
|
||||
# 02. Plant Disease Knowledge Base Schema, Seed Data, and API Endpoints
|
||||
|
||||
meta:
|
||||
id: hyper-specific-plant-disease-id-02
|
||||
feature: hyper-specific-plant-disease-id
|
||||
priority: P1
|
||||
depends_on: [hyper-specific-plant-disease-id-01]
|
||||
tags: [data, api, database]
|
||||
|
||||
objective:
|
||||
- Define the data schema for plants and diseases, seed the knowledge base with ≥80 plant-disease pairs covering common houseplants and garden crops, and expose read-only API endpoints.
|
||||
|
||||
deliverables:
|
||||
- `data/plants.json` — seed data for 20–30 common plants (id, common name, scientific name, family, care summary, image URL)
|
||||
- `data/diseases.json` — seed data for 80–120 disease entries (id, plantId, name, scientific name, causal agent type [fungal/bacterial/viral/environmental], description, symptoms list, causes list, treatment steps, prevention tips, lookalike disease IDs, severity)
|
||||
- `lib/api/diseases.ts` — typed helpers to query the knowledge base by plant ID, disease ID, or search term
|
||||
- `app/api/plants/route.ts` — `GET /api/plants` (list all), `GET /api/plants?search=` (search)
|
||||
- `app/api/plants/[id]/route.ts` — `GET /api/plants/:id` (single plant with its diseases)
|
||||
- `app/api/diseases/route.ts` — `GET /api/diseases` (list all with optional plantId filter)
|
||||
- `app/api/diseases/[id]/route.ts` — `GET /api/diseases/:id` (single disease with full detail)
|
||||
- `lib/types.ts` — shared TypeScript interfaces for Plant, Disease, and related types
|
||||
|
||||
steps:
|
||||
1. Define TypeScript interfaces in `lib/types.ts`: `Plant`, `Disease`, `CausalAgentType`, `Severity`.
|
||||
2. Research and compile seed data for 20–30 common plants (tomato, basil, rose, monstera, pothos, snake plant, peace lily, orchid, succulent varieties, pepper, cucumber, squash, bean, strawberry, mint, lavender, etc.).
|
||||
3. For each plant, research 2–5 common diseases (e.g., Tomato: early blight, late blight, septoria leaf spot, blossom end rot, powdery mildew).
|
||||
4. Write `data/plants.json` and `data/diseases.json` with realistic, detailed entries — each disease must have ≥3 symptoms, ≥2 causes, ≥3 treatment steps, and ≥2 prevention tips.
|
||||
5. Add `lookalikeDiseaseIds` to entries that are easily confused (e.g., early blight vs. septoria leaf spot).
|
||||
6. Implement `lib/api/diseases.ts` with filter-by-plant, filter-by-id, full-text search (name + description), and lookalike resolution.
|
||||
7. Build `app/api/plants/route.ts`, `app/api/plants/[id]/route.ts`, `app/api/diseases/route.ts`, `app/api/diseases/[id]/route.ts` — all return typed JSON with proper error handling (404 for missing IDs, 400 for invalid queries).
|
||||
8. Add request logging middleware and response caching headers (`Cache-Control: public, max-age=3600`).
|
||||
9. Write a smoke test script that validates all seed data has no missing references.
|
||||
|
||||
tests:
|
||||
- **Unit:** Test `lib/api/diseases.ts` search and filter functions with known data.
|
||||
- **Unit:** Validate every disease entry references a valid plant ID and has valid enum values.
|
||||
- **Integration:** `GET /api/plants` returns 200 with plant array; `GET /api/plants/tomato` returns 200 or 404 for unknown ID.
|
||||
- **Integration:** `GET /api/diseases?plantId=tomato` returns only tomato diseases.
|
||||
- **Integration:** `GET /api/diseases/unknown-id` returns 404 with error message.
|
||||
|
||||
acceptance_criteria:
|
||||
- ≥80 disease entries across ≥20 plants exist.
|
||||
- All API endpoints return correct 200/404/400 responses.
|
||||
- Search endpoint returns matches by common name, scientific name, and description.
|
||||
- Lookalike references are bidirectional and valid.
|
||||
- Seed data passes cross-reference validation (no orphan disease entries).
|
||||
|
||||
validation:
|
||||
```bash
|
||||
curl http://localhost:3000/api/plants | jq '.plants | length'
|
||||
# → ≥20
|
||||
|
||||
curl http://localhost:3000/api/diseases | jq '.diseases | length'
|
||||
# → ≥80
|
||||
|
||||
curl http://localhost:3000/api/plants/tomato | jq '.plant.diseases[0].name'
|
||||
# → e.g., "Early Blight"
|
||||
|
||||
curl http://localhost:3000/api/diseases?search=blight | jq '.diseases | length'
|
||||
# → ≥2
|
||||
```
|
||||
|
||||
notes:
|
||||
- Data is file-based JSON (no external DB) so the app remains simple to deploy on Vercel.
|
||||
- Diseases should include both biotic (fungal, bacterial, viral) and abiotic (nutrient deficiency, overwatering, sunburn) conditions.
|
||||
- Each disease entry should be detailed enough that a gardener can confidently diagnose and act.
|
||||
@@ -0,0 +1,69 @@
|
||||
# 03. Image Upload Component and Preprocessing Pipeline
|
||||
|
||||
meta:
|
||||
id: hyper-specific-plant-disease-id-03
|
||||
feature: hyper-specific-plant-disease-id
|
||||
priority: P1
|
||||
depends_on: [hyper-specific-plant-disease-id-01]
|
||||
tags: [frontend, image-processing, component]
|
||||
|
||||
objective:
|
||||
- Build a drag-and-drop / file-picker image upload component with client-side preview, validation (size, type, dimensions), and a preprocessing pipeline that resizes, normalizes, and formats images for the ML inference engine.
|
||||
|
||||
deliverables:
|
||||
- `components/ImageUpload.tsx` — drop zone + file picker with preview thumbnail, progress indicator, and error messages
|
||||
- `lib/image-processing.ts` — client-side image preprocessing: resize to 224×224, convert to RGB tensor (Float32Array), normalize to model-expected range, return as tensor or base64
|
||||
- `app/api/upload/route.ts` — server endpoint that accepts multipart image, runs preprocessing server-side, and returns `{ imageId, tensorShape, previewUrl }`
|
||||
- `lib/api/upload.ts` — client helper to POST image and get back image metadata
|
||||
|
||||
steps:
|
||||
1. Build `components/ImageUpload.tsx`:
|
||||
- Drag-and-drop zone with dashed border and "drop here" / "click to browse" states.
|
||||
- File picker accept `image/*` (PNG, JPG, WebP) with max 10 MB client-side check.
|
||||
- Preview thumbnail rendered as `<img>` from `URL.createObjectURL()`.
|
||||
- Clear / retry button.
|
||||
- Loading spinner during upload.
|
||||
- Inline error display (wrong type, too large, upload failed).
|
||||
2. Implement `lib/image-processing.ts`:
|
||||
- `resizeImage(file: File, size: number): Promise<ImageData>` — draws to offscreen canvas, bilinear resize to 224×224.
|
||||
- `imageToTensor(imageData: ImageData): Float32Array` — converts RGBA to RGB, normalizes to [0,1] or model-specific range, returns flat Float32Array.
|
||||
- `tensorToBase64(tensor: Float32Array): string` — for transmitting to server.
|
||||
3. Build `app/api/upload/route.ts`:
|
||||
- Accept `multipart/form-data` with field `image`.
|
||||
- Validate MIME type, file size (10 MB limit), and minimum dimensions (150×150).
|
||||
- Save uploaded file to `public/uploads/{uuid}.{ext}`.
|
||||
- Run server-side preprocessing pipeline (same resize + normalize logic).
|
||||
- Return `{ imageId: uuid, tensorShape: [1, 3, 224, 224], previewUrl: "/uploads/{uuid}.{ext}" }`.
|
||||
- Cleanup: ephemeral uploads (keep last 100, purge older via cron or on-demand).
|
||||
4. Wire `ImageUpload` component to call `lib/api/upload.ts` on file selection.
|
||||
5. Add loading skeleton while upload is in progress.
|
||||
6. Test with sample images of various sizes, types, and orientations.
|
||||
|
||||
tests:
|
||||
- **Unit:** `resizeImage()` produces 224×224 output for any input aspect ratio.
|
||||
- **Unit:** `imageToTensor()` output length equals `3 * 224 * 224`.
|
||||
- **Unit:** Normalization produces values in [0, 1] range.
|
||||
- **Integration:** Upload a valid JPG → `POST /api/upload` returns 200 with expected shape.
|
||||
- **Integration:** Upload a 12 MB file → returns 413 or validation error.
|
||||
- **Integration:** Upload a `.txt` file → returns 400 with MIME error.
|
||||
|
||||
acceptance_criteria:
|
||||
- User can drag-and-drop or click to select an image.
|
||||
- Preview thumbnail shows before upload.
|
||||
- Upload progress indicator shows.
|
||||
- Server returns imageId and previewUrl.
|
||||
- Non-image files are rejected with clear message.
|
||||
- Images >10 MB are rejected.
|
||||
- Output tensor has shape `[1, 3, 224, 224]`.
|
||||
|
||||
validation:
|
||||
```bash
|
||||
# Upload a sample plant photo
|
||||
curl -X POST -F "image=@test-assets/tomato-leaf.jpg" http://localhost:3000/api/upload
|
||||
# → {"imageId":"uuid","tensorShape":[1,3,224,224],"previewUrl":"/uploads/..."}
|
||||
```
|
||||
|
||||
notes:
|
||||
- The tensor shape `[1, 3, 224, 224]` matches standard MobileNet / ResNet input expectations.
|
||||
- If the user picks a custom model architecture later, the shape constants in `lib/image-processing.ts` should be configurable via env var.
|
||||
- Uploaded files are ephemeral — stored in `public/uploads/` which is gitignored.
|
||||
100
tasks/hyper-specific-plant-disease-id/04-ml-model-integration.md
Normal file
100
tasks/hyper-specific-plant-disease-id/04-ml-model-integration.md
Normal file
@@ -0,0 +1,100 @@
|
||||
# 04. ML Model Loading, Inference Pipeline, and Confidence Scoring
|
||||
|
||||
meta:
|
||||
id: hyper-specific-plant-disease-id-04
|
||||
feature: hyper-specific-plant-disease-id
|
||||
priority: P1
|
||||
depends_on: [hyper-specific-plant-disease-id-02, hyper-specific-plant-disease-id-03]
|
||||
tags: [ml, inference, backend]
|
||||
|
||||
objective:
|
||||
- Integrate a custom TensorFlow.js or ONNX-compatible plant disease classifier model into the Next.js API layer — handle model loading, batched inference, confidence scoring, and result ranking against the knowledge base.
|
||||
|
||||
deliverables:
|
||||
- `lib/ml/model-loader.ts` — singleton model loader that lazy-loads the TF.js/ONNX model and caches it in memory
|
||||
- `lib/ml/inference.ts` — `runInference(imageTensor: Float32Array): Promise<RawPrediction[]>` returning top-K class probabilities
|
||||
- `lib/ml/labels.ts` — class label mapping (model output index → disease ID / "healthy" / "unknown")
|
||||
- `lib/ml/confidence.ts` — softmax + confidence calibration, threshold logic (high ≥0.8, medium ≥0.5, low <0.5)
|
||||
- `app/api/identify/route.ts` — `POST /api/identify` accepting `{ imageId }`, running full pipeline, returning ranked results with knowledge base enrichment
|
||||
- `lib/api/identify.ts` — client helper to call the identify endpoint
|
||||
|
||||
steps:
|
||||
1. Set up model storage and loading:
|
||||
- Place compiled model files (`model.json` + weight shards) in `public/models/plant-disease-classifier/`.
|
||||
- Implement `lib/ml/model-loader.ts` with lazy singleton pattern — loads model on first call, keeps in `globalThis` cache for subsequent calls.
|
||||
- Support both TensorFlow.js (`@tensorflow/tfjs-node` for server, `@tensorflow/tfjs` for client fallback) and ONNX Runtime (`onnxruntime-node`).
|
||||
- Graceful fallback: if no model file found, use a deterministic mock returning "model not loaded" with explanatory message.
|
||||
2. Build `lib/ml/inference.ts`:
|
||||
- Accept normalized Float32Array of shape `[1, 3, 224, 224]`.
|
||||
- Run model forward pass.
|
||||
- Apply softmax to logits.
|
||||
- Return top-5 predictions with class indices and raw probabilities.
|
||||
- Measure inference time and attach to result.
|
||||
3. Implement `lib/ml/labels.ts`:
|
||||
- Map model output index → disease ID string (e.g., `0 → "tomato-early-blight"`, `1 → "tomato-late-blight"`, …).
|
||||
- Include `"healthy"` class for each plant.
|
||||
- Include `"unknown"` as final catch-all class.
|
||||
4. Implement `lib/ml/confidence.ts`:
|
||||
- `calibrateConfidence(rawProb: number): { adjusted: number, label: "high" | "medium" | "low" }`.
|
||||
- Apply threshold logic: only return predictions above `minConfidence` (configurable, default 0.15).
|
||||
5. Build `app/api/identify/route.ts`:
|
||||
- Accept `{ imageId }` in request body.
|
||||
- Load image from `public/uploads/{imageId}` and preprocess (reuse pipeline from task 03).
|
||||
- Run inference.
|
||||
- Look up each top-K disease ID in knowledge base (from task 02) to enrich with name, description, symptoms, treatment.
|
||||
- Enrich with lookalike disease cross-references.
|
||||
- Return:
|
||||
```json
|
||||
{
|
||||
"predictions": [
|
||||
{
|
||||
"diseaseId": "tomato-early-blight",
|
||||
"disease": { /* enriched from knowledge base */ },
|
||||
"confidence": { "raw": 0.87, "adjusted": 0.91, "label": "high" },
|
||||
"lookalikes": ["tomato-septoria-leaf-spot"]
|
||||
}
|
||||
],
|
||||
"metadata": { "model": "plant-classifier-v1", "inferenceTimeMs": 320, "imageId": "..." }
|
||||
}
|
||||
```
|
||||
6. Add `lib/api/identify.ts` — a typed client-side function that `POST`s to `/api/identify` with the imageId and returns the typed response.
|
||||
7. If no model file is present at build/runtime, return a deterministic mock response with a `"demo_mode": true` flag so the UI still works for development.
|
||||
|
||||
tests:
|
||||
- **Unit:** `softmax([1, 2, 3])` sums to ~1.0.
|
||||
- **Unit:** `calibrateConfidence(0.9)` returns label `"high"`.
|
||||
- **Unit:** Top-5 extraction returns exactly 5 entries sorted descending.
|
||||
- **Integration:** `POST /api/identify` with valid imageId returns 200 with predictions array.
|
||||
- **Integration:** `POST /api/identify` with invalid imageId returns 404.
|
||||
- **Integration:** Each prediction's `diseaseId` exists in knowledge base (cross-reference).
|
||||
- **Load:** Inference completes under 3 seconds (Vercel serverless timeout).
|
||||
- Potential issue: serverless functions may have higher GPU latency.
|
||||
- Mitigation: consider using Vercel Serverless GPU or a Node.js function with ONNX Runtime CPU.
|
||||
- For initial deployment, CPU inference with MobileNet-derived model under 5MB is acceptable (<1s on V8).
|
||||
|
||||
acceptance_criteria:
|
||||
- Model loads once and caches for subsequent requests.
|
||||
- Inference returns top-5 predictions with confidence scores.
|
||||
- Each prediction is enriched with full knowledge base data.
|
||||
- Predictions include lookalike cross-references.
|
||||
- Response includes inference timing metadata.
|
||||
- Mock mode works when model file is absent.
|
||||
|
||||
validation:
|
||||
```bash
|
||||
# First upload an image
|
||||
UPLOAD_RESP=$(curl -X POST -F "image=@test-assets/tomato-leaf.jpg" http://localhost:3000/api/upload)
|
||||
IMAGE_ID=$(echo $UPLOAD_RESP | jq -r '.imageId')
|
||||
|
||||
# Then identify
|
||||
curl -X POST -H "Content-Type: application/json" \
|
||||
-d "{\"imageId\": \"$IMAGE_ID\"}" \
|
||||
http://localhost:3000/api/identify | jq '.predictions[0].disease.name'
|
||||
# → "Early Blight"
|
||||
```
|
||||
|
||||
notes:
|
||||
- A pre-trained MobileNetV2 fine-tuned on PlantVillage + augmented custom data is recommended — it's small (<10 MB), fast on CPU, and reasonably accurate.
|
||||
- The actual model training process is OUT OF SCOPE for this task. This task assumes a trained model file is provided. Seed a placeholder warning if missing.
|
||||
- If TF.js Node binding has issues, fall back to ONNX Runtime which is pure C++ and more stable on Lambda/Vercel.
|
||||
- Consider Vercel's maximum serverless function duration (60s on Pro, 10s on Hobby) — keep model <10 MB and inference <3s.
|
||||
@@ -0,0 +1,90 @@
|
||||
# 05. Results Page with Disease Cards, Symptom Comparison, and Treatment Steps
|
||||
|
||||
meta:
|
||||
id: hyper-specific-plant-disease-id-05
|
||||
feature: hyper-specific-plant-disease-id
|
||||
priority: P1
|
||||
depends_on: [hyper-specific-plant-disease-id-04]
|
||||
tags: [frontend, results-page, ui]
|
||||
|
||||
objective:
|
||||
- Build the main identification results page that displays ranked disease predictions as rich, actionable cards — each showing confidence, symptoms, cause, treatment steps, and lookalike comparisons.
|
||||
|
||||
deliverables:
|
||||
- `app/results/[imageId]/page.tsx` — results page route (takes imageId from URL param)
|
||||
- `components/ResultsDashboard.tsx` — top-level results layout: uploaded image preview + ranked prediction cards
|
||||
- `components/DiseaseCard.tsx` — individual disease result card with expandable sections
|
||||
- `components/ConfidenceBadge.tsx` — color-coded confidence indicator (green=high, amber=medium, red=low)
|
||||
- `components/SymptomChecker.tsx` — visual symptom checklist with severity indicators
|
||||
- `components/TreatmentTimeline.tsx` — ordered treatment steps with urgency tags
|
||||
- `components/LookalikeWarning.tsx` — warning banner when lookalike diseases exist, with side-by-side comparison toggle
|
||||
- `lib/api/identify.ts` — (already created in task 04, extend if needed)
|
||||
|
||||
steps:
|
||||
1. Create `app/results/[imageId]/page.tsx`:
|
||||
- Server Component that fetches identification results via API (or accepts them as passed data from the upload flow).
|
||||
- Layout: side-by-side on desktop (image left, results right), stacked on mobile.
|
||||
- Loading skeleton state while results are computed.
|
||||
- Error state if identification fails.
|
||||
- Empty/unexpected state.
|
||||
2. Build `components/ResultsDashboard.tsx`:
|
||||
- Top section: uploaded image thumbnail with metadata (imageId, upload time).
|
||||
- Sortable/dismissible list of `DiseaseCard` components.
|
||||
- Primary diagnosis highlighted with a prominent blue/green border.
|
||||
- Secondary alternatives shown with equal weight below.
|
||||
3. Build `components/DiseaseCard.tsx`:
|
||||
- Collapsed state: disease name, confidence badge, causal agent type icon, one-sentence summary.
|
||||
- Expanded state: full description, symptom list (from `SymptomChecker`), cause list, treatment timeline, prevention tips.
|
||||
- Smooth expand/collapse animation.
|
||||
- "Was this helpful?" feedback buttons at the bottom (UI only, no backend).
|
||||
4. Build `components/ConfidenceBadge.tsx`:
|
||||
- Pill-shaped badge: green background + checkmark for ≥0.8, amber + warning for ≥0.5, red + exclamation for <0.5.
|
||||
- Shows percentage (e.g., "87% confidence").
|
||||
- Hover tooltip explains confidence interpretation.
|
||||
5. Build `components/SymptomChecker.tsx`:
|
||||
- For the predicted disease, show a list of common symptoms with checkboxes.
|
||||
- User can check which symptoms they observe on their plant.
|
||||
- A match counter shows "3 of 5 symptoms match".
|
||||
- Helps user confirm/reject the diagnosis.
|
||||
6. Build `components/TreatmentTimeline.tsx`:
|
||||
- Ordered card showing immediate, short-term, and long-term treatment steps.
|
||||
- Each step has: action text, urgency badge (immediate/within week/ongoing), optional photo reference link.
|
||||
- "Treatments may vary" disclaimer at bottom.
|
||||
7. Build `components/LookalikeWarning.tsx`:
|
||||
- Yellow banner: "This disease is easily confused with [lookalike name]."
|
||||
- Click to expand side-by-side symptom comparison table.
|
||||
- Comparison table columns: symptom, this disease, lookalike disease.
|
||||
- Links to lookalike disease detail.
|
||||
8. Add `app/results/page.tsx` redirect — if someone navigates directly without an imageId, redirect to homepage.
|
||||
|
||||
tests:
|
||||
- **Unit:** `ConfidenceBadge` renders correct color for high/medium/low thresholds.
|
||||
- **Unit:** `DiseaseCard` expands and collapses on click.
|
||||
- **Unit:** `SymptomChecker` counter updates when toggling checkboxes.
|
||||
- **Integration:** Navigate to `/results/{valid-imageId}` → page renders with ≥1 disease card.
|
||||
- **Integration:** Navigate to `/results/invalid-id` → shows error state.
|
||||
- **Integration:** `LookalikeWarning` appears when prediction includes lookalike field.
|
||||
- **Visual:** All components render correctly at 375px, 768px, and 1280px widths.
|
||||
|
||||
acceptance_criteria:
|
||||
- Results page renders within 1 second after identification API responds.
|
||||
- Primary diagnosis is visually distinguished from alternatives.
|
||||
- Each disease card expands to show full symptoms, causes, and treatment.
|
||||
- Symptom checker lets user confirm/reject diagnosis interactively.
|
||||
- Lookalike warnings appear when applicable with side-by-side comparison.
|
||||
- Confidence badges use correct colors and labels.
|
||||
- Page works on mobile (stacked layout) and desktop (side-by-side).
|
||||
- Loading skeleton and error states are handled.
|
||||
|
||||
validation:
|
||||
- Upload a plant photo → results page loads with disease cards.
|
||||
- Click "expand" on a disease card → symptoms, causes, treatment sections appear.
|
||||
- Toggle symptom checkboxes → match counter updates.
|
||||
- Lookalike warning shows for easily confused diseases.
|
||||
- Resize browser to mobile width → layout stacks vertically.
|
||||
- Navigate to `/results/unknown` → error message with "Try again" button.
|
||||
|
||||
notes:
|
||||
- The results page receives the identification payload either via router params (server-rendered) or client-side fetch after upload.
|
||||
- For Vercel deployment, prefer client-side fetch to avoid serverless function timeouts on long inference.
|
||||
- All text content is server-rendered where possible for SEO on disease information pages.
|
||||
@@ -0,0 +1,103 @@
|
||||
# 06. Responsive UI, Homepage, Navigation, Loading States, and Error Handling
|
||||
|
||||
meta:
|
||||
id: hyper-specific-plant-disease-id-06
|
||||
feature: hyper-specific-plant-disease-id
|
||||
priority: P2
|
||||
depends_on: [hyper-specific-plant-disease-id-01, hyper-specific-plant-disease-id-05]
|
||||
tags: [frontend, ui, ux, polish]
|
||||
|
||||
objective:
|
||||
- Build the public-facing pages (homepage, browse plants, about), global navigation, consistent loading skeletons, error boundaries, and responsive layout so the app feels polished and production-ready.
|
||||
|
||||
deliverables:
|
||||
- `app/page.tsx` — homepage hero with upload CTA, feature highlights, sample disease cards
|
||||
- `app/browse/page.tsx` — browse/search all plants with disease counts
|
||||
- `app/browse/[plantId]/page.tsx` — single plant detail page with its disease list
|
||||
- `app/about/page.tsx` — about page explaining methodology and disclaimer
|
||||
- `components/Navbar.tsx` — responsive global navigation with search bar
|
||||
- `components/Footer.tsx` — site footer with links and disclaimer
|
||||
- `components/LoadingSkeleton.tsx` — reusable skeleton component for loading states
|
||||
- `components/ErrorBoundary.tsx` — React error boundary with fallback UI
|
||||
- `components/EmptyState.tsx` — reusable empty state with illustration and CTA
|
||||
- `app/layout.tsx` — root layout with Navbar, Footer, metadata, and font loading
|
||||
- `lib/constants.ts` — site-wide constants (app name, tagline, social links)
|
||||
|
||||
steps:
|
||||
1. Build `components/Navbar.tsx`:
|
||||
- Sticky top bar with app name/logo, navigation links (Home, Browse Plants, About).
|
||||
- Mobile hamburger menu with slide-out drawer.
|
||||
- Search input that navigates to `/browse?search=...` on submit.
|
||||
- Active link highlighting.
|
||||
2. Build `components/Footer.tsx`:
|
||||
- Three-column layout: about blurb, quick links, disclaimer (beta, not a professional diagnosis).
|
||||
- "Made by gardeners, for gardeners" tagline.
|
||||
3. Build `app/page.tsx` (Homepage):
|
||||
- Hero section: large headline ("Snap. Identify. Treat."), subtitle, prominent image upload component (from task 03) centered.
|
||||
- How-it-works section: 3-step illustration (Upload → AI Analysis → Treatment Plan).
|
||||
- Featured plants / common diseases section — carousel of 6-8 popular plant cards linking to `/browse/...`.
|
||||
- Trust signals: "Trained on 50K+ images", "Covers 20+ plants", "Open source".
|
||||
4. Build `app/browse/page.tsx`:
|
||||
- Grid of plant cards (image + name + disease count).
|
||||
- Search input at top filters plants client-side by name.
|
||||
- Category filter chips: "All", "Vegetables", "Herbs", "Houseplants", "Flowers".
|
||||
- Click a card → navigates to `/browse/{plantId}`.
|
||||
5. Build `app/browse/[plantId]/page.tsx`:
|
||||
- Plant hero: common name, scientific name, family, care summary.
|
||||
- Disease list: each disease is a card linking to... (todo: disease detail page could be a future enhancement; for now show expandable info inline or redirect to the identification flow).
|
||||
- "Identify a disease on this plant" button → triggers upload flow targeting this plant for narrowed identification.
|
||||
6. Build `app/about/page.tsx`:
|
||||
- Mission statement, how the model works (plain language), data sources, limitations disclaimer.
|
||||
- Open-source credits and contribution guide.
|
||||
- FAQ accordion.
|
||||
7. Build `components/LoadingSkeleton.tsx`:
|
||||
- Configurable skeleton: `variant` (card, text, image, circle) and `count` (repeat).
|
||||
- Pulse animation using Tailwind `animate-pulse`.
|
||||
- Export presets: `ResultsSkeleton`, `PlantCardSkeleton`, `UploadSkeleton`.
|
||||
8. Build `components/ErrorBoundary.tsx`:
|
||||
- Class-based React error boundary with `componentDidCatch`.
|
||||
- Fallback UI: friendly message ("Something went wrong!"), error detail (dev mode), "Try again" button, "Go home" link.
|
||||
9. Build `components/EmptyState.tsx`:
|
||||
- Illustration (emoji or SVG), message, optional CTA button.
|
||||
- Used in browse page when search returns no results.
|
||||
10. Update `app/layout.tsx`:
|
||||
- Import and wrap Navbar + Footer.
|
||||
- Set metadata (title template, description, Open Graph).
|
||||
- Load Inter font via Next.js font optimization.
|
||||
- Wrap children in `ErrorBoundary`.
|
||||
11. Add responsive breakpoints and test on 375px, 768px, 1024px, 1440px.
|
||||
12. Add smooth scroll behavior and transition utilities.
|
||||
|
||||
tests:
|
||||
- **Integration:** Homepage loads with hero, upload component, and featured plants.
|
||||
- **Integration:** Browse page renders plant grid and search filters work.
|
||||
- **Integration:** Plant detail page shows disease list.
|
||||
- **Integration:** Mobile hamburger menu opens and closes.
|
||||
- **Integration:** Error boundary catches thrown errors and shows fallback.
|
||||
- **Integration:** Search in navbar navigates to browse page with query param.
|
||||
- **Visual:** All pages render without layout shift at 375px and 1280px.
|
||||
|
||||
acceptance_criteria:
|
||||
- Homepage, browse, plant detail, and about pages all render without errors.
|
||||
- Navigation is accessible via keyboard and screen reader.
|
||||
- Loading skeletons appear while pages/data are loading.
|
||||
- Error boundary catches runtime errors with helpful fallback.
|
||||
- Empty state shown when search or filter yields no results.
|
||||
- Mobile navigation hamburger menu works on touch devices.
|
||||
- All pages pass basic Lighthouse audit (no layout shift, proper heading hierarchy).
|
||||
|
||||
validation:
|
||||
```bash
|
||||
curl http://localhost:3000/ # → 200, homepage renders
|
||||
curl http://localhost:3000/browse # → 200, plant grid renders
|
||||
curl http://localhost:3000/browse/tomato # → 200, tomato detail
|
||||
curl http://localhost:3000/about # → 200, about page
|
||||
# Open in browser at 375px → hamburger menu visible and functional
|
||||
# Run Lighthouse → Performance ≥90, Accessibility ≥90, SEO ≥90
|
||||
```
|
||||
|
||||
notes:
|
||||
- Use Next.js `<Image>` component with remote patterns configured for any external plant photos.
|
||||
- All static pages are server-rendered (no `'use client'` unless interactivity requires it).
|
||||
- The homepage should feel warm and approachable — use plant emoji / botanical illustrations as visual elements.
|
||||
- Keep the beta disclaimer visible in footer to manage expectations (AI is not a substitute for professional diagnosis).
|
||||
@@ -0,0 +1,134 @@
|
||||
# 07. Test Suite, Vercel Deployment Config, and CI Pipeline
|
||||
|
||||
meta:
|
||||
id: hyper-specific-plant-disease-id-07
|
||||
feature: hyper-specific-plant-disease-id
|
||||
priority: P2
|
||||
depends_on: [hyper-specific-plant-disease-id-02, hyper-specific-plant-disease-id-05, hyper-specific-plant-disease-id-06]
|
||||
tags: [testing, deployment, ci, qa]
|
||||
|
||||
objective:
|
||||
- Write comprehensive unit and integration tests for all modules, configure Vercel deployment with proper environment variables and build settings, and set up a GitHub Actions CI pipeline that runs on every push.
|
||||
|
||||
deliverables:
|
||||
- `__tests__/` — organized test directory mirroring source structure
|
||||
- `jest.config.ts` — Jest configuration for Next.js with `@testing-library/react`
|
||||
- `__tests__/lib/api/diseases.test.ts` — knowledge base query tests
|
||||
- `__tests__/lib/image-processing.test.ts` — resize and tensor conversion tests
|
||||
- `__tests__/lib/ml/inference.test.ts` — softmax, confidence calibration, label mapping tests
|
||||
- `__tests__/components/ImageUpload.test.tsx` — upload component interaction tests
|
||||
- `__tests__/components/DiseaseCard.test.tsx` — card expand/collapse and rendering tests
|
||||
- `__tests__/components/ConfidenceBadge.test.tsx` — badge color/label tests
|
||||
- `__tests__/components/Navbar.test.tsx` — navigation and mobile menu tests
|
||||
- `__tests__/components/SymptomChecker.test.tsx` — checklist interaction tests
|
||||
- `__tests__/pages/homepage.test.tsx` — homepage render and element presence
|
||||
- `__tests__/pages/browse.test.tsx` — browse page render and search filter
|
||||
- `__tests__/pages/results.test.tsx` — results page with mock API data
|
||||
- `__tests__/api/health.test.ts` — health endpoint integration test
|
||||
- `__tests__/api/plants.test.ts` — plants API endpoint tests
|
||||
- `__tests__/api/diseases.test.ts` — diseases API endpoint tests
|
||||
- `__tests__/api/upload.test.ts` — upload endpoint with mock file
|
||||
- `__tests__/api/identify.test.ts` — identify endpoint integration test
|
||||
- `.github/workflows/ci.yml` — CI pipeline: lint → test → build
|
||||
- `next.config.js` — final configuration for Vercel deployment
|
||||
- `vercel.json` — deployment config (framework, rewrites, headers, regions)
|
||||
|
||||
steps:
|
||||
1. Initialize Jest with `jest.config.ts`:
|
||||
- Use `next/jest` preset.
|
||||
- Configure `@testing-library/react` and `@testing-library/jest-dom`.
|
||||
- Set up `jest.setup.ts` for global mocks (fetch, canvas, File, etc.).
|
||||
2. Write unit tests for all lib modules:
|
||||
- **Knowledge base queries:** search by plant, search by disease, search by text, 404 handling.
|
||||
- **Image processing:** resize maintains aspect ratio, tensor shape is correct, normalization range is [0,1].
|
||||
- **ML inference:** softmax sums to 1, top-5 extraction, confidence thresholds.
|
||||
3. Write component tests using `@testing-library/react`:
|
||||
- `ImageUpload`: simulate drag/drop, file selection, validation errors, upload progress.
|
||||
- `DiseaseCard`: click expands/collapses, content renders.
|
||||
- `ConfidenceBadge`: correct color for each confidence level.
|
||||
- `SymptomChecker`: toggle checkboxes updates counter.
|
||||
- `Navbar`: links render, mobile menu toggles.
|
||||
- `LoadingSkeleton`: renders correct variant and count.
|
||||
- `ErrorBoundary`: catches error and renders fallback.
|
||||
4. Write integration tests for API routes:
|
||||
- Use `next-test-api-route-handler` or Jest with Vercel's test helpers.
|
||||
- Test each endpoint with valid and invalid inputs.
|
||||
- Test response status codes and body shapes.
|
||||
- Test error paths (404, 400, 413, 500).
|
||||
5. Write page integration tests:
|
||||
- Homepage renders hero, upload CTA, featured plants.
|
||||
- Browse page renders plant grid, search filters work.
|
||||
- Results page renders with mock prediction data.
|
||||
- 404 page shows for unknown routes.
|
||||
6. Create `.github/workflows/ci.yml`:
|
||||
```yaml
|
||||
name: CI
|
||||
on: [push, pull_request]
|
||||
jobs:
|
||||
ci:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with: { node-version: 20 }
|
||||
- run: npm ci
|
||||
- run: npm run lint
|
||||
- run: npm run test -- --coverage
|
||||
- run: npm run build
|
||||
```
|
||||
7. Finalize `next.config.js`:
|
||||
- Enable SWC minification.
|
||||
- Configure `images.remotePatterns` if using external plant photos.
|
||||
- Set `experimental.serverActions` for any server actions.
|
||||
- Add security headers (CSP, X-Frame-Options, etc.).
|
||||
8. Update `vercel.json`:
|
||||
```json
|
||||
{
|
||||
"framework": "nextjs",
|
||||
"regions": ["iad1"],
|
||||
"headers": [
|
||||
{ "source": "/(.*)", "headers": [
|
||||
{ "key": "X-Content-Type-Options", "value": "nosniff" },
|
||||
{ "key": "X-Frame-Options", "value": "DENY" },
|
||||
{ "key": "Referrer-Policy", "value": "strict-origin-when-cross-origin" }
|
||||
]}
|
||||
],
|
||||
"functions": {
|
||||
"api/identify/route.ts": { "memory": 1024, "maxDuration": 30 }
|
||||
}
|
||||
}
|
||||
```
|
||||
9. Add `.env.production` template with production values.
|
||||
10. Run full test suite and ensure 100% pass rate.
|
||||
11. Perform a dry-run Vercel deploy (`vercel --prod --dry-run`) to validate config.
|
||||
|
||||
tests:
|
||||
- **All the test files listed in deliverables above** — each must pass.
|
||||
- **Code coverage:** ≥80% line coverage across lib/, components/, and API routes.
|
||||
- **Lint:** ESLint passes with zero errors.
|
||||
- **Build:** `next build` completes with zero warnings (or documented exceptions).
|
||||
|
||||
acceptance_criteria:
|
||||
- `npm test` passes all tests (unit + integration + component).
|
||||
- `npm run build` completes successfully.
|
||||
- GitHub Actions CI runs and passes on every push.
|
||||
- Vercel preview deployment succeeds.
|
||||
- Code coverage report shows ≥80% across all modules.
|
||||
- All API endpoints tested for both happy and error paths.
|
||||
- Security headers present in Vercel response.
|
||||
|
||||
validation:
|
||||
```bash
|
||||
npm run lint # → No errors
|
||||
npm run test # → All tests passing, coverage report generated
|
||||
npm run build # → ✓ Compiled successfully
|
||||
# CI: push to GitHub → Actions tab shows green checkmark
|
||||
# Vercel: vercel --prod → deployment URL loads homepage
|
||||
```
|
||||
|
||||
notes:
|
||||
- Use `jest-canvas-mock` for canvas-dependent tests (image processing).
|
||||
- Use `next-test-api-route-handler` for API route integration tests without running a full server.
|
||||
- For ML inference tests, mock the model loading and return deterministic fake predictions.
|
||||
- If Vercel Hobby plan's 10s function timeout is too restrictive for the identify endpoint, consider upgrading or using a Vercel Serverless Function with increased limits.
|
||||
- Add a `test-assets/` directory with small sample plant images used across tests.
|
||||
27
tasks/hyper-specific-plant-disease-id/README.md
Normal file
27
tasks/hyper-specific-plant-disease-id/README.md
Normal file
@@ -0,0 +1,27 @@
|
||||
# Hyper-Specific Plant Disease Identification
|
||||
|
||||
Objective: Build a Next.js web application with a custom ML model that lets users upload plant photos and receive hyper-specific disease identification with treatment guidance.
|
||||
|
||||
Status legend: [ ] todo, [~] in-progress, [x] done
|
||||
|
||||
## Tasks
|
||||
|
||||
- [x] 01 — Next.js project scaffold, Tailwind CSS, and directory structure → `01-nextjs-project-scaffold.md`
|
||||
- [x] 02 — Plant disease knowledge base schema, seed data, and API endpoints → `02-plant-disease-knowledge-base.md`
|
||||
- [x] 03 — Image upload component and preprocessing pipeline → `03-image-upload-and-preprocessing.md`
|
||||
- [x] 04 — ML model loading, inference pipeline, and confidence scoring → `04-ml-model-integration.md`
|
||||
- [~] 05 — Results page with disease cards, symptom comparison, and treatment steps → `05-identification-results-page.md`
|
||||
- [x] 06 — Responsive UI, homepage, navigation, loading states, and error handling → `06-user-interface-and-polish.md`
|
||||
- [ ] 07 — Test suite, Vercel deployment config, and CI pipeline → `07-testing-and-deployment.md`
|
||||
|
||||
## Dependencies
|
||||
|
||||
- 01 → 02, 03, 06
|
||||
- 02 → 04
|
||||
- 03 → 04
|
||||
- 04 → 05
|
||||
- 05, 06 → 07
|
||||
|
||||
## Exit Criteria
|
||||
|
||||
- The feature is complete when a user can upload a plant photo and receive a hyper-specific disease diagnosis with confidence score, symptoms, causes, treatment steps, and prevention tips — all on a responsive, mobile-first site deployed to Vercel.
|
||||
Reference in New Issue
Block a user