Design redesign (dark-first) — long-lived integration PR#89
Conversation
… 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>
…gn in Chunk 1 The custom display*/label* variants set fontVariantNumeric, but components still use standard MUI variants until per-surface chunks migrate them. Applying it on body makes tabular figures effective now (design-system.md intent). + theme test lock. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ence ids Seed blocks fed non-hex / ObjectId ids into fields the app reads via ObjectId.createFromHexString (no isValid guard) → HTTP 500 when the seeded doc is opened through the normal route: - recipes: ingredient.id was a BSON ObjectId (foodItemsRef path) and a 'placeholder-food-N' string (default path); coerce to hex string + valid-hex placeholders. - meal-plan: slot MealItem.id used 'placeholder-recipe-1' (non-hex) on the DEFAULT (no recipesRef) config → 500 on the meal-plans list AND detail. Update the two block tests that codified the buggy shape. Audit of the other 7 blocks found no id-shape mismatches. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Removes the middleware approval gate + auth.ts jwt 'update' refresh + the middleware test added in Chunk 1. The dedicated #83 effort (fix/83-unapproved- users-access-issue, off main) is further along and more complete: middleware gate + requireApprovedSession() helper + per-route data enforcement. It lands in prod via main and reaches this branch via the normal main->branch back-merge, so duplicating a middleware-only subset here just creates a back-merge conflict. Prod runs the same prod DB with the same client-only gating today, so the beta gains no incremental protection from a beta-only server gate — the real risk closes for prod AND beta when #83 merges. Restored middleware.ts / auth.ts / auth.test.ts to origin/main state. Kept: src/components/__tests__/AuthenticatedLayout.test.tsx — locks the CLIENT approval-gate mount so Chunk 2's AuthenticatedLayout rewrite can't silently drop it (redesign-scoped, complements #83's server-side tests). Do NOT close #83 from this branch. 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>
A trailing/leading space leaked into the search request — the empty-check trimmed but the actual fetch sent the raw input, so "chicken " hit /api/food-items?query=chicken%20 and returned different results than "chicken". Trim once in performSearch and use it for the empty-query check and both fetch URLs. The field still displays the raw text the user typed; only the wire value is trimmed (matching ItemSearchField's display gate). Applies to all four ItemSearchField surfaces via the shared hook. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
App-wide toastPlacement centered every Snackbar with left:50% + right:auto + translateX(-50%). A fixed shrink-to-fit element positioned that way can only use ~50vw before wrapping (the space from the 50% mark to the right edge); the translateX re-centers it, so it reads as a too-narrow pill with room to spare on both sides. On mobile that wrapped short messages like "Added to your pantry" onto two lines. Split toastPlacement by breakpoint: desktop keeps the centered content pill; mobile centers inside a full-width flex container (left:8/right:8 + justifyContent:center + transform:none) so the toast has the full viewport width before wrapping. Verified on the live page at 430px — the real toast goes from 215px/2 lines to 414px/1 line, even with the longer error string. Also give the pantry Alert width:100% to match the four sibling pages. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The meal editor used a single shared footer search plus a "search target" you set with a "+ Add to group" button — but that button only set the target silently (the footer field never focused), so clicking it appeared to do nothing: "the picker does not open." Adopt the recipes ingredient-editor model instead: every group renders its OWN always-visible ItemSearchField inside it, so adding to a group means typing in that group's picker. Items (and created food) route straight into the group. Removes the searchTarget/pendingTargetRef machinery, the "Adding to:" banner, the body click-to-clear, and the "+ Add to group" affordance. The footer field now only adds loose items. Each group also gets a raised bounding card (surface.raised, padding, rounded corners) matching the recipes group cards. Tests rewritten for the new model: a group's inline picker adds into that group; create-food routes loose (footer) vs into-group (group picker). 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