diff --git a/AGENTS.md b/AGENTS.md index f4c3eaa..9dff4ce 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -6,6 +6,11 @@ This file is the canonical brief. Read it at the start of every session before m Codex and Claude Code both work on this project. Keep `AGENTS.md`, `CLAUDE.md`, `STATUS.md`, task briefs, and dev notes synchronized so either agent can pick up the next task without relying on agent-specific memory. +## Regular agent skills + +- Use the React best-practices skill for any React or Next.js page/component work, data-loading changes, bundle/performance work, or review of those areas. +- Use the shadcn skill for any UI work involving shadcn components, Tailwind styling, overlays, forms, icons, component composition, or updates to `components.json` / `src/components/ui`. + --- ## Goals diff --git a/CLAUDE.md b/CLAUDE.md index 27731fd..6129cd1 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -6,6 +6,11 @@ This file is the canonical brief. Read it at the start of every session before m Codex and Claude Code both work on this project. Keep `AGENTS.md`, `CLAUDE.md`, `STATUS.md`, task briefs, and dev notes synchronized so either agent can pick up the next task without relying on agent-specific memory. +## Regular agent skills + +- Use the React best-practices skill for any React or Next.js page/component work, data-loading changes, bundle/performance work, or review of those areas. +- Use the shadcn skill for any UI work involving shadcn components, Tailwind styling, overlays, forms, icons, component composition, or updates to `components.json` / `src/components/ui`. + --- ## Goals diff --git a/docs/superpowers/plans/2026-06-13-react-shadcn-fixes.md b/docs/superpowers/plans/2026-06-13-react-shadcn-fixes.md new file mode 100644 index 0000000..95ee560 --- /dev/null +++ b/docs/superpowers/plans/2026-06-13-react-shadcn-fixes.md @@ -0,0 +1,141 @@ +# React and shadcn Fixes Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Fix the security-sensitive share-link authorization gap and the highest-confidence shadcn Base UI composition issues from the audit. + +**Architecture:** Add registry-level share authorization callbacks and require them in the generic share service before token creation and shared data loading. Update current modules to provide household/user-scoped authorization and share loaders, then align selected UI call sites with shadcn Base API. + +**Tech Stack:** Next.js 15 App Router, TypeScript, Drizzle, shadcn/ui Base UI, Tailwind v4, Node test runner via `tsx --test`. + +--- + +### Task 1: Share Authorization Helper + +**Files:** + +- Create: `tests/unit/share-authorization.test.ts` +- Create: `src/modules/_core/share-authorization.ts` +- Modify: `src/modules/_core/module.ts` + +- [ ] **Step 1: Write the failing test** + +Create tests for missing callbacks, denied callbacks, and allowed callbacks. + +- [ ] **Step 2: Run test to verify it fails** + +Run: `pnpm exec tsx --test tests/unit/share-authorization.test.ts` +Expected: failure because `share-authorization.ts` does not exist. + +- [ ] **Step 3: Implement the helper and registry types** + +Add `ShareContext`, `ensureEntityShareAuthorized`, `EntityTypeRegistration.canShareEntity`, and scoped `loadForShare` context typing. + +- [ ] **Step 4: Run test to verify it passes** + +Run: `pnpm exec tsx --test tests/unit/share-authorization.test.ts` +Expected: all tests pass. + +### Task 2: Wire Authorization Into Share Service + +**Files:** + +- Modify: `src/modules/_core/share.ts` +- Modify: `src/app/s/[token]/page.tsx` + +- [ ] **Step 1: Call `ensureEntityShareAuthorized` in `createShareLink`** + +Require the active user and household to be authorized before token insertion. + +- [ ] **Step 2: Pass household context into public share loading** + +Call `entityReg.loadForShare(resolved.entityId, { householdId: resolved.householdId })`. + +- [ ] **Step 3: Run typecheck** + +Run: `pnpm typecheck` +Expected: type errors until module adapters are updated. + +### Task 3: Add Module Share Adapters + +**Files:** + +- Modify: `src/modules/calendar/manifest.tsx` +- Modify: `src/modules/calendar/server/share-queries.ts` +- Modify: `src/modules/lists/manifest.tsx` +- Modify: `src/modules/lists/server/share-queries.ts` +- Modify: `src/modules/notes/manifest.tsx` +- Modify: `src/modules/notes/server/share-queries.ts` +- Modify: `src/modules/garden/manifest.tsx` +- Modify: `src/modules/garden/server/share-queries.ts` + +- [ ] **Step 1: Implement `canShareEntity` for every shareable entity** + +Use household scoping and existing private-calendar rules. + +- [ ] **Step 2: Scope `loadForShare` queries by token household** + +Require the public token household to match the entity household. + +- [ ] **Step 3: Run typecheck** + +Run: `pnpm typecheck` +Expected: pass. + +### Task 4: Fix Base UI Select Composition + +**Files:** + +- Modify: `src/modules/calendar/components/calendar-shell.tsx` +- Modify: `src/components/theme-picker.tsx` + +- [ ] **Step 1: Add `items` arrays and `SelectGroup` wrappers** + +Keep existing labels and values; do not redesign layout. + +- [ ] **Step 2: Run typecheck** + +Run: `pnpm typecheck` +Expected: pass. + +### Task 5: Targeted shadcn Menu and Icon Cleanup + +**Files:** + +- Modify: `src/components/dashboard-switcher.tsx` +- Modify: `src/components/dashboard-editor.tsx` +- Modify: `src/components/share-button.tsx` + +- [ ] **Step 1: Wrap dropdown items in `DropdownMenuGroup`** + +Preserve menu behavior. + +- [ ] **Step 2: Replace icon sizing inside shadcn buttons/menu items** + +Use `data-icon` where icons sit in `Button`; remove explicit `size-4 mr-*` from dropdown item icons. + +- [ ] **Step 3: Run lint** + +Run: `pnpm lint` +Expected: no new errors; existing warnings may remain. + +### Task 6: Final Verification + +**Files:** + +- No direct edits. + +- [ ] **Step 1: Run targeted test** + +Run: `pnpm exec tsx --test tests/unit/share-authorization.test.ts` +Expected: pass. + +- [ ] **Step 2: Run typecheck** + +Run: `pnpm typecheck` +Expected: pass. + +- [ ] **Step 3: Run lint** + +Run: `pnpm lint` +Expected: exit 0; warnings only if pre-existing. diff --git a/docs/superpowers/specs/2026-06-13-react-shadcn-fixes-design.md b/docs/superpowers/specs/2026-06-13-react-shadcn-fixes-design.md new file mode 100644 index 0000000..ebf6ac7 --- /dev/null +++ b/docs/superpowers/specs/2026-06-13-react-shadcn-fixes-design.md @@ -0,0 +1,35 @@ +# React and shadcn Fixes Design + +## Goal + +Fix the audit findings that have security or correctness impact, and make a small targeted pass on shadcn Base UI composition without turning this into a broad visual rewrite. + +## Approaches Considered + +1. **Security-first, targeted UI cleanup.** Add a registry authorization hook for share links, implement it for current modules, fix Base `Select` usage, and clean up the compact dropdown/button issues found in the audit. +2. **Full shadcn migration sweep.** Replace all `.btn`, raw forms, raw colors, and hand-rolled overlays in one pass. This would touch many dirty files and blur behavior changes with style churn. +3. **Security only.** Fix share authorization and defer UI. This leaves known Base UI composition drift in place. + +Chosen approach: **Option 1**. It fixes the exploitable path, handles the highest-confidence shadcn correctness issue, and leaves broad styling convergence to the existing shadcn-tier task docs. + +## Architecture + +Share authorization belongs in the entity registry, not in `_core` switch statements. Each shareable entity registration will expose `canShareEntity(id, ctx)` and the generic `createShareLink` action will require it to return true before inserting a token. + +Public share rendering will also pass the token household into `loadForShare(id, ctx)` so loaders can scope database reads. This prevents a bad token row from loading an unrelated entity by id alone. + +## UI Composition + +The project uses shadcn `base-nova`, so `Select` roots need an `items` prop and `SelectItem` children should be wrapped in `SelectGroup`. The calendar and theme picker selects will be updated to that shape. + +Compact menu/button drift will be cleaned where it does not require redesign: dropdown items should sit in `DropdownMenuGroup`, menu icons should rely on component icon sizing, and button icons should use `data-icon`. + +## Testing + +Add a small Node test around the new share authorization helper first. The test will prove that missing or false registry authorization rejects share creation and that approved entities pass through. Typecheck and lint will cover module hook signatures and Base UI prop usage. + +## Out of Scope + +- Full replacement of `.btn` and raw garden forms. +- Reworking command palette and quick-add sheet into shadcn dialogs. +- Measuring bundle deltas or adding dynamic imports for global overlays. diff --git a/docs/tasks/1a-badges-avatars.md b/docs/tasks/1a-badges-avatars.md new file mode 100644 index 0000000..fee5927 --- /dev/null +++ b/docs/tasks/1a-badges-avatars.md @@ -0,0 +1,104 @@ +# Tier 1: Garden badges → Badge, topbar avatar → Avatar, + fallback, share-green → semantic token + +## Goal + +Replace ad-hoc badge and avatar styling across the app with shadcn/ui component primitives. Standardize color tokens for green success/warning indicators. Create a reusable avatar fallback pattern. + +## Affected files (6) + +### Task 1a: Garden health badges → Badge variant + +**File:** `src/modules/garden/components/plant-detail.tsx` + +- **Current state (line ~44-48):** `healthBadgeClass(status)` returns hand-crafted class names (`badge-success`, `badge-danger`, `badge-warning`) not from shadcn. +- **Line 119-123:** Badge rendered as a span with those classes. +- **Change:** Replace with `` component: + - "healthy" → `variant="default"` (primary color) + - "sick" → `variant="destructive"` + - "sick/other" → `variant="secondary"` +- Delete `healthBadgeClass()` function. Remove the span and replace with Badge import + usage. + +### Task 1b: Topbar avatar → `` + +**File:** `src/components/topbar.tsx` + +- **Current state (line ~60-75):** Manual `` with inline width/height/background styles, plus conditional img tag or initial text fallback. +- **Change:** Replace with shadcn ``: + + ```tsx + import { Avatar, AvatarImage, AvatarFallback } from "@/components/ui/avatar"; + + + {userRow?.image ? ( + + ) : ( + {initial} + )} + ; + ``` + +- Remove the `getUserAvatar` function — already fetches name/email/image. Just pass the image directly. + +### Task 1c: HouseholdPill avatars → Avatar with fallback + +**File:** `src/components/sidebar.tsx` + +- **Current state (line ~106-120):** Manual span-based avatar circles for household members. Uses `avatarColor()` hash function. +- **Change:** Replace inner spans with ``: + ```tsx + 0 ? -6 : 0, + boxShadow: "0 0 0 1.5px var(--card)", + }} + > + {m.image ? ( + + ) : ( + {initial} + )} + + ``` +- The `avatarColor()` function stays as-is — it produces the color variable for the fallback background. + +### Task 1d: Create a generic Avatar fallback wrapper (reusable component) + +**File:** `src/components/avatar-fallback.tsx` (new) + +- **Purpose:** A thin wrapper that takes `(name|initial, image?)` and renders the right Avatar/AvatarImage/Fallback pattern with proper fallback initials. +- **Export:** `AvatarFallbackWithName` — accepts `{ name?: string; initial?: string; image?: string | null; size?: "sm" | "default" | "lg" }` +- Used by both topbar and sidebar avatars, removing code duplication. + +### Task 1e: share-green → semantic token + +**Files affected:** + +1. `src/components/push-opt-in.tsx` line ~104: `text-green-600 dark:text-green-400` → `text-[var(--c-success)] dark:text-[var(--c-success)]` +2. `src/modules/garden/components/care-schedule-editor.tsx` line ~185: green borders for "Active" button → use a semantic border class like `border-[var(--c-success)] text-[var(--c-success)]` +3. `src/modules/garden/components/plant-widget.tsx` urgency colors: ensure overdue/due-today colors use semantic CSS vars rather than hardcoded greens/red/amber + +**Changes:** + +- In `src/lib/themes.ts` or the global CSS root, add: `--c-success: #16a34a; dark: --c-success: #4ade80;` (or pick from existing palette) +- Replace all `text-green-600`/`dark:text-green-400` → `text-[var(--c-success)] dark:text-[var(--c-success)]` +- Similarly for border-green-400 → `border-[var(--c-success)]` + +## Acceptance criteria + +1. No hardcoded badge-success/badge-danger/badge-warning class names remain in plant-detail.tsx +2. Topbar avatar renders via shadcn Avatar components (verified by DOM inspection) +3. HouseholdPill avatars use shadcn Avatar components +4. AvatarFallbackWithName component exists and is imported by both topbar.tsx and sidebar.tsx +5. All green-600/green-500 references replaced with --c-success semantic variable +6. Zero TypeScript errors, zero lint errors + +## Steps + +1. Edit plant-detail.tsx: replace healthBadgeClass usage with component +2. Create src/components/avatar-fallback.tsx +3. Edit topbar.tsx: use Avatar from shadcn + AvatarFallbackWithName +4. Edit sidebar.tsx: update HouseholdPill to use Avatar components +5. Search for remaining green-600/green-500/border-green references and replace with --c-success +6. Run `pnpm lint` and `pnpm typecheck` to verify no errors diff --git a/docs/tasks/1b-skeleton-tabs-toasts.md b/docs/tasks/1b-skeleton-tabs-toasts.md new file mode 100644 index 0000000..d21d561 --- /dev/null +++ b/docs/tasks/1b-skeleton-tabs-toasts.md @@ -0,0 +1,183 @@ +# Tier 2: Skeleton loading states, settings Tabs navigation, toast notifications via sonner, form FieldGroup audit, manual separators → + +## Goal + +Standardize loading UX, navigation, and toast patterns across the app using shadcn components. Audit form field consistency. Replace hand-crafted separator borders with the shadcn Separator component. + +--- + +### Task 2a: Skeleton loading states + +**Files affected:** + +1. `src/app/d/[slug]/page.tsx` line ~120 — inline `` with animate-pulse divs (3 lines). Convert to `` components. +2. `src/modules/garden/components/plant-detail.tsx` line 55 — `uploadingImage` state during image upload, no visual feedback other than button text. Add a skeleton overlay or shimmer on the gallery card area. +3. `src/modules/garden/components/container-detail.tsx` line 30 — same pattern: `uploadingImage` state without skeleton. +4. Any garden page with loading states that use animate-pulse instead of Skeleton. + +**Changes:** + +1. **d/[slug]/page.tsx:** Replace inline fallback divs: + ```tsx + + + + + + }> + ``` +2. **plant-detail.tsx / container-detail.tsx:** When `uploadingImage` is true, wrap the image area with a Skeleton overlay (absolute positioned, full-width). Or add a shimmer effect using the existing skeleton component. + +--- + +### Task 2b: Settings Tabs navigation → `` + +**File:** `src/app/settings/page.tsx` + `src/components/settings-section.tsx` + +- **Current state:** Uses a sidebar nav (vertical list with URL hash sync). This is fine for desktop but doesn't use ``. +- **What needs to change:** The **garden detail pages** that have manual tab buttons need to use shadcn Tabs, NOT the settings page. The tier says "settings Tabs navigation" — checking the context: plant-detail.tsx line 164 and container-detail.tsx line 139 both have hand-crafted tab buttons with border-bottom active state. These should become `` + `` + `` + ``. + +**Changes:** + +1. **plant-detail.tsx (lines 162-180):** Replace manual tab buttons: + + ```tsx + import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs"; + + setTab(v as Tab)} className="mt-4"> + + Info + Gallery + Care + + {/* existing info section */} + {/* existing gallery section */} + {/* existing care section */} + ; + ``` + +2. **container-detail.tsx (lines 138-155):** Same pattern — info/gallery tabs: + ```tsx + setTab(v as Tab)} className="mt-4"> + + Info + Gallery + + {/* ... */} + {/* ... */} + + ``` + +--- + +### Task 2c: Toast notifications via sonner + +**Files affected:** Need to find all manual error/success messages and convert to toast calls. + +1. **`src/app/layout.tsx`:** Add `` component to the app layout root so toasts are globally available. +2. **Places that need toast conversion (search for inline `setError` / success messages):** + - `src/modules/garden/components/plant-detail.tsx` — delete confirmation success/error → `toast.success()` / `toast.error()` + - `src/modules/garden/components/container-detail.tsx` — same pattern + - `src/modules/garden/components/care-schedule-editor.tsx` line 192 (`calendarSuccess`) and error states → toast + - `src/components/share-button.tsx` — "Share link created" dialog could be a toast instead (or keep as dialog but add toast on copy) + +**Changes:** + +```tsx +// In any component that needs toasts: +import { toast } from "sonner"; + +// On success: +toast.success("Event added to calendar"); + +// On error: +toast.error("Failed to create share link"); + +// In layout.tsx (client component or client wrapper): +import { Toaster } from "@/components/ui/sonner"; + +return ; +``` + +**Acceptance for 2c:** + +- `` is present in the app shell layout +- All delete confirmations show toasts on success/error +- The share dialog can remain as-is (it's a distinct UX), but copy action adds toast confirmation + +--- + +### Task 2d: Form FieldGroup audit + +**Scope:** Audit all form field components across the app. Check for consistency in label-input-error patterns. + +**Files to audit:** + +- `src/modules/garden/components/container-form.tsx` — garden container form fields +- `src/modules/garden/components/plant-form.tsx` — garden plant form fields +- `src/app/settings/household/*` — household edit forms +- Any other server/action-driven forms + +**Audit checklist:** + +1. Do all form inputs have `