Design redesign (dark-first) — long-lived integration PR#89
Conversation
…t allowlists (Chunk 0c) Pre-redesign security hardening surfaced by the audit sweep, behavior- preserving for legitimate clients: - Critical: /api/ably/token had no auth check and minted a wildcard- capability token, letting any caller subscribe to any store's shopping-store:<id> channel (cross-user data leak). Now requires a session and scopes capability to the user's owned + accepted-invitation stores (subscribe+presence only; server still publishes via the API key). - ObjectId.isValid guards on meal-plans/[id] (GET/PUT/DELETE), food-items/[id] (PUT/DELETE), and admin approve/toggle-admin body userId — malformed ids now return 400 instead of 500. - Mass-assignment allowlists: recipes/[id] PUT no longer spreads the raw body into $set (blocks createdBy/createdAt/_id injection; isGlobal stays client-settable as the recipe-sharing mechanism); user/settings POST writes only themeMode + defaultMealPlanOwner via dot-notation, so a crafted body can't forge sharing-invitation grants. Adds regression tests for each, including a new ably/token test file. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Backfill behavior-preserving tests for the untouched data layer, per the audit sweep — golden-master for pure transforms, MSW path coverage for fetch wrappers, and 401/400/ownership/500 coverage for the sharing/store API routes. Utils (lib): - meal-plan-utils: golden-master for findNextAvailableMealPlanStartDate skip-advance loop + checkMealPlanOverlap edge cases (vi.setSystemTime) - shopping-list-position-utils: new (pure position math + MSW) - shopping-list-utils: cover all remaining fetch wrappers (MSW) - recipe-sharing-utils, meal-plan-sharing-utils, recipe-user-data-utils: new MSW - recipe-utils, pantry-utils: new (array/paginated normalization) - user-utils: getCurrentUserAdminStatus; date-utils: day/next-day arms; auth.ts: redirect/session/jwt callbacks API routes (14 sharing/store routes, previously untested): - stores/[id], stores/[id]/invite, stores/[id]/invitations/[userId], stores/invitations, shopping-lists/[storeId]/positions - user/meal-plan-sharing/* and user/recipe-sharing/* (invitations, invitations/[userId], invite, owners, shared-users) Full suite: 1304 tests across 125 files, green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
review-code (base main) returned READY FOR PR (0 Critical/Important). Applied the informational Minor/Nit fixes that harden Chunk 0's own work: - meal-plans/[id] invalid-id guard returns API_ERRORS.BAD_REQUEST (was a "not found" message with a 400 status) — consistent with food-items/[id] - auth.test: add jwt happy-path + missing-user coverage (was catch-path only) - meal-plan-sharing invitations/[userId]: assert the $pull filter/shape on owner-remove and self-leave (was bare toHaveBeenCalled) - recipe-sharing tests: drop no-op collection name-branching npm run check green: 1306 tests, lint clean, build OK. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… Chunk 1 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…hemeChange wiring Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…m Providers Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ree + light theme dep Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Removes theme Select, defaultMealPlanOwner dropdown, and dead window.dispatchEvent(themeChange) coupling. Route stays; renders "Nothing to settle right now — light mode will return." placeholder. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…session approvals)
…cope icon-font lint disables) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
…rred Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…rrides) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Manual Test Plan —
|
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The Recipes/Food items labels were both emitted up-front, stacking them at the top of the list above every row instead of "Food items" sitting before the first food row. Dormant in pantry (allowRecipes=false → no labels); surfaced when recipes became the first allowRecipes consumer. Render each label inline before its group's first row; lock the order with a test. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A list-above dropdown grows upward in flow, pushing the input DOWN the page; with the soft keyboard up on a phone, the input lands behind it. On phone widths, force list-below so the input stays pinned (results grow downward, away from it) and seat the field near the top of the scroll area on focus so results have room above the keyboard. Desktop keeps the upward dropdown. A richer full-screen mobile search-sheet treatment is deferred (own artboard). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The field used list-above on desktop, so the input visibly jumped down when results loaded. Switch to list-below: the input stays pinned and results open beneath it (matches the mobile behavior, which already forces list-below). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ws the viewport A list-below field low on the page opens its dropdown past the bottom of the viewport. On desktop, when the list opens (or grows as results arrive), scroll the field+results box fully into view if its bottom is below the fold — guarded so it's a no-op once visible (no scroll-on-every-keystroke), with a 16px scroll-margin so it lands with breathing room rather than flush to the edge. Phones keep the focus-time scroll. Locked with a test. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Set the viewport interactive-widget to resizes-visual so the soft keyboard overlays content instead of resizing the layout viewport. The fixed bottom nav (position:fixed; bottom:0) then stays pinned to the screen bottom under the keyboard rather than riding up above it. App-wide; browsers still auto-scroll the focused input into the visual viewport so inputs stay reachable. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The desktop scroll-into-view effect's deps are complete, so the react-hooks/exhaustive-deps disable directive was unused — and eslint --max-warnings=0 fails on unused directives. Remove it. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The interactive-widget=resizes-visual meta did not stop the fixed bottom nav from riding up with the keyboard on-device, so revert it (back to the default viewport) and handle the nav deterministically instead: hide it whenever a text field is focused (keyboard up), keying off focusin/focusout. Works regardless of how a browser treats the virtual keyboard (resize vs. overlay). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Keying the bottom-nav hide off input focus left it stuck hidden: a user can dismiss the keyboard (back gesture / down-chevron) while the field keeps focus, so focusout never fires and the nav stayed invisible until a blur (dialog close / refresh). Switch to the VisualViewport API — the viewport shrinks when the keyboard slides up and grows back when it slides down, independent of focus. Hide when it compresses past a 150px threshold vs. its tallest seen height; reappear as soon as it grows back. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…iles Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…on to a + Group affordance Retire the hand-rolled CombinedSearch combobox in the meal editor and migrate MealEditorDialog onto the shared ItemSearchField (allowRecipes, list-below). The create-food flow (useFoodItemCreator + AddFoodItemDialog) moves into the dialog; onItemCreated routes the new food through addLooseFood so it lands in the active search-target group. The combobox's in-dropdown "new group" option is replaced by a caller-owned + Group button (recipes-aligned) that appends an empty untitled group and auto-targets it via the existing pendingTargetRef machinery. CombinedSearch + its test are deleted; the combobox interaction is locked in the ItemSearchField suite. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…Phase 4 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…e shared field; drop FoodItemAutocomplete Extend the shared ItemSearchField with an opt-in retain-on-pick mode (clearOnPick=false) plus a showSelection() imperative handle, then migrate the shopping ItemEditorDialog onto it as a single-select form. Default clear-on-pick behavior is unchanged, so pantry/recipes/meal-plans are unaffected (their suites pass). Deletes the orphaned FoodItemAutocomplete. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…done Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Manual Test Plan —
|
…wiring Restores coverage lost when CombinedSearch (+ its test) was deleted in the ItemSearchField migration. Mocks useFoodItemCreator to capture onItemCreated, invokes it with a new food item, and asserts it routes into the meal as a loose item — the load-bearing wiring whose absence would silently drop created items. From /review-code finding test-001. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Seeds food items, recipes, pantry, a store, and a partial shopping list for the cross-cutting ItemSearchField manual test plan (posted to PR #89). Mirrors the existing per-chunk manifests; tagged to its own slot so cleanup is isolated. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Long-lived draft integration PR for the dark-first redesign migration. Lands surface-by-surface in chunks; stays in draft until the final squash merge into
main.docs/superpowers/specs/2026-05-28-design-redesign-migration-design.mddocs/superpowers/plans/redesign-progress.mddocs/design/weekly-eats-redesign/Per-chunk manual-test checklists are posted as their own slot comments (from Chunk 2 onward).
Chunk 1 — Foundation
Dark-first token + theme + typography + icon foundation; light mode dropped (plumbing preserved); one-time redesign setup.
src/lib/design-tokens.ts— canonical dark tokenssrc/lib/theme.ts— single dark MUI theme bridging tokens + custom palette keys/typography variants (src/types/mui.d.ts);responsiveDialogStylepreserved; tabular-nums applied app-widenext/font; Material Symbols via stylesheet<link>(next/font lacks the face)src/components/ui/Icon.tsx— decorative-by-default Material Symbols icon (realSxProps)ThemeColorMetahard-dark, Settings → placeholderreview-code --baseoverride, Vercel beta deploymentExisting screens render with new tokens/fonts (rough/mixed look interim — expected; surfaces land in later chunks). Server-side approval enforcement is handled separately.
🤖 Generated with Claude Code