From c961e48953e1175dc3240463cf26ba3a0ea34818 Mon Sep 17 00:00:00 2001 From: fireshaper Date: Sun, 21 Jun 2026 23:29:56 -0400 Subject: [PATCH] Initial commit: MyKindlebox rabbitry management app React + Vite client and Express + SQLite/Drizzle server, plus memory-bank design docs. Co-Authored-By: Claude Opus 4.8 --- .claude/settings.json | 8 + .gitignore | 30 + CLAUDE.md | 81 + memory-bank/app-design-document.md | 385 ++ memory-bank/architecture.md | 98 + memory-bank/implementation-plan.md | 655 ++++ memory-bank/progress.md | 247 ++ memory-bank/tech-stack.md | 17 + mykindlebox/README.md | 3 + mykindlebox/client/.dockerignore | 2 + mykindlebox/client/.gitignore | 24 + mykindlebox/client/Dockerfile | 20 + mykindlebox/client/README.md | 16 + mykindlebox/client/eslint.config.js | 21 + mykindlebox/client/index.html | 36 + mykindlebox/client/nginx.conf | 38 + mykindlebox/client/package-lock.json | 3432 +++++++++++++++++ mykindlebox/client/package.json | 33 + mykindlebox/client/postcss.config.js | 6 + mykindlebox/client/public/favicon.svg | 1 + mykindlebox/client/public/icon-192.png | Bin 0 -> 944 bytes mykindlebox/client/public/icons.svg | 24 + mykindlebox/client/public/manifest.json | 16 + mykindlebox/client/public/sw.js | 39 + mykindlebox/client/src/App.jsx | 70 + mykindlebox/client/src/api.js | 100 + mykindlebox/client/src/auth.jsx | 103 + mykindlebox/client/src/authContext.js | 10 + mykindlebox/client/src/components/Badge.jsx | 29 + mykindlebox/client/src/components/Button.jsx | 21 + mykindlebox/client/src/components/Card.jsx | 10 + .../client/src/components/EmptyState.jsx | 11 + .../client/src/components/GeneticsForm.jsx | 65 + mykindlebox/client/src/components/Layout.jsx | 98 + .../client/src/components/LitterCard.jsx | 42 + .../client/src/components/LitterForm.jsx | 144 + .../client/src/components/PageHeader.jsx | 13 + .../client/src/components/PedigreeTree.jsx | 357 ++ .../src/components/PedigreeTreeReadOnly.jsx | 64 + .../client/src/components/QuickSearch.jsx | 222 ++ .../src/components/RabbitAutocomplete.jsx | 107 + .../client/src/components/RabbitCard.jsx | 46 + .../src/components/RabbitDetailsForm.jsx | 204 + .../client/src/components/ReviewStep.jsx | 75 + .../client/src/components/Silhouette.jsx | 9 + mykindlebox/client/src/hooks/useWeightUnit.js | 11 + mykindlebox/client/src/index.css | 163 + mykindlebox/client/src/lib/format.js | 55 + mykindlebox/client/src/lib/format.test.js | 37 + mykindlebox/client/src/lib/genetics.js | 109 + mykindlebox/client/src/lib/genetics.test.js | 47 + mykindlebox/client/src/lib/keyboardNav.js | 28 + mykindlebox/client/src/lib/pedigree.js | 243 ++ mykindlebox/client/src/lib/pedigree.test.js | 71 + mykindlebox/client/src/lib/theme.js | 92 + mykindlebox/client/src/main.jsx | 16 + mykindlebox/client/src/pages/AddBreeding.jsx | 67 + mykindlebox/client/src/pages/AddLitter.jsx | 63 + mykindlebox/client/src/pages/AddRabbit.jsx | 183 + mykindlebox/client/src/pages/Dashboard.jsx | 215 ++ mykindlebox/client/src/pages/EditLitter.jsx | 59 + mykindlebox/client/src/pages/EditRabbit.jsx | 121 + mykindlebox/client/src/pages/Genetics.jsx | 5 + mykindlebox/client/src/pages/Herd.jsx | 349 ++ mykindlebox/client/src/pages/LitterDetail.jsx | 165 + mykindlebox/client/src/pages/Litters.jsx | 46 + mykindlebox/client/src/pages/NotFound.jsx | 5 + .../client/src/pages/PedigreePrint.jsx | 259 ++ mykindlebox/client/src/pages/RabbitDetail.jsx | 261 ++ mykindlebox/client/src/pages/Settings.jsx | 286 ++ mykindlebox/client/tailwind.config.js | 31 + mykindlebox/client/vite.config.js | 18 + mykindlebox/docker-compose.yml | 26 + mykindlebox/server/.dockerignore | 2 + mykindlebox/server/Dockerfile | 24 + mykindlebox/server/auth.ts | 83 + mykindlebox/server/db/client.ts | 43 + .../db/migrations/0000_modern_power_pack.sql | 44 + .../migrations/0001_military_lord_tyger.sql | 11 + .../0002_luxuriant_the_stranger.sql | 4 + .../db/migrations/0003_add_breed_date.sql | 18 + .../db/migrations/meta/0000_snapshot.json | 334 ++ .../db/migrations/meta/0001_snapshot.json | 421 ++ .../db/migrations/meta/0002_snapshot.json | 445 +++ .../db/migrations/meta/0003_snapshot.json | 452 +++ .../server/db/migrations/meta/_journal.json | 34 + mykindlebox/server/db/schema.ts | 81 + mykindlebox/server/drizzle.config.ts | 10 + mykindlebox/server/index.ts | 56 + mykindlebox/server/package-lock.json | 3221 ++++++++++++++++ mykindlebox/server/package.json | 33 + mykindlebox/server/routes/auth.ts | 26 + mykindlebox/server/routes/export.ts | 13 + mykindlebox/server/routes/import.ts | 310 ++ mykindlebox/server/routes/litters.ts | 175 + mykindlebox/server/routes/rabbits.ts | 526 +++ mykindlebox/server/routes/settings.ts | 40 + mykindlebox/server/routes/stats.ts | 39 + mykindlebox/server/tsconfig.json | 15 + mykindlebox/server/utils/case.test.ts | 36 + mykindlebox/server/utils/case.ts | 37 + 101 files changed, 16596 insertions(+) create mode 100644 .claude/settings.json create mode 100644 .gitignore create mode 100644 CLAUDE.md create mode 100644 memory-bank/app-design-document.md create mode 100644 memory-bank/architecture.md create mode 100644 memory-bank/implementation-plan.md create mode 100644 memory-bank/progress.md create mode 100644 memory-bank/tech-stack.md create mode 100644 mykindlebox/README.md create mode 100644 mykindlebox/client/.dockerignore create mode 100644 mykindlebox/client/.gitignore create mode 100644 mykindlebox/client/Dockerfile create mode 100644 mykindlebox/client/README.md create mode 100644 mykindlebox/client/eslint.config.js create mode 100644 mykindlebox/client/index.html create mode 100644 mykindlebox/client/nginx.conf create mode 100644 mykindlebox/client/package-lock.json create mode 100644 mykindlebox/client/package.json create mode 100644 mykindlebox/client/postcss.config.js create mode 100644 mykindlebox/client/public/favicon.svg create mode 100644 mykindlebox/client/public/icon-192.png create mode 100644 mykindlebox/client/public/icons.svg create mode 100644 mykindlebox/client/public/manifest.json create mode 100644 mykindlebox/client/public/sw.js create mode 100644 mykindlebox/client/src/App.jsx create mode 100644 mykindlebox/client/src/api.js create mode 100644 mykindlebox/client/src/auth.jsx create mode 100644 mykindlebox/client/src/authContext.js create mode 100644 mykindlebox/client/src/components/Badge.jsx create mode 100644 mykindlebox/client/src/components/Button.jsx create mode 100644 mykindlebox/client/src/components/Card.jsx create mode 100644 mykindlebox/client/src/components/EmptyState.jsx create mode 100644 mykindlebox/client/src/components/GeneticsForm.jsx create mode 100644 mykindlebox/client/src/components/Layout.jsx create mode 100644 mykindlebox/client/src/components/LitterCard.jsx create mode 100644 mykindlebox/client/src/components/LitterForm.jsx create mode 100644 mykindlebox/client/src/components/PageHeader.jsx create mode 100644 mykindlebox/client/src/components/PedigreeTree.jsx create mode 100644 mykindlebox/client/src/components/PedigreeTreeReadOnly.jsx create mode 100644 mykindlebox/client/src/components/QuickSearch.jsx create mode 100644 mykindlebox/client/src/components/RabbitAutocomplete.jsx create mode 100644 mykindlebox/client/src/components/RabbitCard.jsx create mode 100644 mykindlebox/client/src/components/RabbitDetailsForm.jsx create mode 100644 mykindlebox/client/src/components/ReviewStep.jsx create mode 100644 mykindlebox/client/src/components/Silhouette.jsx create mode 100644 mykindlebox/client/src/hooks/useWeightUnit.js create mode 100644 mykindlebox/client/src/index.css create mode 100644 mykindlebox/client/src/lib/format.js create mode 100644 mykindlebox/client/src/lib/format.test.js create mode 100644 mykindlebox/client/src/lib/genetics.js create mode 100644 mykindlebox/client/src/lib/genetics.test.js create mode 100644 mykindlebox/client/src/lib/keyboardNav.js create mode 100644 mykindlebox/client/src/lib/pedigree.js create mode 100644 mykindlebox/client/src/lib/pedigree.test.js create mode 100644 mykindlebox/client/src/lib/theme.js create mode 100644 mykindlebox/client/src/main.jsx create mode 100644 mykindlebox/client/src/pages/AddBreeding.jsx create mode 100644 mykindlebox/client/src/pages/AddLitter.jsx create mode 100644 mykindlebox/client/src/pages/AddRabbit.jsx create mode 100644 mykindlebox/client/src/pages/Dashboard.jsx create mode 100644 mykindlebox/client/src/pages/EditLitter.jsx create mode 100644 mykindlebox/client/src/pages/EditRabbit.jsx create mode 100644 mykindlebox/client/src/pages/Genetics.jsx create mode 100644 mykindlebox/client/src/pages/Herd.jsx create mode 100644 mykindlebox/client/src/pages/LitterDetail.jsx create mode 100644 mykindlebox/client/src/pages/Litters.jsx create mode 100644 mykindlebox/client/src/pages/NotFound.jsx create mode 100644 mykindlebox/client/src/pages/PedigreePrint.jsx create mode 100644 mykindlebox/client/src/pages/RabbitDetail.jsx create mode 100644 mykindlebox/client/src/pages/Settings.jsx create mode 100644 mykindlebox/client/tailwind.config.js create mode 100644 mykindlebox/client/vite.config.js create mode 100644 mykindlebox/docker-compose.yml create mode 100644 mykindlebox/server/.dockerignore create mode 100644 mykindlebox/server/Dockerfile create mode 100644 mykindlebox/server/auth.ts create mode 100644 mykindlebox/server/db/client.ts create mode 100644 mykindlebox/server/db/migrations/0000_modern_power_pack.sql create mode 100644 mykindlebox/server/db/migrations/0001_military_lord_tyger.sql create mode 100644 mykindlebox/server/db/migrations/0002_luxuriant_the_stranger.sql create mode 100644 mykindlebox/server/db/migrations/0003_add_breed_date.sql create mode 100644 mykindlebox/server/db/migrations/meta/0000_snapshot.json create mode 100644 mykindlebox/server/db/migrations/meta/0001_snapshot.json create mode 100644 mykindlebox/server/db/migrations/meta/0002_snapshot.json create mode 100644 mykindlebox/server/db/migrations/meta/0003_snapshot.json create mode 100644 mykindlebox/server/db/migrations/meta/_journal.json create mode 100644 mykindlebox/server/db/schema.ts create mode 100644 mykindlebox/server/drizzle.config.ts create mode 100644 mykindlebox/server/index.ts create mode 100644 mykindlebox/server/package-lock.json create mode 100644 mykindlebox/server/package.json create mode 100644 mykindlebox/server/routes/auth.ts create mode 100644 mykindlebox/server/routes/export.ts create mode 100644 mykindlebox/server/routes/import.ts create mode 100644 mykindlebox/server/routes/litters.ts create mode 100644 mykindlebox/server/routes/rabbits.ts create mode 100644 mykindlebox/server/routes/settings.ts create mode 100644 mykindlebox/server/routes/stats.ts create mode 100644 mykindlebox/server/tsconfig.json create mode 100644 mykindlebox/server/utils/case.test.ts create mode 100644 mykindlebox/server/utils/case.ts diff --git a/.claude/settings.json b/.claude/settings.json new file mode 100644 index 0000000..8915afd --- /dev/null +++ b/.claude/settings.json @@ -0,0 +1,8 @@ +{ + "permissions": { + "allow": [ + "Bash(node -e ' *)", + "Bash(curl -s http://localhost:3001/api/rabbits)" + ] + } +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0e8e555 --- /dev/null +++ b/.gitignore @@ -0,0 +1,30 @@ +# Dependencies +node_modules/ + +# Build output +dist/ +build/ + +# Runtime data (SQLite DB + uploaded photos) — never commit the herd database +mykindlebox/server/data/ + +# Scratch data dirs (e.g. throwaway DB used to test imports) +.tmp-*/ + +# Environment / secrets +.env +.env.* +!.env.example + +# Logs +*.log +npm-debug.log* + +# Editor / OS +.DS_Store +Thumbs.db +.vscode/ +.idea/ + +# Claude Code local (machine-specific) settings +.claude/settings.local.json diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..188ba63 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,81 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +# IMPORTANT: +# Always read memory-bank/@architecture.md before writing any code. Include entire database schema. +# Always read memory-bank/@app-design-document.md before writing any code. +# After adding a major feature or completing a milestone, update memory-bank/@architecture.md. + +## Project status + +The application is built and lives under [mykindlebox/](mykindlebox/) — a `client/` (React + Vite) and a `server/` (Express + SQLite/Drizzle). See [memory-bank/progress.md](memory-bank/progress.md) for the phase-by-phase build log. The source-of-truth design docs are: + +- [memory-bank/app-design-document.md](memory-bank/app-design-document.md) — full product spec: pages, components, data models, UX decisions +- [memory-bank/tech-stack.md](memory-bank/tech-stack.md) — the chosen stack (summarized below) +- [memory-bank/architecture.md](memory-bank/architecture.md) — current architecture + database schema + +### Commands + +Backend (`mykindlebox/server`): + +- `npm run dev` — start the API on port 3001 with `tsx watch` (hot reload) +- `npm run build` — typecheck + compile to `dist/` +- `npm start` — run the compiled server (`node dist/index.js`) +- There is no test suite yet (`npm test` is a placeholder). + +Frontend (`mykindlebox/client`): + +- `npm run dev` — Vite dev server on port 5173 (proxies `/api` and `/uploads` to `localhost:3001`) +- `npm run build` — production build to `dist/` +- `npm run lint` — ESLint + +Deployment: `docker compose up --build` from `mykindlebox/` serves the app on port 8080 (nginx serves the built client and reverse-proxies `/api` + `/uploads` to the Node server). Set `MYKINDLEBOX_PASSWORD` (e.g. in `mykindlebox/.env`) to require a login; if unset, auth is disabled. + +### Conventions + +- The REST API speaks **snake_case** at the boundary; Drizzle columns are camelCase. `server/utils/case.ts` converts both directions — don't hand-write conversions. +- The client talks to the API over **relative URLs** (`/api`, `/uploads`). Never reintroduce a hardcoded `http://localhost:3001`; it breaks LAN/PWA access from other devices. + +## What MyKindlebox is + +A self-hosted rabbitry (rabbit breeding herd) management web app, replacing an existing PHP app (`belowland.com/rabbitsite`). It is single-user, self-hosted, and out of scope for v1 includes multi-tenancy, public listing pages, show tracking, and payments. + +The core differentiator: **adding a rabbit starts from a pedigree tree, not a blank form** — the 3-generation ancestor tree (sire/dam, their sires/dams) is the primary data-entry surface, with inline autocomplete search against existing herd records and graceful fallback to freeform text for unknown/bought-in ancestors. + +## Tech stack (planned) + +| Layer | Choice | +|---|---| +| Frontend | React + Vite (PWA-ready) | +| Routing | React Router v7 | +| State | Zustand (local UI state) + React Query (server sync) | +| Styling | Tailwind CSS with custom theme tokens | +| Backend | Node.js + Express (REST API) | +| Database | SQLite via Drizzle ORM (single-file DB) | +| Auth | Single-user session token (no OAuth) | +| Hosting | Docker Compose (Nginx + Node + SQLite volume) | + +## Data model + +There are only two core tables — no separate "Kit" table: + +- **Rabbit**: name, sex, dob, weight_lbs, status (`active`/`for_sale`/`on_hold`/`sold`/`deceased`/`other`), `sire_id`/`dam_id` (self-referencing FKs), and genetics loci fields (`locus_a/b/c/d/e/en/v`). +- **Litter**: `sire_id`/`dam_id`, `kindle_date`, `kit_count`, `notes`. + +Kits are full Rabbit records from birth, linked to a litter implicitly via matching `sire_id`/`dam_id`/DOB — the Litter detail page derives its kit list by querying Rabbits rather than a join table. When adding features touching litters or kits, preserve this derivation rather than introducing a Kit entity. + +Ancestor pedigree nodes that aren't in the DB are stored as freeform text rather than null FKs, so entry is never blocked by incomplete ancestor records. + +Weight is stored as a decimal float in lbs internally; lb+oz vs decimal display is a per-user setting applied at the read/write boundary, not stored separately. + +The genetics calculator's pairings are session-only and never persisted. + +## Key UX decisions to preserve + +- Pedigree-first entry flow (Step 1: pedigree tree → Step 2: rabbit details → Step 3: genetics loci, collapsible/optional → Step 4: review & save). +- Genetics section defaults collapsed on Add, expanded on Edit if loci data already exists. +- Dashboard herd counts (For Sale/On Hold/Sold/etc.) are derived live from the Rabbit `status` field, not cached counters. +- Mobile bottom nav's "Add" tab opens the rabbit entry flow directly. + + diff --git a/memory-bank/app-design-document.md b/memory-bank/app-design-document.md new file mode 100644 index 0000000..a952482 --- /dev/null +++ b/memory-bank/app-design-document.md @@ -0,0 +1,385 @@ +# MyKindlebox — App Design Document + +**Version:** 1.0 +**Status:** Draft +**Last Updated:** 2026-06-19 + +--- + +## Overview + +MyKindlebox is a self-hosted rabbitry management web application. It replaces the existing Kindlebox PHP app at `belowland.com/rabbitsite` with a modern, responsive React/Node stack while preserving all the core domain knowledge baked into the original — particularly the genetics system and pedigree-aware entry flow. + +The defining improvement over the current site: **adding a new rabbit starts from a pedigree template, not a blank form.** The pedigree tree is the primary data entry surface. + +--- + +## Goals + +- Make herd entry fast — one rabbit in under 60 seconds for known bloodlines +- Maintain the full genetics model (A/B/C/D/E/En/V loci) +- Track litters, kits, weights, ages, and sale status in one place +- Mobile-friendly for barn use (phone in hand, cage in front of you) +- Self-hostable with a simple Docker or Node+SQLite setup + +--- + +## Out of Scope (v1) + +- Multi-user/multi-rabbitry support +- Public-facing listing pages +- Show tracking (planned for a future release) +- Payments or invoicing + +--- + +## Tech Stack + +| Layer | Choice | Notes | +|---|---|---| +| Frontend | React + Vite | PWA-ready | +| Routing | React Router v7 | | +| State | Zustand + React Query | Local UI state + server sync | +| Styling | Tailwind CSS | Custom theme tokens | +| Backend | Node.js + Express | REST API | +| Database | SQLite (via Drizzle ORM) | Single-file DB, easy backup | +| Auth | Single-user session token | Self-hosted, no OAuth complexity | +| Hosting | Docker Compose | Nginx + Node + SQLite volume | + +--- + +## Visual Design + +### Design Direction + +The palette pulls from the working barn — weathered cedar, hay, and deep forest green. Not the typical dark-green-navbar rabbitry website, but a warm neutral base with earthy sage accents and a muted amber highlight. Typography pairs **DM Sans** (headings, data labels) with **Inter** (body, forms) — legible at small sizes and clean on mobile. + +The signature element: the **pedigree entry tree** rendered as an actual interactive branching diagram, not a flat form. It should feel like filling out a paper pedigree slip, but faster. + +### Color Tokens + +| Token | Hex | Usage | +|---|---|---| +| `--surface` | `#F7F3EC` | Page background | +| `--surface-raised` | `#FFFFFF` | Cards, modals | +| `--surface-sunken` | `#EDE8DE` | Input backgrounds, zebra rows | +| `--border` | `#D4C9B4` | Dividers, input borders | +| `--text-primary` | `#1C1A16` | Body text | +| `--text-secondary` | `#6B6354` | Labels, secondary info | +| `--accent` | `#4A6741` | Primary actions (sage green) | +| `--accent-hover` | `#3A5233` | Button hover | +| `--accent-warm` | `#C47D2E` | Highlights, status badges | +| `--danger` | `#B34040` | Delete, warnings | + +### Typography Scale + +``` +Display: DM Sans, 700, 28px / 32px +Heading: DM Sans, 600, 20px / 24px +Subhead: DM Sans, 500, 15px / 20px +Body: Inter, 400, 14px / 21px +Label: Inter, 500, 12px / 16px, uppercase, 0.05em tracking +Data: Inter, 400 / 600, 13px / 18px (tabular nums) +``` + +--- + +## Information Architecture + +``` +/ Dashboard +/herd Herd view (all / bucks / does / other / kits) +/herd/:id Rabbit detail page +/herd/add Add rabbit (pedigree-first form) +/herd/:id/edit Edit rabbit +/litters Litter list +/litters/:id Litter detail (kit list) +/litters/add Record a new litter +/genetics Genetics calculator +/settings App settings (rabbitry name, backup/restore) +``` + +--- + +## Navigation + +Persistent left sidebar on desktop (240px), bottom nav bar on mobile (5 tabs). + +**Sidebar items:** +- Dashboard +- Herd *(with sub-filter: All / Bucks / Does / Kits / Other)* +- Litters +- Genetics Calculator +- Settings + +**Mobile bottom nav:** Dashboard · Herd · Litters · Add · Settings + +The **Add** tab in mobile nav opens the Rabbit entry flow directly — primary action is always one tap away. + +--- + +## Pages & Components + +### Dashboard + +Replaces the current dashboard's plain link grid with a live stats bar + action surface. + +**Stats bar (top):** + +``` +┌─────────────┬─────────────┬─────────────┬─────────────┐ +│ Herd 14 │ Bucks 7 │ Does 7 │ Kits 20 │ +├─────────────┼─────────────┼─────────────┼─────────────┤ +│ For Sale 21 │ On Hold 0 │ Sold 69 │ Others 586 │ +└─────────────┴─────────────┴─────────────┴─────────────┘ +``` + +Each stat card is a link to the corresponding filtered herd view. + +**Quick actions:** +- Add Rabbit +- Record Litter +- View Recent Activity (last 5 added/modified) + +**Recent litters widget:** shows the 3 most recent litters with kit count, date, and sire/dam names. + +--- + +### Herd View (`/herd`) + +Filterable grid/list of rabbits. Default: card grid, 2-col mobile / 3-col tablet / 4-col desktop. + +**Rabbit Card:** + +``` +┌────────────────────────────┐ +│ [photo or breed silhouette]│ +│ Jim Bean │ +│ Broken Black Tort • Buck │ +│ 5y 2m • 3.09 lbs │ +│ 6 litters • 18 kits │ +└────────────────────────────┘ +``` + +**Filters (top bar):** Gender tabs (All / Bucks / Does / Kits / Other) + sort (Name / Age / Recent / Weight) + search input. + +**List view toggle:** compact table view with name, color, sex, age, weight, litter count. Better for large herds. + +--- + +### Rabbit Detail (`/herd/:id`) + +Two-column layout on desktop, stacked on mobile. + +**Left column:** +- Photo (upload or breed silhouette placeholder) +- Core stats: name, farm, sex, DOB, age, weight, ear number, location +- Registration / GC numbers +- Color with genetics breakdown (loci pills) +- Status badge: Active / For Sale / On Hold / Sold / Deceased + +**Right column:** +- Pedigree tree (full 4-generation visual — root + 3 ancestor generations, 15 nodes — read-only, each ancestor with a DB record is a clickable link) +- Litter history table (for bucks and does — all litters they appear in) + +**Action bar:** Edit · Add Litter · Mark For Sale · Archive + +--- + +### Add / Edit Rabbit (`/herd/add`, `/herd/:id/edit`) + +**This is the core differentiator.** The entry form is organized as a pedigree tree first. + +#### Entry Flow + +**Step 1 — Pedigree Template** + +The user sees a 4-generation pedigree tree with empty nodes (root + 3 ancestor generations: parents, grandparents, great-grandparents). Each node is an inline autocomplete input. Typing searches existing rabbits in the DB and lets you select one, which auto-fills that branch. + +``` + ┌─ Sire's Sire's Sire + ┌─ Sire's Sire ─┤ + │ └─ Sire's Sire's Dam + ┌─ Sire ────┤ + │ │ ┌─ Sire's Dam's Sire + │ └─ Sire's Dam ──┤ + │ └─ Sire's Dam's Dam +[New Rabbit]─┤ + │ ┌─ Dam's Sire's Sire + │ ┌─ Dam's Sire ──┤ + │ │ └─ Dam's Sire's Dam + └─ Dam ─────┤ + │ ┌─ Dam's Dam's Sire + └─ Dam's Dam ───┤ + └─ Dam's Dam's Dam +``` + +- Selecting an existing rabbit for Sire auto-fills their sire/dam into the next generation if known. +- "Other" and "Unknown" remain as options, just like the original site. +- Clicking any empty ancestor node offers: **Search existing** · **Enter manually** · **Unknown** + +**Step 2 — Rabbit Details** + +Once the pedigree is set (or skipped), a standard form collects: + +| Field | Input Type | Notes | +|---|---|---| +| Name | Text | Required | +| Farm / Prefix | Text | Pre-filled from settings default | +| Sex | Segmented control | Buck / Doe / Unknown | +| Date of Birth | Date picker | | +| Weight | Number + unit toggle (lbs/oz or kg) | | +| Ear Number | Text | | +| Location | Text / select | Cage/hutch label | +| Status | Select | Active / For Sale / On Hold / Sold | +| Color Name | Text | e.g. "Broken Black Tort" | +| Photo | File upload | Optional | + +**Step 3 — Genetics** + +Collapsible section. Renders the same A/B/C/D/E/En/V loci selectors from the original, but as a clean pill-group toggle UI instead of a dropdown list. + +``` +A Locus [AA Agouti] [aᵗa Tan] [aa Self] +B Locus [BB] [bb] +… +``` + +The selected loci auto-generate a color description string (read-only) that can be used as the color field suggestion. + +**Step 4 — Review & Save** + +Summary card showing all entered data before commit. One "Save Rabbit" button. + +--- + +### Litter List (`/litters`) + +Replaces the plain HTML table with a filterable card list. + +**Litter Card:** + +``` +┌──────────────────────────────┐ +│ Ragnar × Venus │ +│ May 16, 2025 • 1 kit │ +│ [View Kits] [Edit] │ +└──────────────────────────────┘ +``` + +Filters: year, sire, dam, kit count range. Sort by date (default desc). + +--- + +### Litter Detail (`/litters/:id`) + +- Sire + Dam links at top (with ages at time of litter) +- Kindle date, expected wean date, actual wean date +- Kit table: each kit row links to its full Rabbit record — name, sex, color, ear number, status, weight +- "Add Kit" button opens the Add Rabbit flow with sire, dam, and DOB pre-filled from the litter + +--- + +### Genetics Calculator (`/genetics`) + +Same functionality as the existing calculator. Modernized as an interactive two-panel form: + +- Left: Parent A genotype selectors +- Right: Parent B genotype selectors +- Center: Offspring probability table (live-updating as you change inputs) + +--- + +## Data Models + +### Rabbit + +```typescript +{ + id: number + name: string + farm: string | null + sex: 'buck' | 'doe' | 'unknown' + dob: string | null // ISO date + weight_lbs: number | null + ear_number: string | null + location: string | null + status: 'active' | 'for_sale' | 'on_hold' | 'sold' | 'deceased' | 'other' + color_name: string | null + photo_path: string | null + registration_number: string | null + gc_number: string | null + sire_id: number | null // FK → Rabbit + dam_id: number | null // FK → Rabbit + // Genetics loci + locus_a: string | null // 'AA' | 'aᵗa' | 'aa' + locus_b: string | null + locus_c: string | null + locus_d: string | null + locus_e: string | null + locus_en: string | null + locus_v: string | null + created_at: string + updated_at: string +} +``` + +### Litter + +```typescript +{ + id: number + sire_id: number | null // FK → Rabbit + dam_id: number | null // FK → Rabbit + kindle_date: string // ISO date + kit_count: number + notes: string | null + created_at: string +} +``` + +> Kits are standard Rabbit records linked back to their litter via `sire_id` / `dam_id`. No separate Kit table. The Litter detail page queries all Rabbit records whose sire/dam match the litter's parents and whose DOB matches the kindle date. + +--- + +## Key UX Decisions + +### Pedigree-first entry over blank forms + +The original site's Add Rabbit form is a flat list of fields. Connecting a rabbit to its lineage is an afterthought. In MyKindlebox, the pedigree tree *is* the form. This mirrors how breeders actually think about a new animal — always in the context of its parents. + +### Inline ancestor lookup with graceful fallback + +The pedigree tree nodes use an autocomplete that searches the existing rabbit DB. If the ancestor isn't in the system (bought-in or historical), users can type freeform text for the ancestor's name and it stores as a text reference rather than a FK. This avoids blocking entry on having complete records. + +### Genetics section is collapsible and optional + +Not every breeder uses the full loci system. The genetics section defaults to collapsed on add; open on edit for rabbits that already have genetics data. + +### Weight in lb+oz or decimal lbs + +The original uses decimal lbs. The app will support both entry modes (3 lbs 2 oz or 3.13 lbs) because breeders use both on show tables. + +### Rabbit status drives herd counts + +The Dashboard stat cards ("For Sale", "On Hold", etc.) are driven entirely by the `status` field on the Rabbit record. Changing status anywhere instantly updates the counts. + +--- + +## Future Considerations (v2+) + +- **Show tracking** — log ARBA show entries and placements per rabbit; mobile-optimized for live entry at shows +- **Health log** — vet visits, treatments, notes per rabbit +- **Breeding calendar** — planned breeding dates, expected kindle dates, reminders +- **Export** — CSV export of herd, printable pedigree PDF generation +- **Photo gallery** — multiple photos per rabbit, breed standard comparison overlay +- **Public herd page** — optional read-only share link for buyers browsing available rabbits + +--- + +## Resolved Decisions + +1. **Kit records** — Kits are created as full Rabbit records immediately when added to a litter. No separate "kit row" stage; every kit is a first-class herd entry from birth. +2. **Genetics calculator pairings** — Session-only. Pairings are not persisted to the DB. +3. **Weight storage** — Stored internally as a decimal float in lbs. Display and entry format (decimal lbs vs lb+oz) is controlled by a user setting in the app and converted on read/write. +4. **Backup/restore** — Manual export button in Settings. Exports a JSON or SQLite snapshot; no scheduled automation. diff --git a/memory-bank/architecture.md b/memory-bank/architecture.md new file mode 100644 index 0000000..53931d2 --- /dev/null +++ b/memory-bank/architecture.md @@ -0,0 +1,98 @@ +# MyKindlebox — Architecture + +**Audience:** AI developers. This file explains what each file/directory does and why, as the codebase grows. Keep it current — update after each major feature or milestone per `CLAUDE.md`. + +--- + +## Repo layout + +``` +mykindlebox/ + server/ — Express + TypeScript REST API, SQLite via Drizzle ORM + client/ — React + Vite frontend +docker-compose.yml — orchestrates both services for self-hosted deployment +``` + +## `mykindlebox/server` + +| File | Purpose | +|---|---| +| `index.ts` | Entry point. Creates the Express app, applies `cors()` (allow-all — added in Phase 4 Step 4.3 so the Vite dev server on port 5173 can call this API cross-origin; revisit with a stricter allowlist once auth exists) and `express.json()`, imports `db/client` (so the DB connection/file is initialized on startup), mounts all routers, and starts listening on port 3001. | +| `db/client.ts` | Opens the SQLite connection and exports the Drizzle `db` instance used by all query code. Ensures `server/data/` exists before opening the file — `better-sqlite3` will not create missing parent directories on its own. The DB file lives at `server/data/mykindlebox.db`. On startup it now (Step 2.5) calls `drizzle-orm/better-sqlite3/migrator`'s `migrate()` to apply any pending migrations, then seeds the two default `settings` rows (`rabbitry_name`, `weight_unit`) via raw `INSERT OR IGNORE` keyed on the `key` primary key — idempotent across restarts. Also exports `dataDir` and `uploadsDir` (`data/uploads/`, created here) so other modules (`routes/rabbits.ts`'s multer config, `index.ts`'s static file serving) share one canonical path instead of each recomputing it. | +| `db/schema.ts` | Drizzle schema definitions: `rabbits` (24 base fields, 7 genetics loci columns, 14 chain-authority `_name_text` ancestor columns; `sex`/`status` enforced via SQLite `CHECK` constraints, not just TS union types; `sire_id`/`dam_id` self-referencing FKs), `litters` (`sire_id`/`dam_id` FKs to `rabbits.id`, `breed_date`, `kindle_date`, `kit_count`, `notes`, `created_at` — a litter row spans the breeding→kindle lifecycle: `breed_date` is set when a breeding is recorded, `kindle_date` is nullable until the doe kindles; at least one of the two is required, enforced in the route), and `settings` (`key` text primary key, `value` text — simple key-value store, not a typed config object). | +| `db/migrations/` | Generated by `drizzle-kit generate`, applied either manually via `drizzle-kit migrate` (CLI) or automatically on server startup via `db/client.ts`'s in-code `migrate()` call. Both share the same `__drizzle_migrations` tracking table, so there's no double-apply risk mixing the two paths. Contains the SQL migration files (`0000_modern_power_pack.sql` — rabbits, `0001_military_lord_tyger.sql` — litters, `0002_luxuriant_the_stranger.sql` — settings) — these are committed and intentionally **not** excluded by `.dockerignore` (only `node_modules` and `data` are). | +| `drizzle.config.ts` | Points `drizzle-kit` at `./db/schema.ts` for migration generation and `./data/mykindlebox.db` as the target DB. | +| `tsconfig.json` | TypeScript config: CommonJS module output (matches `package.json`'s `"type": "commonjs"`), strict mode on, compiles to `dist/`. | +| `package.json` scripts | `dev` — `tsx watch index.ts` (hot-reload local dev, no separate build step needed). `build` — `tsc` compiles to `dist/`. `start` — runs the compiled output, used in production/Docker. | +| `Dockerfile` | Builds a Node 20 Alpine image, installs deps, runs `npm run dev` (tsx) inside the container — not yet switched to a production build for the container; revisit when deploying for real. | +| `.dockerignore` | Excludes `node_modules` and `data/` from the Docker build context. | +| `routes/rabbits.ts` | Express `Router` mounted at `/api/rabbits` in `index.ts`. `GET /` (list, with `sex`/`status`/`search`/`sort` filters — `sex=kits` is a special case matching any sex with `dob` in the last 56 days), `GET /search` (autocomplete by *substring* on name only, must be registered before `GET /:id`), `GET /quick-search` (the global top-of-page quick-search bar — distinct from `/search`: a **prefix** match — "wildcard at the end" — across a chooseable subset of `farm`/`name`/`ear_number`/`color` via `?fields=` comma-list, defaulting to all four; returns each hit plus a `matched_fields` array re-derived in JS so the dropdown can label which filter the row flagged on. Empty `q` or no valid fields → `[]`. Also must be registered before `GET /:id`), `GET /:id` (404 if missing), `POST /` (validates `name`, 201 on success), `PATCH /:id` (partial update, refreshes `updated_at`), `DELETE /:id` (nulls referencing `sire_id`/`dam_id` before deleting), `GET /:id/ancestors` (nested 4-generation pedigree tree — see chain-authority implementation note below; each node carries `id`/`name`/`farm`/`sex`/`ear_number`/`weight_lbs`/`dob`/`color_name` — `ear_number`/`weight_lbs`/`dob` were added so the printable pedigree slip can show full per-ancestor detail), `POST /:id/photo` (multer disk upload, JPEG/PNG/WebP only, invoked manually inside the handler so `fileFilter` rejections become this route's own 400 rather than the global error handler's 500). | +| `routes/litters.ts` | Express `Router` mounted at `/api/litters`. `GET /` and `GET /:id` join `rabbits` twice (via `alias()`) to embed sire/dam `{ id, name }`. `GET /:id` derives its `kits` list from `rabbits` matching `sire_id`/`dam_id`/`dob` (the same no-Kit-table derivation the design doc specifies elsewhere). `POST /` (requires a `breed_date` or `kindle_date`; only generates kits when a `kindle_date` is present), `PATCH /:id`, `DELETE /:id`. Recording a breeding (`POST` with `breed_date`, no `kindle_date`) creates a litter-in-progress with no kits yet. | +| `routes/settings.ts` | Express `Router` mounted at `/api/settings`. `GET /` flattens the `settings` table into a single object; `PATCH /` rejects any request containing a key outside the `rabbitry_name`/`weight_unit` allowlist. No casing conversion needed — settings keys are stored and read as snake_case directly. | +| `routes/stats.ts` | Express `Router` mounted at `/api/stats`. Returns dashboard counts (`herd`, `bucks`, `does`, `kits`, `for_sale`, `on_hold`, `sold`, `archived`) computed live from `rabbits` on every request — no cached counters. `kits` reuses the same 56-day `dob` window as the rabbits list's `sex=kits` filter; `archived` sums `status IN ('other', 'deceased')`. | +| `routes/import.ts` | Express `Router` mounted at `/api/import` (behind `requireAuth`). `POST /` accepts a single multipart `file` (multer **memory** storage, 10 MB cap) and **replaces the entire herd**: it wipes `litters` then `rabbits` and inserts the file's rabbits, all inside one `db.transaction` so a parse/insert failure rolls back and leaves existing data untouched. Auto-detects three shapes: **legacy CSV** (the old PHP Kindlebox export — header has `ID`/`GENDER`/`SIRE_ID`), **native CSV** (snake_case headers matching the API), and **JSON** (a rabbit array or `{ rabbits: [...] }`). Legacy mapping: `GENDER` 1→buck/0→doe; numeric `STATUS` via `LEGACY_STATUS_MAP` (0 Herd→active, 1 For Sale→for_sale, 2 Pending Sale→on_hold, 3 Sold→sold, 4 Other→other, 5 Dead→deceased — sourced from the original `editrabbit.php` status `` directly to a value that might be the string `"Unknown"` — it's not a valid date and the input will silently show blank, which is why the split exists. + - **Weight and DOB need real `