Files
Moonbase/CLAUDE.md
Bonna Moon 5319e8af2f v0.26.0 — Calendar: month view of all tasks + bloom/harvest windows
New /calendar page merges every Task (garden, home, equipment, animals)
into one month grid, color-coded by category, with a click-a-day detail
panel and inline Done (reuses /api/tasks/[id]/complete). Recurring tasks
project every occurrence in view via the new occurrencesInRange in
src/lib/tasks/schedule.ts (first tests for that module — 12, including
month-end clamping). Season chips per month come from PlantType
bloom/harvest windows via src/lib/garden/season.ts (monthInWindow,
year-wrap aware). Nav gains a Calendar item.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 16:06:58 -05:00

138 lines
7.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Moon Base — Project Notes
Property management for the Moon homestead (Eau Claire, WI). Next.js 14 + Prisma +
Postgres. Same conventions as FMB (`/Users/bonnamoon/Projects/fullmoonbakehouse`).
Repo on Gitea: **`bonna61/Moonbase`**. Live at NAS:8053. See `README.md` for the
user-facing overview.
## Modules (all live)
- **Garden** — Plant registry + care/harvest logs, plant **groups**, reusable
**PlantType** records, Wikipedia photos. Plants sit on the shared location tree.
Harvest logs carry structured `amount`+`unit` (legacy freeform `quantity` kept
for old rows — don't regress); a harvest can simultaneously create a pantry
item (`addPlantLog`'s `pantry` input → GARDEN-source consumable). PlantTypes
can link a UWMadison Extension article (`uwExtensionUrl/Title`), searched via
`/api/v1/plant-types/uw-search` (proxy to hort.extension.wisc.edu's WP REST
API; helper `src/lib/garden/uw-extension.ts`).
- **Inventory** — durable **Item**s that nest (`parentItemId`: a toolbox is an
Item that holds Items, with a cycle guard), with **Label**s (M:N via
`LabelOnItem`), `ItemAttachment` (PHOTO/MANUAL/RECEIPT/WARRANTY), and `ItemLog`.
Items carry value/purchase/warranty (incl. `lifetimeWarranty`, `insured`),
`serial`/`modelNumber`/`brand`, a **sold** lifecycle, and freeform `customFields`
(JSON, e.g. VIN). Maintenance = a `Task` linked via `Task.itemId`. **HomeBox CSV
import** at `/api/v1/items/import` (pure parser `src/lib/inventory/homebox-import.ts`).
`kind` is DURABLE|CONSUMABLE. Shares the Location spine.
- **Pantry** (Kitchen → Pantry & Freezer) — CONSUMABLE Items with categories,
price paid, tags (shared `Label` table), and expiry. Pulls go through
`/api/v1/pantry/[id]/use` or `/bulk-use` and write `ItemLog` type `USED` with a
structured `amount` (consumption is queryable — don't regress to NOTE strings).
Service `src/lib/services/pantry.ts`; pure logic + tests in `src/lib/pantry/core.ts`.
Rides the `items:*` scopes (pantry items ARE Items).
- **Animals** — `Animal` + `AnimalLog` (pets/livestock); placed on the Location
tree, and `Task`s can link to an animal (`Task.animalId`).
- **Locations** — `Location` tree (self-ref `parentId`) shared by plants, items, animals.
- **Reminders** — `Task` + `TaskCompletion`; recurrence in `src/lib/tasks/schedule.ts`
(incl. `occurrencesInRange` projection). **/calendar** renders a month view of
all tasks (garden + house) plus PlantType bloom/harvest windows
(`src/lib/garden/season.ts`).
- **REST API** — `/api/v1/*` authed by `ApiToken` (per-scope; logic in `src/lib/api/`).
- **Suggestions** — `@otm/account-panel` (Gitea npm registry) → files issues to the repo.
- **Backup & Restore** — `/api/admin/backup` + `/api/admin/restore` (admin only).
- **Self-update** — admin "Pull & rebuild" (see Deploy).
## Data model (the one rule)
**Two nesting worlds, crossed once.** *Places* (`Location`, self-ref `parentId`,
unlimited depth — `LocationKind` is an icon hint, never a constraint) nest in
places. *Items* (things you own) nest in items, and an Item lives in a Place **or**
inside another Item. You never put a Place inside an Item. **Plants** and
**Animals** are leaves in a Place (own logs, don't nest). HomeBox + Grocy + farmOS
collapse into: **Places · Plants/Animals (leaves) · Items (durable + consumable,
nesting)**. Logs are per-domain tables (`PlantLog`/`ItemLog`/`AnimalLog`), NOT a
polymorphic table; `Task` carries nullable `plantId`/`itemId`/`animalId`.
## Architecture conventions
- **API-first.** New modules (Inventory, Animals) are built on `/api/v1/*` and the
UI calls those same endpoints — no separate UI routes. Garden/Tasks predate this
and still use `/api/plants` etc.
- **Service layer** `src/lib/services/*` does DB work + AuditEvent, taking
`(ctx: AuthContext, input)`. Routes are thin: `authenticateRequest(req, scope)`
service → `handleApiError`. The MCP server (unbuilt) will reuse these services.
- **Auth helper** `src/lib/api/authenticate.ts` accepts EITHER a NextAuth cookie
session (full scope `["*"]`) OR a Bearer PAT (limited to its `scopes`; write
implies read). Scope constants in `src/lib/api/scopes.ts` (client-safe).
- **Decimals** for money/quantity (`@db.Decimal`) — convert to `Number()` before
passing to client components (Prisma Decimal isn't serializable across the boundary).
## Blocked on a domain + HTTPS (deferred)
Camera scanning/photo-capture (`getUserMedia` needs a secure context), the **PWA**
(service worker + install need HTTPS), and the **MCP server** (Claude needs a public
HTTPS URL) all wait until there's a domain + reverse-proxy/tunnel. The app is
LAN-only over `http://NAS:8053` today. Pantry/consumables and other pure web work
can ship before then.
## Local dev
- DB: `postgresql://tonym@localhost:5432/moonbase_dev`; copy `.env.example``.env`.
- `npm install`, `npm run dev` (3000). After schema edits: `npx prisma generate`.
- `npx prisma migrate deploy` provisions Bonna + Tony. `npm run db:seed` for demo
data (`SEED_MODE=minimal` = accounts only).
- Backfills for the location/plant-type refactors: `npm run db:backfill-locations`,
`npm run db:backfill-plant-types`.
## Deploy — git checkout on the NAS (NOT rsync)
> Working without this file (e.g. a Claude on another machine)? The self-contained
> version is `docs/git-deploy-guide.md`.
The NAS appdata (`/mnt/user/appdata/moonbase`) is a real **git checkout** tracking
`origin/master` (remote URL carries the token). Deploy by:
```sh
git push origin master
ssh root@192.168.0.5 'cd /mnt/user/appdata/moonbase && git pull && docker compose up -d --build'
```
Or admins click **Settings → Updates → Pull & rebuild**, which signals the
privileged `updater` sidecar (docker socket + adnanh/webhook) to run the same
`git pull && compose up -d --build app` detached; the UI polls `/api/version` and
reloads. **Never rsync over the appdata dir** — it fights the checkout.
- **Secrets** (`NEXTAUTH_SECRET`, `GITEA_TOKEN`, `UPDATER_SECRET`) live in a
gitignored host `.env` next to the compose file, interpolated in — never committed.
- **Docker runtime** mirrors FMB: `node:20-bookworm-slim` + openssl, runs as root,
full `node_modules`, `next start`. Migrations run on container start via the
entrypoint (waits for Postgres first). Image gotcha: don't let `npx prisma`
resolve at runtime — with no local prisma on PATH it pulls Prisma 7, which
rejects `url` in the datasource.
## Conventions (same as FMB)
- **Every commit bumps APP_VERSION** in `src/lib/version.ts` + a plain-English
entry atop `src/lib/changelog.ts` (the footer shows it + an "update available"
badge from `src/lib/update-check.ts`). Minor for features/migrations, patch for fixes.
**Fetch first**: parallel sessions push to master — check
`git show origin/master:src/lib/version.ts` before picking a number
(a v0.22.0 collision has already happened once).
- **AuditEvent on every mutation** — append-only.
- **Soft-delete** via `active=false`.
- **Pure core + colocated test** — logic in `src/lib/...` with a `.test.ts`; routes thin.
## Auth
- Username OR email login. Accounts `bonna` / `tony`, role `ADMIN`.
- `getSession()` / `requireAuth()` / `isAdmin()` in `src/lib/auth.ts`.
- **Forced password change is wired** (provisioned accounts ship `moonbase!` +
`mustChangePassword=true`): the dashboard layout redirects to `/change-password`
(outside the layout group; backed by `/api/account/change-password`). Don't
remove that redirect.
- `NEXTAUTH_SECRET` is asserted at runtime in production (skipped during build).
## Color palette
Forest / earth theme: `--canopy` (sidebar), `--leaf` (accent), `--soil` (text),
`--dew` (bg), `--ember` (destructive).