- 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>
drizzle/meta/ was gitignored, so GitHub Actions had no _journal.json to
copy into the image. The migrator requires this file to know which SQL
files to apply. Unblock by committing the meta directory.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The standalone tracer only includes subpaths the Next.js app imports.
drizzle-orm/postgres-js/migrator is only used by migrate.mjs (not app
code) so it is excluded from the trace. Copy the full packages from the
builder stage to ensure all subpaths are available at startup.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Next.js bundles all non-externalized server packages into server chunks.
drizzle-orm and postgres were bundled, so they were absent from the
standalone node_modules/ and unavailable to migrate.mjs at startup.
Adding them to serverExternalPackages keeps them as separate modules and
tells the standalone tracer to include them.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
drizzle/ was excluded in .dockerignore but the Dockerfile copies it into
the runner stage (needed by migrate.mjs at container startup). Remove the
exclusion so migration SQL files are present in the builder stage.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
.dockerignore excludes deploy/ entirely. The entrypoint script lived
there, so the COPY in Stage 3 failed with "not found". Move it to the
project root (alongside Dockerfile) where the build context includes it.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
next.config.ts regenerates public/sw.js on every build with a new
timestamp; tracking it produces noise on every build. Add to .gitignore
and untrack — it is always regenerated by `pnpm dev` / `pnpm build`
and by the Docker build stage.
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>