f740f40103c07356f388b0f7837f881d02127075
F2 — Vim-style keyboard shortcuts (the audit's F2 / H7 finding).
New files:
- frontend/src/hooks/useKeyboardShortcuts.ts: lightweight global
handler. Supports both single keys ('/', '?', 'Escape') and
vim-style 2-key sequences ('g d', 'g r', 'g p', 'g s' for nav).
Sequence timeout is 1500ms; pending prefix is cleared on any
unrecognised key so typing 'g' alone is safe. Suppressed when
the user is typing in an input/textarea/select/contenteditable,
or when any modifier key (Ctrl/Cmd/Alt) is held — those chords
belong to the browser or other handlers. Uses a ref so the
listener is registered once and always sees the latest callbacks.
- frontend/src/hooks/useFocusSearch.ts: tiny CustomEvent bus.
requestFocusSearch() dispatches a 'mealplanner:focus-search'
event; useFocusSearchOnShortcut(ref) subscribes and focuses the
supplied input. The decoupling lets any page opt in without the
global handler needing to know the page's DOM.
- frontend/src/components/ShortcutHelpBanner.tsx: dismissible help
dialog that slides down under the nav when '?' is pressed.
Auto-dismisses after 6s; Escape also dismisses. role=dialog +
aria-label for screen readers; the kbd elements use the
<kbd> semantic for assistive tech.
Wired in App.tsx:
- New <GlobalShortcuts /> child of <BrowserRouter> calls
useKeyboardShortcuts with the 4 nav sequences, '/' →
requestFocusSearch(), and '?' → dispatch SHOW_SHORTCUT_HELP_EVENT.
- <ShortcutHelpBanner /> mounted inside the page wrapper (after
<main>).
Pantry and Recipes now call useFocusSearchOnShortcut with a
forwardRef attached to their top search inputs. Recipes's search
already debounced via handleSearch so focusing just selects the
existing text for the user to replace. Pantry's search is a plain
controlled input, same treatment.
Behaviour summary:
- g d / g r / g p / g s → navigate to the 4 main pages
- / → focus the search input on the current page (Pantry + Recipes
only — other pages have no search)
- ? → show the help banner
- All shortcuts are no-ops inside text-entry controls, so a user
typing 'p' into the pantry search box will not trigger navigation.
Build: tsc 0 errors, vite 0 errors. 5 files, +185/-3.
Meal Planner
Self-hosted meal planning system that integrates with Lucky California grocery store, sends weekly meal proposals via email to family members, generates shopping lists, and learns from feedback.
Background
This project was born out of frustration with meal kit services (Blue Apron → EveryPlate → HungryRoot → Sunbasket) that:
- Escalate costs to 3x ingredient markup
- Fall into repetitive meal rhythms
- Force users to log into apps to manage selections
- Don't integrate with home pantry items
Features
- Grocery Integration: Scrapes Lucky California weekly ads and sales
- Family Approval Workflow: Email proposals with approve/deny; one denial swaps the meal
- Shopping List Generation: Weekly list grouped by store aisles, highlighting sales, with interactive checkboxes to track purchased items
- Pantry Integration: Specify home items to incorporate into suggestions
- Web UI: Modern interface for the whole family
- Learning: Feedback-based meal recommendations, with weekly auto-discovery of new recipes from external APIs when family preferences are signaled
- Recipe Images: Scraped from public recipe sites, AI fallback available
- Unit Conversion: Converts recipe quantities (cups, tbsp, lb) to grocery units for accurate cost estimates
Architecture
- Backend: Python/FastAPI
- Database: PostgreSQL
- Frontend: React + Tailwind CSS
- Email: SendGrid
- Hosting: Docker Compose with nginx reverse proxy
Documentation
Quick Start
# Clone and start
docker-compose up -d
# View logs
docker-compose logs -f
# Stop
docker-compose down
Family Profile
Household: 2 adults, 2 children
- One adult likes mushrooms, one child OK with them
- Three family members do NOT like mushrooms
- No allergies
- Calorie, budget, and health conscious eating
Approval Workflow
- System generates 7-day meal plan based on sales, dietary constraints, budget, variety
- Email sent to both adults with meal previews
- One denial = meal swapped; no denials = auto-approved
- Shopping list generated after approval
Tech Stack
| Component | Technology |
|---|---|
| Backend | Python 3.11, FastAPI |
| Database | PostgreSQL 15 |
| Frontend | React 18, TypeScript, Tailwind |
| Scraping | Playwright, BeautifulSoup |
| SendGrid | |
| Hosting | Docker Compose, nginx |
Languages
Python
72.5%
TypeScript
25.9%
JavaScript
0.5%
PLpgSQL
0.4%
CSS
0.3%
Other
0.2%