Replaces the 8-line placeholder with a complete README covering all four
modules, platform capabilities, quick start, dev setup, configuration
reference, and deployment links. Badges use dynamic shields.io URLs so
version/CI status update automatically on each release.
Wires README review into the release pipeline via a release-it
before:git:release hook and a new item in the pre-deploy checklist.
deploy/compose.yaml, deploy/Caddyfile, and deploy/Caddyfile.dev.snippet
contain homelab-specific config and are not useful as public templates.
Public-facing equivalents: compose.example.yaml and Caddyfile.snippet.
- rename AUTH_SECRET to RESUME_AUTH_SECRET in production .env and compose
app service — famapp's AUTH_SECRET is now unambiguous
- remove orphaned stale AUTH_SECRET entry from .env
- apply-compose.ps1: pull + restart famapp immediately after pushing compose
rather than waiting for watchtower; uses dc alias via bash -ic
- update .env.production.example to reflect corrected variable names
- deploy/compose.yaml: replace generated placeholder with actual monolith
compose (Foundry, Caddy, media stack, famapp, Authentik, Watchtower, etc.)
- deploy/Caddyfile.snippet: replace with full production Caddyfile
- .env.production.example: update variable names to match production
(famapp_MINIO_ROOT_USER/PASSWORD/BUCKET, CF_API_TOKEN, etc.)
- scripts/apply-compose.ps1: add -Compose/-Caddy flags; push Caddyfile
and reload caddy in addition to compose restart
Zod v4 changed .url() to use the WHATWG URL spec, which rejects relative
paths like /api/uploads/... that our upload API returns. Relax image URL
validators to z.string().min(1) since these are internal paths.
Adds a new "bangs" module with a dashboard widget that tracks household
bang events. The widget shows the total count, a configurable recent-bang
list, and an "Add Bang" button that accepts a backdatable date.
On submission, a full celebration sequence fires: screen flash, widget
shake, count pop animation, synthesized firework sound (Web Audio API),
50 full-screen emoji particles via portal, canvas-confetti star bursts,
side cannons, and a timed sequence of 7 firework bursts.
- New bang_events table (migration 0017)
- canvas-confetti dependency for fireworks/confetti effects
- CSS keyframe animations: shake, countPop, emojiFloat
- Fixes pre-existing Drizzle snapshot chain collision (0007/0008)
- Adds OpenPlantBook env vars to compose.yaml
Trailing slashes on /plant/search/ and /plant/detail/ caused Django to
return an HTML redirect page instead of JSON. SDK uses no trailing slashes.
Also updates the helper text to reference OpenPlantBook instead of Perenual.
Perenual's free tier does not serve care instructions. OpenPlantBook
provides watering, sunlight, and other care data at no cost.
- OAuth2 client_credentials token fetch with module-level cache
- Search via /api/v1/plant/search/, detail via /api/v1/plant/detail/{pid}/?include=care
- SpeciesSuggestion shape unchanged; no frontend or schema changes required
- Env vars: OPENPLANTBOOK_CLIENT_ID + OPENPLANTBOOK_CLIENT_SECRET (replaces PERENUAL_API_KEY)
The bottom nav was hardcoded; the sidebar already read from the module
registry. Converted BottomNav to a server component that calls
getRegistry() using the same pattern as Sidebar, so any module with a
nav field automatically populates both nav surfaces.
Bumps version to 0.4.2.
- care reminders now fire with plant-specific body ("Time to water Pothos")
via title/body columns on the reminders table (migration 0016)
- bi-directional task sync: checking off a garden-linked task list item
creates a care log; logging care from garden marks the linked task done
(list_items.metadata stores gardenPlantId + gardenCareType linkage)
- upload error response no longer leaks debug detail; error message
corrected from "5 MB" to "100 MB"
- getCareDueWidgetRows query: schedule rows due within N days, filtered
by optional container list, ordered by next_due_at
- getGardenOverviewStats query: plant/container/overdue counts + next care
- CareDueWidget: compact list sorted by urgency, inline log-care button,
truncates at 10 rows with View all link
- GardenOverviewWidget: stat row (plants, containers, overdue) + next care
line + garden link
- plant-widget.tsx server component: InlineLogButton uses form action to
call logCare without client JS or navigation
- manifest: garden.care-due and garden.overview widgets registered;
resolveConfigOptions returns container list for care-due config picker
- quickAdds: icons updated (leaf/box/droplets), log-care URL ->
/garden?logCare=1
- calendar-bridge.ts and lists-bridge.ts isolate cross-module deps
- scheduleOnCalendar action: creates event from care schedule with 30-min
slot, title convention (Water/Fertilize/Repot/Prune/custom), reminder opt
- pushOverdueToTaskList action: inserts one task per overdue (plant+schedule)
pair into the default task list, skips duplicate text entries
- CareScheduleEditor: per-row calendar 📅 button opens inline form with
calendar select + optional reminder minutes + success link to /calendar
- Garden page header: PushOverdueButton with auto-dismissing feedback toast
- logCare/deleteCareLog actions with schedule update and reminder wiring
- upsertCareSchedule/deleteCareSchedule/toggleCareSchedule actions
- updateScheduleAfterCare helper cancels old reminder, schedules new one
- getCareLogs/getCareSchedules/getOverduePlants/getCareDueSoon queries
- CareLogForm, CareScheduleEditor, CareHistoryList components
- Plant detail Care tab wired up with schedule editor + log form + history
- Log plant care quick-add added to garden manifest
- pnpm dev:local/dev:reset: orchestrate DB container, migrations, seed, and Next.js dev server in one command; Caddy snippet + docs for HTTPS via dev.ginnoir.com
- Fix dev login on HTTPS: set both authjs.session-token and __Secure-authjs.session-token so Auth.js finds the session regardless of cookie name resolution
- Suppress hydration mismatch on <html> caused by pre-paint script changing data-nav before React hydrates
- VAPID startup warning if keys not configured; remove dead NEXT_PUBLIC_VAPID_PUBLIC_KEY var
- PushOptIn: hydrate subscription state on mount; reuse existing subscription on iOS to avoid redundant prompts
- sendPushToEndpoint: new function to send to a single device endpoint
- sendTestNotification: scoped to the calling device's endpoint (ownership-verified) instead of all user subscriptions
Replaces the generic shadcn/ui gray theme + horizontal top-bar shell with
the paper-and-ink language from the Claude Design handoff bundle: warm
off-white paper, near-black ink, Source Serif 4 + Inter, hairline borders,
muted ink accents (clay/indigo/sage/plum/ochre) used functionally for
calendars and share scopes.
Theme switcher expanded from 2 dimensions (theme × mode) to 7: palette ×
mode × fontPair × density × dashLayout × calView × navStyle. All exposed
in Settings → Appearance and persisted on the users row. Pre-paint script
applies all four data-* attributes from localStorage so reload doesn't
flash.
App shell restructured to a CSS-grid driven by data-nav on <html>: sidebar
on desktop, bottom-nav + FAB under 760px. Four desktop nav modes wired
(sidebar/rail/top/fab-only). Topbar gets a search-→-CommandPalette button,
notification bell, "+ New" quick-add, avatar.
Dashboard, calendar, lists, notes, settings, login, public share viewer,
and quick-add sheet all reskinned. Dashboard editor gains a Preset menu
(classic/split/glance) that fills the layout from the registered widgets.
FullCalendar wrapped in .fc-skin and inherits all paper-and-ink tokens via
CSS variable overrides. Public share viewer (/s/<token>) rebuilt around
ShareFrame: expiration banner, brand strip, eyebrow chip, 38px serif
title, mini-day + mini-map cards, share-rows.
Schema: drops users.theme; adds theme_palette, theme_font_pair,
theme_density, theme_dash_layout, theme_cal_view, theme_nav_style with
defaults that match the design (clay / serif-sans / regular / classic /
month / rail-desktop). Migration 0014_paper_ink_theme.
Middleware sets x-pathname so the AppShell server component can render
bare for /s/* and /login without a route-group refactor.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Auth.js v5 calls signIn before the new user's row is fully committed, so
inserting into household_members immediately caused a FK violation for any
new user (e.g. cam on first sign-in). Enrollment is now handled lazily in
getCurrentSession's auto-enroll fallback, which runs after the session is
established and the user row is guaranteed to exist.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
On a new user's first load, getDefaultDashboardSlug fell through to
createDashboard("Home"), which calls revalidatePath("/"). That is illegal
in a render context (page component → server action → revalidatePath).
Inline the DB insert directly in the fallback and skip revalidatePath —
the redirect that immediately follows is a fresh request, so no cache
invalidation is needed here.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
If a user has a valid session but no household_members row (e.g. they
first signed in before the household was seeded), getCurrentSession now
auto-enrolls them instead of throwing. Assigns owner if first member,
member otherwise, and creates their default calendars and lists.
Removes the permanently-stuck state that occurred on first deploy.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Without a household row the signIn callback has nothing to attach the
first user to, causing "No household membership" on first load. Add
scripts/seed.mjs (plain ESM, only needs postgres which is already in the
image) and run it from the entrypoint after migrations. Idempotent — skips
if a household already exists. Default calendars/lists are created by the
signIn callback when the first user authenticates.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>