- 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>
- PushOptIn now accepts vapidKey as a prop from its server-component
parent (settings page reads VAPID_PUBLIC_KEY at runtime) — eliminates
the NEXT_PUBLIC_* build-time dependency so pre-built GHCR images work
without a build arg.
- deploy/compose.yaml: famapp exposes 3010:3000, authentik-server exposes
9200:9000 so the existing Caddy stack can proxy by IP, matching every
other service in the homelab. NEXT_PUBLIC_APP_URL replaced by AUTH_URL
(correct next-auth v5 var).
- deploy/Caddyfile.snippet: updated to 192.168.1.69:3010 / :9200.
- .env.production.example: AUTH_URL, ntfy pre-wired to ntfy.ginnoir.com,
VAPID_SUBJECT prefilled with real email.
- typecheck and pnpm build both pass.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The online event could fire between page load and useEffect registering
its listener, leaving isOffline stuck at true despite navigator.onLine
being true. A zero-delay setTimeout after listener registration re-syncs
from navigator.onLine, covering that race.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
SW is now generated by next.config.ts on every build with a versioned
cache name (timestamp in prod, "dev" in dev). Strategies: stale-while-
revalidate for static chunks and navigation, network-first with 2s
timeout for API GETs, network-only for mutations. Offline mutations
postMessage to clients; pwa-register.tsx shows amber offline banner,
red mutation-failed toast, and indigo "new version" refresh prompt.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Items on the lists page and dashboard widget now show as crossed-out for
a configurable duration (default 3s) before vanishing. Unchecking within
the window cancels the removal. The delay is stored in localStorage and
configurable via Settings → Lists (options: 1s / 3s / 5s / 10s / 30s).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Lists page: show up to 10 open items per list in collapsible cards;
items can be checked off directly without opening the list
- Dashboard widget: replace static span-checkboxes with a real client
component using useOptimistic so items can be toggled from the dashboard
- listWidgetItems now returns listId for navigation links
- New listListsWithItems query fetches counts + top 10 open items per list
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add ShareButton to list detail, note editor, and calendar event panel
- Add Settings gear icon to AppNav (core page not in module registry)
- Fix calendar event dialog showing raw UUID: pass explicit children to
SelectValue so labels display before SelectContent mounts
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Makes actorId nullable in activity_log (migration 0010) with onDelete:
set null so audit history survives user deletion; adds logShareActivity
for anonymous mutations
- Adds resolveShareToken return of householdId for downstream validation
- Adds renderSharedView to EntityTypeRegistration; each module implements
loadForShare (no-session bare queries) and renderSharedView
- Calendar: CalendarSharedView (upcoming 90-day events) and
EventSharedView (title/time/location/notes)
- Lists: ListSharedView (client component with optimistic toggles via
toggleShareListItem server action in lists/server/share-actions.ts)
- Notes: NoteSharedView (title + body + updated date)
- /app/s/[token]/page.tsx: resolves token, dispatches to loadForShare +
renderSharedView, friendly error for invalid/expired tokens, noindex
- Middleware /s/* exemption confirmed present (no change needed)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- activity_log table in _core/schema.ts with household+created_at index;
migration 0008_activity_log.sql applied
- logActivity() in _core/activity.ts reads current session and inserts a row
- ActivityLogEntry type + renderActivity?(entry): string added to
EntityTypeRegistration so modules declare human-readable labels without
any if/else branches in the widget
- core.activity widget replaced with a real async server component that
queries the last 20 rows and renders via the registry
- logActivity wired into every create/update/delete in calendar, lists,
and notes server actions; getAuthorizedItem also returns text so
toggle/delete can include item text in the payload
- pnpm typecheck, lint, build, and all 4 E2E specs pass
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add url field to QuickAddAction; make action optional
- Add getQuickAdds() + SerializedQuickAddItem to registry so quick-add
data can cross the server→client RSC boundary without serializing fns
- Update calendar/lists/notes manifests with navigation URLs
- QuickAddProvider wraps root layout: holds sheet/palette open state and
global cmd/ctrl+k shortcut
- QuickAddSheet: bottom drawer on mobile, right-side popover on desktop,
actions grouped by module
- CommandPalette: cmdk-powered modal with fuzzy filter and full keyboard
nav (arrows + enter + esc)
- QuickAddFab: client button replacing the plain + stub in the dashboard
- Add .claude/** to ESLint ignores to prevent stale worktree artifacts
from failing lint
All 4 E2E specs pass; typecheck, lint, and build clean.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- scripts/seed.ts: idempotent pnpm db:seed inserts "Home" household if none exists (powered by tsx)
- signIn callback: first member of household gets owner role, subsequent users get member
- src/lib/session.ts: getCurrentSession() returns { user, household, role }, throws if unauthenticated or unmembered
- /settings/household: member list for all roles; rename form gated to owner only (server action also enforces)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Implements the extensibility foundation: ModuleManifest/DashboardWidget/EntityTypeRegistration types in _core/module.ts, a plain-Map registry singleton with registerModule/getRegistry/getEntityType/getWidget, and stub manifests for calendar/lists/notes. Root layout imports src/modules/index.ts for side-effect registration; AppNav reads the registry to render nav links. /debug/registry dumps the full registry JSON in dev using zod v4 + z.toJSONSchema(). Adding a fourth module requires one folder + one line in index.ts.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>