v0.10.0 — Location spine: garden moved onto a shared place tree

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>
This commit is contained in:
2026-06-15 23:36:54 -05:00
parent 6f8fe9a798
commit 9c0312676a
19 changed files with 788 additions and 103 deletions

View File

@@ -56,6 +56,25 @@ async function main() {
{ commonName: "Garlic", variety: "Music", category: PlantCategory.BULB, zone: "Raised bed #1", source: "Keene Organics", plantedAt: new Date("2024-10-10") },
];
// Starter Location tree — a root property node with the garden areas under it.
const root = await db.location.upsert({
where: { id: "seed-loc-root" },
update: {},
create: { id: "seed-loc-root", name: "Moon Homestead", kind: "OTHER", path: "Moon Homestead" },
});
const zoneNames = Array.from(new Set(plants.map((p) => p.zone).filter(Boolean) as string[]));
const zoneToLocId = new Map<string, string>();
for (const z of zoneNames) {
const id = `seed-loc-${z.toLowerCase().replace(/[^a-z0-9]+/g, "-")}`;
await db.location.upsert({
where: { id },
update: {},
create: { id, name: z, kind: "AREA", parentId: root.id, path: `Moon Homestead ${z}` },
});
zoneToLocId.set(z, id);
}
for (const p of plants) {
await db.plant.upsert({
where: { id: `seed-${p.commonName.toLowerCase().replace(/\s/g, "-")}-${p.variety?.toLowerCase().replace(/\s/g, "-") ?? "0"}` },
@@ -63,6 +82,7 @@ async function main() {
create: {
id: `seed-${p.commonName.toLowerCase().replace(/\s/g, "-")}-${p.variety?.toLowerCase().replace(/\s/g, "-") ?? "0"}`,
...p,
locationId: p.zone ? zoneToLocId.get(p.zone) : undefined,
},
});
}