v0.5.1 — Reminders, plant groups, location views, Wikipedia photos, Docker deploy

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Bonna Moon
2026-06-15 20:53:03 -05:00
parent e3f6a8ec92
commit 04dec2a198
32 changed files with 1988 additions and 181 deletions

View File

@@ -8,6 +8,79 @@ export type ChangelogEntry = {
};
export const CHANGELOG: ChangelogEntry[] = [
{
version: "0.5.1",
date: "2026-06-15",
changes: [
"Plant database expanded with species-level entries for irises (bearded, Siberian, yellow flag, Japanese, Dutch), daylilies, hostas, hydrangeas (4 species), coneflowers, milkweeds, bee balms, rudbeckias, phlox, goldenrod, oregano types, and more. Type \"iris\" and get the actual species to choose from.",
],
},
{
version: "0.5.0",
date: "2026-06-15",
changes: [
"Plant photos — when you add a plant, the app automatically pulls a photo from Wikipedia using the scientific name (or common name as a fallback). Photos show on the garden cards and the plant detail page. Plants without a photo get a green leaf placeholder.",
],
},
{
version: "0.4.5",
date: "2026-06-15",
changes: [
"All plants in the \"By type\" view now use the same collapsible row format — consistent look whether a plant is in one location or several.",
],
},
{
version: "0.4.4",
date: "2026-06-15",
changes: [
"In the \"By type\" view, plants with the same name and variety now collapse into a single row showing their locations as chips. Click to expand and see each individual bed's card.",
],
},
{
version: "0.4.3",
date: "2026-06-15",
changes: [
"Add a plant to multiple locations at once — tick as many beds as you like and the app creates a separate entry for each. The button even shows \"Add to 3 locations\" so you know what's about to happen.",
],
},
{
version: "0.4.2",
date: "2026-06-15",
changes: [
"Location picker — \"Location on your property\" is now a dropdown like groups. Pick an existing location or type a new one; no more typo mismatches between plants in the same bed.",
],
},
{
version: "0.4.1",
date: "2026-06-15",
changes: [
"Garden now has a \"By location\" view — switch between viewing plants by type (groups) or by garden bed/location. Each location collapses just like groups, with its own \"Log all\" button to log care for everything in that bed at once.",
],
},
{
version: "0.4.0",
date: "2026-06-15",
changes: [
"Plant groups — group multiple plants together (like all your irises) so they show collapsed on the garden page and you can log care for the whole group at once with one tap.",
"When adding a plant, pick an existing group or create a new one on the spot.",
],
},
{
version: "0.3.1",
date: "2026-06-15",
changes: [
"Plants now have a \"Where did it come from?\" dropdown: We planted it, Already here when we moved in, Gift or trade, or Self-seeded. Great for tracking what was on the property before you.",
],
},
{
version: "0.3.0",
date: "2026-06-15",
changes: [
"Reminders — new section in the sidebar for tracking recurring tasks: watering schedules, fertilizing, pruning, HVAC filters, smoke alarm batteries, and anything else that needs doing on a schedule.",
"Set a reminder to happen just once, every N days, every month, or every year. Mark any reminder done and the next due date updates automatically.",
"Reminders can optionally be linked to a specific plant in your garden.",
],
},
{
version: "0.2.0",
date: "2026-06-15",

View File

@@ -94,7 +94,7 @@ export const PLANT_DB: PlantSuggestion[] = [
{ commonName: "Catnip", species: "Nepeta cataria", category: "HERB" },
{ commonName: "Valerian", species: "Valeriana officinalis", category: "HERB" },
{ commonName: "St. John's wort", species: "Hypericum perforatum", category: "HERB" },
{ commonName: "Echinacea / Coneflower", species: "Echinacea purpurea", category: "HERB" },
{ commonName: "Chamomile, Roman", species: "Chamaemelum nobile", category: "HERB" },
// ---------------------------------------------------------------------------
// Groundcovers
@@ -137,48 +137,115 @@ export const PLANT_DB: PlantSuggestion[] = [
// ---------------------------------------------------------------------------
// Perennial flowers
// ---------------------------------------------------------------------------
{ commonName: "Coneflower", species: "Echinacea purpurea", category: "PERENNIAL_FLOWER" },
{ commonName: "Black-eyed Susan", species: "Rudbeckia hirta", category: "PERENNIAL_FLOWER" },
{ commonName: "Bee balm", species: "Monarda didyma", category: "PERENNIAL_FLOWER" },
{ commonName: "Wild bergamot", species: "Monarda fistulosa", category: "PERENNIAL_FLOWER" },
{ commonName: "Peony", species: "Paeonia lactiflora", category: "PERENNIAL_FLOWER" },
{ commonName: "Hosta", species: "Hosta spp.", category: "PERENNIAL_FLOWER" },
{ commonName: "Daylily", species: "Hemerocallis spp.", category: "PERENNIAL_FLOWER" },
{ commonName: "Iris", species: "Iris spp.", category: "PERENNIAL_FLOWER" },
{ commonName: "Salvia, perennial", species: "Salvia nemorosa", category: "PERENNIAL_FLOWER" },
{ commonName: "Catmint", species: "Nepeta × faassenii", category: "PERENNIAL_FLOWER" },
{ commonName: "Astilbe", species: "Astilbe spp.", category: "PERENNIAL_FLOWER" },
{ commonName: "Coreopsis", species: "Coreopsis spp.", category: "PERENNIAL_FLOWER" },
{ commonName: "Lupine", species: "Lupinus spp.", category: "PERENNIAL_FLOWER" },
{ commonName: "Delphinium", species: "Delphinium spp.", category: "PERENNIAL_FLOWER" },
{ commonName: "Hollyhock", species: "Alcea rosea", category: "PERENNIAL_FLOWER" },
{ commonName: "Foxglove", species: "Digitalis purpurea", category: "PERENNIAL_FLOWER" },
{ commonName: "Phlox", species: "Phlox paniculata", category: "PERENNIAL_FLOWER" },
{ commonName: "Sedum / Stonecrop", species: "Sedum spp.", category: "PERENNIAL_FLOWER" },
{ commonName: "Russian sage", species: "Perovskia atriplicifolia", category: "PERENNIAL_FLOWER" },
{ commonName: "Goldenrod", species: "Solidago spp.", category: "PERENNIAL_FLOWER" },
{ commonName: "Joe Pye weed", species: "Eutrochium purpureum", category: "PERENNIAL_FLOWER" },
{ commonName: "Bleeding heart", species: "Lamprocapnos spectabilis", category: "PERENNIAL_FLOWER" },
{ commonName: "Columbine", species: "Aquilegia spp.", category: "PERENNIAL_FLOWER" },
{ commonName: "Coral bells", species: "Heuchera spp.", category: "PERENNIAL_FLOWER" },
{ commonName: "Liatris / Blazing star", species: "Liatris spicata", category: "PERENNIAL_FLOWER" },
{ commonName: "Milkweed", species: "Asclepias spp.", category: "PERENNIAL_FLOWER" },
{ commonName: "Butterfly weed", species: "Asclepias tuberosa", category: "PERENNIAL_FLOWER" },
{ commonName: "Monkshood", species: "Aconitum spp.", category: "PERENNIAL_FLOWER" },
{ commonName: "Prairie dropseed", species: "Sporobolus heterolepis", category: "PERENNIAL_FLOWER" },
{ commonName: "Coneflower, yellow", species: "Ratibida pinnata", category: "PERENNIAL_FLOWER" },
{ commonName: "False indigo", species: "Baptisia australis", category: "PERENNIAL_FLOWER" },
{ commonName: "Shasta daisy", species: "Leucanthemum × superbum", category: "PERENNIAL_FLOWER" },
{ commonName: "Spiderwort", species: "Tradescantia ohiensis", category: "PERENNIAL_FLOWER" },
{ commonName: "Tickseed", species: "Coreopsis tinctoria", category: "PERENNIAL_FLOWER" },
{ commonName: "Rose mallow", species: "Hibiscus moscheutos", category: "PERENNIAL_FLOWER" },
{ commonName: "Obedient plant", species: "Physostegia virginiana", category: "PERENNIAL_FLOWER" },
{ commonName: "Globe thistle", species: "Echinops spp.", category: "PERENNIAL_FLOWER" },
{ commonName: "Agastache", species: "Agastache spp.", category: "PERENNIAL_FLOWER" },
// Irises — species level
{ commonName: "Iris, bearded (purple)", species: "Iris germanica", category: "PERENNIAL_FLOWER" },
{ commonName: "Iris, Siberian", species: "Iris sibirica", category: "PERENNIAL_FLOWER" },
{ commonName: "Iris, yellow flag", species: "Iris pseudacorus", category: "PERENNIAL_FLOWER" },
{ commonName: "Iris, Japanese", species: "Iris ensata", category: "PERENNIAL_FLOWER" },
{ commonName: "Iris, Dutch", species: "Iris × hollandica", category: "PERENNIAL_FLOWER" },
{ commonName: "Iris, dwarf bearded", species: "Iris pumila", category: "PERENNIAL_FLOWER" },
{ commonName: "Iris, Louisiana", species: "Iris fulva", category: "PERENNIAL_FLOWER" },
// Daylilies — species level
{ commonName: "Daylily, tawny (orange)", species: "Hemerocallis fulva", category: "PERENNIAL_FLOWER" },
{ commonName: "Daylily, lemon", species: "Hemerocallis lilioasphodelus", category: "PERENNIAL_FLOWER" },
{ commonName: "Daylily, hybrid", species: "Hemerocallis × hybrida", category: "PERENNIAL_FLOWER" },
// Hostas — species level
{ commonName: "Hosta, plantain lily", species: "Hosta plantaginea", category: "PERENNIAL_FLOWER" },
{ commonName: "Hosta, siebold", species: "Hosta sieboldiana", category: "PERENNIAL_FLOWER" },
{ commonName: "Hosta, hybrid", species: "Hosta × hybrida", category: "PERENNIAL_FLOWER" },
// Rudbeckia / daisy types
{ commonName: "Black-eyed Susan, perennial", species: "Rudbeckia fulgida", category: "PERENNIAL_FLOWER", notes: "Spreads into large clumps over time." },
{ commonName: "Black-eyed Susan, annual", species: "Rudbeckia hirta", category: "ANNUAL_FLOWER" },
{ commonName: "Cutleaf coneflower", species: "Rudbeckia laciniata", category: "PERENNIAL_FLOWER" },
{ commonName: "Sweet coneflower", species: "Rudbeckia subtomentosa", category: "PERENNIAL_FLOWER" },
{ commonName: "False sunflower", species: "Heliopsis helianthoides", category: "PERENNIAL_FLOWER" },
{ commonName: "Sneezeweed", species: "Helenium autumnale", category: "PERENNIAL_FLOWER" },
{ commonName: "Shasta daisy", species: "Leucanthemum × superbum", category: "PERENNIAL_FLOWER" },
{ commonName: "Ox-eye daisy", species: "Leucanthemum vulgare", category: "PERENNIAL_FLOWER" },
// Hydrangeas — species level
{ commonName: "Hydrangea, bigleaf", species: "Hydrangea macrophylla", category: "LARGE_SHRUB" },
{ commonName: "Hydrangea, panicle", species: "Hydrangea paniculata", category: "LARGE_SHRUB" },
{ commonName: "Hydrangea, smooth", species: "Hydrangea arborescens", category: "LARGE_SHRUB" },
{ commonName: "Hydrangea, oakleaf", species: "Hydrangea quercifolia", category: "LARGE_SHRUB" },
{ commonName: "Hydrangea, climbing", species: "Hydrangea petiolaris", category: "VINE" },
// Echinacea — species level
{ commonName: "Coneflower, purple", species: "Echinacea purpurea", category: "PERENNIAL_FLOWER" },
{ commonName: "Coneflower, pale", species: "Echinacea pallida", category: "PERENNIAL_FLOWER" },
{ commonName: "Coneflower, narrow-leaf", species: "Echinacea angustifolia", category: "PERENNIAL_FLOWER" },
{ commonName: "Coneflower, yellow", species: "Ratibida pinnata", category: "PERENNIAL_FLOWER" },
// Milkweed — species level
{ commonName: "Milkweed, common", species: "Asclepias syriaca", category: "PERENNIAL_FLOWER" },
{ commonName: "Milkweed, butterfly", species: "Asclepias tuberosa", category: "PERENNIAL_FLOWER" },
{ commonName: "Milkweed, swamp", species: "Asclepias incarnata", category: "PERENNIAL_FLOWER" },
// Bee balm / Monarda
{ commonName: "Bee balm, scarlet", species: "Monarda didyma", category: "PERENNIAL_FLOWER" },
{ commonName: "Bee balm, wild / lavender", species: "Monarda fistulosa", category: "PERENNIAL_FLOWER" },
{ commonName: "Bee balm, spotted", species: "Monarda punctata", category: "PERENNIAL_FLOWER" },
// Salvia
{ commonName: "Salvia, perennial", species: "Salvia nemorosa", category: "PERENNIAL_FLOWER" },
{ commonName: "Salvia, meadow sage", species: "Salvia pratensis", category: "PERENNIAL_FLOWER" },
// Phlox
{ commonName: "Phlox, garden", species: "Phlox paniculata", category: "PERENNIAL_FLOWER" },
{ commonName: "Phlox, creeping", species: "Phlox subulata", category: "GROUNDCOVER" },
{ commonName: "Phlox, woodland", species: "Phlox divaricata", category: "PERENNIAL_FLOWER" },
// Sedum
{ commonName: "Sedum, autumn joy", species: "Hylotelephium spectabile", category: "PERENNIAL_FLOWER" },
{ commonName: "Sedum, stonecrop", species: "Sedum acre", category: "GROUNDCOVER" },
// Lily of the valley
{ commonName: "Lily of the valley", species: "Convallaria majalis", category: "GROUNDCOVER", notes: "Spreads by rhizome — can become aggressive." },
// Oregano types
{ commonName: "Oregano, culinary", species: "Origanum vulgare", category: "HERB" },
{ commonName: "Oregano, flowering", species: "Origanum laevigatum", category: "PERENNIAL_FLOWER", notes: "Ornamental — varieties like Herrenhausen and Hopley's." },
{ commonName: "Oregano, Greek", species: "Origanum vulgare subsp. hirtum", category: "HERB" },
// Catmint / Nepeta
{ commonName: "Catmint", species: "Nepeta × faassenii", category: "PERENNIAL_FLOWER" },
{ commonName: "Catmint, six hills giant", species: "Nepeta racemosa", category: "PERENNIAL_FLOWER" },
// Goldenrod
{ commonName: "Goldenrod, showy", species: "Solidago speciosa", category: "PERENNIAL_FLOWER" },
{ commonName: "Goldenrod, stiff", species: "Solidago rigida", category: "PERENNIAL_FLOWER" },
{ commonName: "Goldenrod, wreath", species: "Solidago caesia", category: "PERENNIAL_FLOWER" },
// Remaining perennial flowers
{ commonName: "Astilbe", species: "Astilbe spp.", category: "PERENNIAL_FLOWER" },
{ commonName: "Lupine", species: "Lupinus spp.", category: "PERENNIAL_FLOWER" },
{ commonName: "Delphinium", species: "Delphinium spp.", category: "PERENNIAL_FLOWER" },
{ commonName: "Hollyhock", species: "Alcea rosea", category: "PERENNIAL_FLOWER" },
{ commonName: "Foxglove", species: "Digitalis purpurea", category: "PERENNIAL_FLOWER" },
{ commonName: "Russian sage", species: "Perovskia atriplicifolia", category: "PERENNIAL_FLOWER" },
{ commonName: "Joe Pye weed", species: "Eutrochium purpureum", category: "PERENNIAL_FLOWER" },
{ commonName: "Bleeding heart", species: "Lamprocapnos spectabilis", category: "PERENNIAL_FLOWER" },
{ commonName: "Columbine", species: "Aquilegia spp.", category: "PERENNIAL_FLOWER" },
{ commonName: "Coral bells", species: "Heuchera spp.", category: "PERENNIAL_FLOWER" },
{ commonName: "Liatris / Blazing star", species: "Liatris spicata", category: "PERENNIAL_FLOWER" },
{ commonName: "Monkshood", species: "Aconitum spp.", category: "PERENNIAL_FLOWER" },
{ commonName: "Prairie dropseed", species: "Sporobolus heterolepis", category: "PERENNIAL_FLOWER" },
{ commonName: "False indigo", species: "Baptisia australis", category: "PERENNIAL_FLOWER" },
{ commonName: "Spiderwort", species: "Tradescantia ohiensis", category: "PERENNIAL_FLOWER" },
{ commonName: "Rose mallow", species: "Hibiscus moscheutos", category: "PERENNIAL_FLOWER" },
{ commonName: "Obedient plant", species: "Physostegia virginiana", category: "PERENNIAL_FLOWER" },
{ commonName: "Globe thistle", species: "Echinops spp.", category: "PERENNIAL_FLOWER" },
{ commonName: "Agastache", species: "Agastache spp.", category: "PERENNIAL_FLOWER" },
{ commonName: "Gaillardia / Blanket flower", species: "Gaillardia spp.", category: "PERENNIAL_FLOWER" },
{ commonName: "Veronicastrum", species: "Veronicastrum virginicum", category: "PERENNIAL_FLOWER" },
{ commonName: "Turtlehead", species: "Chelone glabra", category: "PERENNIAL_FLOWER" },
{ commonName: "Wild ginger", species: "Asarum canadense", category: "PERENNIAL_FLOWER" },
{ commonName: "Veronicastrum", species: "Veronicastrum virginicum", category: "PERENNIAL_FLOWER" },
{ commonName: "Turtlehead", species: "Chelone glabra", category: "PERENNIAL_FLOWER" },
{ commonName: "Wild ginger", species: "Asarum canadense", category: "PERENNIAL_FLOWER" },
{ commonName: "Coreopsis, threadleaf", species: "Coreopsis verticillata", category: "PERENNIAL_FLOWER" },
{ commonName: "Coreopsis, lance-leaf", species: "Coreopsis lanceolata", category: "PERENNIAL_FLOWER" },
// ---------------------------------------------------------------------------
// Annual vegetables

View File

@@ -0,0 +1,25 @@
// Fetch a thumbnail image URL from Wikipedia for a given plant.
// Tries scientific name first, falls back to common name.
// Returns null if nothing is found or the request fails.
export async function fetchPlantImageUrl(
species?: string | null,
commonName?: string | null,
): Promise<string | null> {
const queries = [species, commonName].filter(Boolean) as string[];
for (const query of queries) {
const url = `https://en.wikipedia.org/api/rest_v1/page/summary/${encodeURIComponent(query)}`;
try {
const res = await fetch(url, {
headers: { "User-Agent": "MoonBase/1.0 (homestead plant tracker)" },
signal: AbortSignal.timeout(4000),
});
if (!res.ok) continue;
const data = await res.json();
const img = data?.thumbnail?.source ?? data?.originalimage?.source ?? null;
if (img) return img;
} catch {
// network error or timeout — try next query
}
}
return null;
}

76
src/lib/tasks/schedule.ts Normal file
View File

@@ -0,0 +1,76 @@
export type RecurrenceType = "ONCE" | "CUSTOM" | "MONTHLY" | "YEARLY";
// Given a completed task, compute the next due date.
export function nextDueDate(
recurrence: RecurrenceType,
completedAt: Date,
intervalDays: number | null,
month: number | null,
): Date | null {
const d = new Date(completedAt);
switch (recurrence) {
case "ONCE":
return null; // no next due — task is done forever
case "CUSTOM": {
const days = intervalDays ?? 30;
d.setDate(d.getDate() + days);
return d;
}
case "MONTHLY": {
// Same day of month, next month
d.setMonth(d.getMonth() + 1);
return d;
}
case "YEARLY": {
// Same month next year — set to the 1st of that month
const targetMonth = (month ?? d.getMonth() + 1) - 1; // convert to 0-indexed
d.setFullYear(d.getFullYear() + 1);
d.setMonth(targetMonth);
d.setDate(1);
return d;
}
default:
return null;
}
}
// Human-readable description of a recurrence pattern.
export function describeRecurrence(
recurrence: RecurrenceType,
intervalDays: number | null,
month: number | null,
): string {
switch (recurrence) {
case "ONCE": return "One time only";
case "CUSTOM": return `Every ${intervalDays ?? 30} days`;
case "MONTHLY": return "Every month";
case "YEARLY": {
if (month) {
const monthName = new Date(2000, month - 1, 1).toLocaleString("en-US", { month: "long" });
return `Every year in ${monthName}`;
}
return "Every year";
}
}
}
// Is a task overdue?
export function isOverdue(nextDue: Date): boolean {
return new Date(nextDue) < new Date();
}
// Is a task due within the next N days?
export function isDueSoon(nextDue: Date, days = 30): boolean {
const cutoff = new Date();
cutoff.setDate(cutoff.getDate() + days);
return new Date(nextDue) <= cutoff;
}
export const MONTH_NAMES = [
"January", "February", "March", "April", "May", "June",
"July", "August", "September", "October", "November", "December",
];

View File

@@ -1,2 +1,2 @@
// Bump on every user-visible release. Changelog entries live in `src/lib/changelog.ts`.
export const APP_VERSION = "0.2.2";
export const APP_VERSION = "0.5.1";