commit 5b2cc8754006b33358cfe1bce07cb51216114f22 Author: Stephan Kerkman Date: Wed May 27 19:23:20 2026 +0200 Add PlantDB design spec Co-Authored-By: Claude Sonnet 4.6 diff --git a/docs/superpowers/specs/2026-05-27-plantdb-design.md b/docs/superpowers/specs/2026-05-27-plantdb-design.md new file mode 100644 index 0000000..02b9449 --- /dev/null +++ b/docs/superpowers/specs/2026-05-27-plantdb-design.md @@ -0,0 +1,228 @@ +# PlantDB — Design Spec + +**Date:** 2026-05-27 +**Stack:** Django 5 + Bootstrap 5 + HTMX +**Deployment:** Docker Compose on homelab + +--- + +## 1. Overview + +PlantDB is a mobile-first family plant and flower registry. It tracks both indoor and outdoor plants, stores care information per species, surfaces a pruning calendar, and integrates with the Perenual API to auto-populate species data. No user accounts — a single shared instance for the whole family. + +--- + +## 2. Architecture + +``` +Browser (mobile-first) + │ Bootstrap 5 + HTMX (partial page updates) + ▼ +Nginx (reverse proxy + static/media files) + ▼ +Gunicorn + Django 5 + ▼ +SQLite (single file, Docker volume) + │ + └── Perenual API (external, cached on first lookup) +``` + +**Key principles:** +- Server-rendered Django templates throughout; HTMX handles dynamic interactions (species search live results, pruning log updates) without a separate API layer. +- Perenual API is called only once per species — results are cached in the `Species` table. No recurring API cost after initial lookups. +- All uploaded media (personal plant photos) stored in a Docker volume and served by Nginx. + +--- + +## 3. Data Model + +### `Species` +Populated from Perenual API or entered manually. One row per plant species. + +| Field | Type | Notes | +|---|---|---| +| `id` | PK | | +| `common_name` | CharField | | +| `scientific_name` | CharField | | +| `description` | TextField | | +| `watering` | CharField | e.g. "Weekly", "Twice a week" | +| `sunlight` | CharField | e.g. "Full sun", "Partial shade" | +| `shadow_tolerance` | CharField | e.g. "Low", "Medium", "High" | +| `max_height_cm` | IntegerField | nullable | +| `growth_rate` | CharField | e.g. "Slow", "Moderate", "Fast" | +| `growth_season` | CharField | e.g. "Spring–Summer" | +| `pruning_months` | JSONField | Default schedule, e.g. `[2, 6, 8]` — pre-fills the Plant field | +| `api_image_url` | URLField | Image from Perenual, nullable | +| `perenual_id` | IntegerField | Unique, nullable — prevents duplicate fetches | + +### `Plant` +One row per physical plant owned by the family. + +| Field | Type | Notes | +|---|---|---| +| `id` | PK | | +| `name` | CharField | User's nickname, e.g. "Big rose on back fence" | +| `species` | FK → Species | Nullable — plant can exist without a species link | +| `location` | CharField | Free text, e.g. "Living room windowsill" | +| `is_indoor` | BooleanField | | +| `pruning_months` | JSONField | This plant's actual pruning schedule — pre-filled from species, editable per plant | +| `photo` | ImageField | Personal photo, stored in media volume, nullable | +| `notes` | TextField | Free text, nullable | +| `date_added` | DateField | Auto-set on creation | + +### `PruningLog` +One row per pruning event, logged by the family. + +| Field | Type | Notes | +|---|---|---| +| `id` | PK | | +| `plant` | FK → Plant | | +| `pruned_on` | DateField | | +| `notes` | TextField | nullable | + +--- + +## 4. Pages & Views + +### Dashboard (home `/`) +- Summary tiles: total plant count, number of plants due for pruning this month. +- Upcoming pruning list: overdue plants first (red), then due this month (amber). +- Buttons: "View all plants", "Add plant". +- HTMX: none needed — static server render on load. + +### Plant List (`/plants/`) +- Scrollable list of all plants, each showing name, location, indoor/outdoor badge, and thumbnail. +- Search bar at top — HTMX live search filters the list on each keystroke (targets the list partial). +- Filter chips: All / Indoor / Outdoor. +- FAB-style "+" button links to Add Plant. + +### Plant Detail (`/plants//`) +- Full-width personal photo hero (falls back to API species image if no personal photo). +- Name, scientific name (italic), indoor/outdoor badge. +- Location line. +- Care chips: 💧 watering · ☀️ sunlight · 📏 max height. +- Species reference image (small, below care info) if API image available and personal photo also exists. +- Pruning strip: recommended months + last pruned date. Amber highlight if due this month, red if overdue. +- "Log pruning" button → inline HTMX form (date + optional notes), updates pruning strip on success. +- Pruning history accordion: list of all `PruningLog` entries for this plant. +- Edit and Delete actions. + +### Add Plant (`/plants/add/`) +Two-step flow: + +**Step 1 — Species search (optional)** +- Search input with HTMX live lookup against Perenual API (debounced, 300ms). +- Results show common name, scientific name, thumbnail. +- Selecting a result caches the species in the DB (if not already cached) and pre-fills the form. +- "Skip — enter manually" link bypasses to step 2 with a blank form. + +**Step 2 — Plant form** +- Nickname field (required). +- Location field (free text). +- Indoor/Outdoor toggle. +- Care fields (watering, sunlight, etc.) — pre-filled from species if selected, all editable. +- Pruning months — pre-filled from species, shown as a 12-checkbox month picker. +- Photo section: "Take photo" (camera capture via ``). The species API image, if available, is always shown automatically on the plant detail as a reference — no separate action needed. +- Notes textarea. +- Save button. + +### Edit Plant (`/plants//edit/`) +Same form as Add, pre-populated. + +### Pruning Calendar (`/pruning/`) +- Grouped by urgency: + 1. **Overdue** (red) — plants with a recommended pruning month in the past and no log entry since. + 2. **This month** (amber) — plants with current month in `pruning_months`. + 3. **Next month** (grey) — plants due next month. + 4. **Later this year** — collapsed section, expandable. +- Each entry: plant name, location, recommended months, last pruned date. Tap to open plant detail. +- Month strip at top (scrollable) to jump to any month and see what's scheduled. + +### Species Detail (`/species//`) — optional read-only +- Shows all species info as reference. Linked from plant detail. Not editable directly (edit via the plant form). + +--- + +## 5. Perenual API Integration + +- **Endpoint used:** `GET https://perenual.com/api/species-list?q=&key=` +- **API key:** stored in Django settings via environment variable `PERENUAL_API_KEY`. +- **Cache strategy:** on first successful lookup, a `Species` row is created with `perenual_id` set. Any subsequent search that returns a species already in the DB skips the API entirely. +- **Free tier limit:** 100 requests/day. Given that lookups only happen when adding new plants (not on every page view), this is not a practical constraint for a family app. +- **Fallback:** if the API is unreachable or returns an error, the add-plant form stays open with an empty species form for manual entry. No hard dependency on the API. + +--- + +## 6. Pruning Logic + +A plant is considered **overdue** for pruning if: +- It has at least one `pruning_month` that fell within the past 12 months, AND +- There is no `PruningLog` entry with `pruned_on` falling within that month's window. + +A plant is **due this month** if the current month is in `Plant.pruning_months` and not yet logged this month. + +Using a 12-month rolling window (rather than calendar-year) correctly handles plants due in December when checked in January. + +The pruning calendar view computes this in a Django queryset annotated with the current date — no background tasks or scheduled jobs needed. + +--- + +## 7. Deployment + +### Docker Compose + +``` +services: + web: Django + Gunicorn + nginx: Reverse proxy + static/media serving + +volumes: + db: SQLite file + media: Uploaded plant photos +``` + +- Accessible on the local network via homelab IP or a local hostname (e.g. `plantdb.home`). +- No SSL required for LAN use. +- `docker compose up -d` starts everything; `docker compose down` stops it. +- Database backup: copy the SQLite volume file. Simple enough to add to an existing homelab backup routine. + +### Environment Variables + +| Variable | Purpose | +|---|---| +| `PERENUAL_API_KEY` | Perenual API key | +| `SECRET_KEY` | Django secret key | +| `DEBUG` | `False` in production | +| `ALLOWED_HOSTS` | Homelab hostname/IP | + +--- + +## 8. Static & Media Files + +- Static files (CSS, JS, Bootstrap): collected via `manage.py collectstatic`, served by Nginx. +- Media files (plant photos): stored in `/media/` inside the container, mounted as a Docker volume, served by Nginx under `/media/`. + +--- + +## 9. Dependencies + +| Package | Purpose | +|---|---| +| `django` | Web framework | +| `gunicorn` | WSGI server | +| `django-htmx` | HTMX integration helpers | +| `pillow` | Image handling for `ImageField` | +| `requests` | Perenual API calls | + +No frontend build step — Bootstrap 5 and HTMX loaded via CDN or bundled as static files. + +--- + +## 10. Out of Scope + +- User authentication / accounts +- Watering or feeding reminders +- Push notifications +- Multi-language support +- Public internet access / SSL (LAN only) +- Offline / PWA mode