Pantry now runs on the v1 API like Inventory/Animals: service layer in
src/lib/services/pantry.ts, pure logic + tests in src/lib/pantry/core.ts,
thin routes at /api/v1/pantry/* riding the items:* scopes. Pulls write
ItemLog type USED with a structured amount (queryable consumption).
Bulk-use runs its reads inside the transaction, accumulates repeated
pulls, and reports skipped items instead of silently dropping them —
the Pull dialog now surfaces those by name. Old /api/pantry routes
deleted.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adds a search box (matches name, location, and labels) and a Location/Label
grouping toggle to Inventory, and fixes the unstyled Suggestions page.
(Renumbered from 0.19.0 → 0.22.0: Pantry work took 0.19–0.21 in parallel.)
Hardening from a max-effort code review of the new grid:
- Contained items resolve to their container's effective location, so search
finds them and the flat view groups them under the right place (not "No
location").
- Location/label groups key on id, not name, so same-named places/labels stay
separate; synthetic bucket renamed "No labels" to avoid colliding with a real
label; fixed duplicate React keys and two empty-state regressions.
- deleteItem now reparents a deleted container's contents into its own spot
(its place or parent item) instead of orphaning them; the grid also treats
any still-orphaned child as top-level so nothing silently vanishes.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Trash icon on each row; clicking shows "Delete? Yes / No" inline
(no dialog needed). Soft-deletes via active=false. Removed the
admin-only guard on DELETE /api/pantry/[id] since anyone logged in
should be able to manage their own pantry.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
New "Pull from pantry" button opens a dialog where you search/filter
all items, type a quantity next to each one you're taking, pick or type
a reason, and submit all in one shot. Items with amounts entered
highlight; over-stock amounts show inline error. Backed by new
POST /api/pantry/bulk-use (transactional quantity deduction + ItemLog).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Uses existing Label/LabelOnItem tables — no schema change. Tags field
in the add/edit dialog: type + Enter/comma to add, backspace to remove,
autocomplete from previously-used labels. Tags display as chips on each
item row. API: POST/PATCH accept tags[], upsert Label rows, sync
LabelOnItem. Catalog endpoint also returns all label names for
autocomplete.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The dropdown already only shows Kitchen locations so the
"Kitchen › Freezer" prefix is redundant — just show "Freezer".
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Previously the "Add" option only appeared after typing a non-matching
query, which wasn't discoverable. Now the combobox always shows
"Add new…" at the bottom when allowCustom=true; clicking it focuses the
search input so you can type the new name.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Changed step from 0.25 to 1 on the Quantity and "how much are you
using" number inputs so the spinner arrows move by wholes. Added
noValidate on both forms (native step-mismatch validation would
otherwise block submitting typed partials like 2.5) plus manual
amount-bounds checks in the use dialog to replace the native min/max
constraints that noValidate disables.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Quick-pick chips (Family dinner, Dinner party, Taking on a trip, Meal
prep, Gave it away) in the Use dialog, alongside the existing freeform
notes field — picking one fills the note, no schema change needed.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Filter locations to Kitchen and its children (fridge, freezer, pantry
shelves, etc.). Display the full path so "Kitchen › Freezer" shows
instead of just "Freezer".
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Type a new name in the Category or Subcategory dropdown and pick
"Add '...'" at the bottom. Custom categories persist (stored as strings
on the item) and reappear merged into future dropdowns.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
#4 cleanup + Tony's house structure.
- Task.locationId (migration): reminders can attach to a place, not just a
plant/item/animal. Add one from a location's menu (e.g. clean gutters on House >
Exterior); the Reminders page + tasks service surface the linked location/item/
animal.
- Locations "Quick setup": paste/confirm an indented outline → creates the nested
tree (find-or-create by name+parent, idempotent), pre-filled with the Moon
homestead house layout. Optional "sweep existing garden areas under Yard" moves
every top-level place that holds plants under a new Yard parent — the
'move all the yard stuff to a new top level' ask.
- Pure outline parser (src/lib/locations/outline.ts, +4 tests); bulk setup service
+ /api/locations/bulk. Verified end-to-end: nested tree + paths, garden sweep,
place reminder.
Yard sub-bucketing deferred per Tony.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Addresses Gitea suggestions #3 and #2.
#3 Mobile: the sidebar was desktop-only (hidden md:flex), leaving phones with no
nav at all. Added a slide-out drawer (hamburger in the top bar, md:hidden) that
reuses the nav. Lifted navItems/settingsItems into src/components/layout/nav-items.ts
so the sidebar and mobile drawer share one source + active-state logic. No HTTPS
needed (pure responsive UI).
#2 Suggestions: the @otm/account-panel SuggestionsPanel hardcodes light neutral-*
text that assumes a white backdrop, so it washed out on the app theme — now
rendered on a white card for contrast in light/dark. Added a lightbulb button in
the header that jumps to Suggestions (like FMB).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- README/CLAUDE.md: Inventory depth (warranty/value/maintenance/sold/custom fields,
HomeBox CSV import), Locations page, the two-world data-model rule, API-first +
service-layer conventions, and the HTTPS-gated future work (MCP/PWA/camera)
- New docs/git-deploy-guide.md: self-contained git + deploy walkthrough for a
Claude instance working without this repo's CLAUDE.md (NAS is a git checkout,
push + pull/rebuild, never rsync, secrets in host .env, prisma-at-runtime gotcha)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The dashboard gate trusted session.user.mustChangePassword from the JWT, which is
only refreshed on re-login/update() — so after a user set a new password the stale
cookie kept bouncing them back to /change-password. Read the flag from the DB (the
source of truth) so it clears immediately and never re-fires. Cheap for a 2-user
app; also re-checks the account is still active.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Bring an existing HomeBox inventory straight in, and capture the fields HomeBox
has that we were missing (skipping asset IDs per Tony).
- Item: soldTo/soldPrice/soldDate/soldNotes + flexible customFields (JSON, e.g.
VIN / oil weight for vehicles); both editable in the item dialog and shown on
the detail page
- Pure HomeBox CSV parser (src/lib/inventory/homebox-import.ts, +5 tests) —
RFC4180 quoting (commas/newlines/escaped quotes in fields), HomeBox's 0001-*
null dates and 0 = empty price, semicolon labels, HB.field.* custom columns,
nested 'A / B' location paths
- Import service + /api/v1/items/import: find-or-creates the nested locations
and labels, creates items, recomputes location paths, returns a summary
- Inventory 'Import' button: paste/upload CSV, review every row with checkboxes
(uncheck to skip), then import. Preview parses client-side (parser is pure)
- Verified end-to-end: nested locations, split labels, sold + custom fields,
skip, quoted names, null-date handling. tsc + 38 tests + build green
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Rounds out Inventory toward HomeBox, and gives Locations a real home.
Inventory:
- Attach manuals, receipts, and warranty docs (any file), not just photos —
attachment uploader now has a kind picker; WARRANTY kind added
- Richer purchase/warranty: purchasedFrom, warrantyDetails, lifetimeWarranty,
insured (item dialog + detail facts + header badge)
- Schedule recurring maintenance from an item (creates a Task linked via
itemId); item detail shows its maintenance with overdue flagged. Task create
now accepts itemId/animalId
Locations:
- New Locations page + nav: browse/add/rename/move/delete the whole place tree
(LocationManager over the existing /api/locations routes); shows plant/item
counts per place. The spot to merge near-dupes before dropping legacy zone
Verified end-to-end against a running server (new fields round-trip, maintenance
task links to item, MANUAL attachment stored). tsc + 33 tests + build green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Livestock and household pets as leaves on the Location tree, mirroring Garden.
- Animal model (species/breed/sex/birthdate/location, soft-delete) + AnimalLog
(feeding, vet, weight, medication, breeding, acquired, died); Task.animalId for
recurring care reminders
- API-first /api/v1/animals (CRUD) + logs, animals:read/write scopes; animals
service shared with the coming MCP
- Animals pages: list grouped by species with auto-computed age, detail with
facts + latest weight + care log; add/edit/remove + log entry. Nav entry.
Backup/restore cover the new tables
- Pure age.ts (newborn/days/months/years) with tests; verified end-to-end against
a running server (create, vet + weigh-in logs, read-back)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The first inventory vertical on the shared spine. Things you own — tools, gear,
equipment — each living in a Location or inside another Item.
- Item model (kind DURABLE|CONSUMABLE; consumable fields present but unused so
Pantry needs no migration), self-nesting via parentItemId (toolbox holds drill,
with a cycle guard), value/warranty/serial/brand/qty, qrSlug, photo
- Label (m2m) + ItemAttachment (photos/manuals/receipts) + ItemLog
(maintenance/repair/move/dispose) history; Task.itemId
- API-first: /api/v1/items (CRUD), labels, logs, multipart attachments — the UI
consumes the same scoped endpoints. items service shared with the coming MCP
- Inventory pages: tree grouped by Location with items nested inside items;
item detail with warranty status, photos, contents, history; add/edit/delete,
log, photo upload. Nav entry. Backup/restore cover the new tables
- Pure value.ts (warrantyStatus, totalValue) with tests; verified end-to-end
against a running server (nest, cycle-guard 400, log 201)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Fixes from the code review:
- #1 Wire mustChangePassword: dashboard layout redirects to a new /change-password
page + /api/account/change-password (verifies current pw, re-hashes, clears the
flag, refreshes the session). Provisioned admins can no longer keep the default.
- #2 Assert NEXTAUTH_SECRET in production (not during build); set it on authOptions.
- #3 Backup page now server-gated to admins (split into server page + client panel),
matching the Updates page.
- #4 Restore validates input: strict zod schema for the users table (known fields,
role enum) + array checks for the rest; returns generic errors instead of String(err).
No version bump/changelog — left for you to fold into the next release (you're
editing those live). Not pushed, not deployed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A bidirectional API so other apps (Home Assistant, phone shortcuts, scripts,
and the coming MCP server) can read and update the homestead.
- ApiToken model (sha256-hashed, scoped, with prefix/lastUsed/expiry/revoke)
- authenticateRequest() accepts EITHER a NextAuth cookie session OR a Bearer
PAT; cookie sessions get full access, tokens are limited to their scopes
(write implies read; resource and global wildcards). ApiError + handleApiError
- Shared service layer (plants/tasks/locations/plant-types) reused by every
endpoint and, later, the MCP server — logic lives once
- /api/v1: plants (read + add log), plant-types, locations, tasks (read/create/
complete), and a whoami/discovery root
- Settings → API tokens: create with per-resource read/write checkboxes, raw
token shown once, revoke; tokens added to backup/restore
- Pure tests for scope matching + token hashing; verified end-to-end (401/403/
201, lastUsedAt) against a running server
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A rich-but-all-optional reference record per kind of plant (care, sun, water,
bloom & harvest windows, toxic-to-pets, edible parts, spacing), distinct from a
specific planting or a group.
- PlantType model + optional Plant.plantTypeId; migration + idempotent backfill
that seeds 247 kinds from the built-in plant library and links plantings by
species/common name (wired into the deploy entrypoint)
- /api/plant-types CRUD; new "Plant types" section (browse by category, edit a
kind, see every spot you've planted it)
- New plants auto-link to their type on create; plant detail shows "More about…"
- PlantType added to backup/restore; bump APP_VERSION 0.11.0 + changelog
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Completes the Location feature (schema + migration landed earlier under 98cabc5).
- Hierarchical Location model is the shared spine; unlimited nesting
- Garden add/edit now picks a reusable Location (inline create) instead of
freeform text; garden groups By Location off the real tree
- Dual-write keeps legacy Plant.zone in sync with location.name (Release A)
- /api/locations CRUD + /api/locations/[id]/log (logs a location + its sub-locations)
- Location tree pure-core (buildTree/computePath/descendantIds) with tests
- Location added to backup/restore export
- Bump APP_VERSION 0.10.0 + plain-English changelog entry
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
webhook returns 200 even when a trigger rule isn't satisfied, which would let
the app's res.ok check falsely report success if the secret drifted. Set
trigger-rule-mismatch-http-response-code: 403 so unauthorized triggers are
clearly rejected.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The -template parser is Go text/template; JSON-escaped \" broke the action.
Backtick raw-string literal is valid inside the JSON string and parses cleanly.
Config-only (mounted + hotreload) — no app rebuild.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add an admin 'Pull & rebuild' button (Settings → Updates). It POSTs an
admin-gated route that signals a privileged 'updater' sidecar (adnanh/webhook +
docker socket) over the internal network with a shared secret; the sidecar runs
'git fetch && reset --hard origin/master && docker compose up -d --build app'
detached. The UI polls /api/version until the rebuilt app returns on the new
version, then reloads.
Secret matching uses webhook -template + getenv so UPDATER_SECRET stays out of
the repo (host .env only). No request data reaches the shell — the command is
fixed in updater/update.sh.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Footer becomes a changelog chip and shows an 'Update available' badge when a
newer APP_VERSION exists upstream. Detection reads src/lib/version.ts from the
Gitea default branch (dms model) and compares — self-contained, no OTM control
plane. Adds src/lib/update-check.ts and GET /api/version (also a running-version
probe for the upcoming one-click rebuild).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Wire the shared Gitea-backed suggestions panel: page, list/create + comments +
upload API routes, sidebar link. Suggestions file as issues on bonna61/Moonbase;
screenshot uploads persist via a mounted volume. Mirrors the FMB wiring.
Secrets (NEXTAUTH_SECRET, GITEA_TOKEN) moved out of docker-compose.yml to an
interpolated gitignored .env on the host, per CLAUDE.md (never commit the token).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Version the backup/restore feature that merged in unversioned, and add
netcat-openbsd to the bookworm-slim runtime so the new entrypoint Postgres
readiness check (nc -z postgres 5432) works instead of looping forever.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The NAS container was crash-looping: npx pulled Prisma 7.8.0 (which rejects
url in the datasource), and the alpine + non-root + standalone setup couldn't
detect openssl or write the migration engine. Switch to the FMB-proven runtime
(node:20-bookworm-slim + openssl, run as root, full node_modules, next start)
so the local pinned Prisma 5.22 CLI runs and migrations apply cleanly.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
npx prisma was downloading Prisma CLI v7.8.0 which dropped the
url = env("DATABASE_URL") schema syntax. Use the project's own
installed Prisma CLI from node_modules instead, copied from the
builder stage.