2026-03-26
What shipped today
Closing the ranking feedback loop
The biggest fix of the day was a one-line product gap: post-meal ratings were being collected but never actually affecting future recommendations. When a family member rates a meal 4–5 stars, that data lives in recipe_bookmarks with bookmark_type: "tried". The ranking engine in lib/ranking/score.ts has explicit weights for this (bookmark_tried_positive: +2, bookmark_tried_negative: -2) — it was designed for this from the start. But plan/page.tsx was never loading recipe_bookmarks and never passing them to rankCandidates. The fix was adding one entry to the Promise.all batch and one field in the call. Now recipes the family has loved will bubble up in the plan builder sidebar, and recipes they didn’t enjoy will fall. The core “learn from what you actually eat” promise now works end to end.
Test coverage for critical server actions
Three of the most consequential server actions in the app had zero tests. This session added 27 tests across feedback/actions.ts, vote/actions.ts, and plan/actions.ts. The feedback actions were the most complex: they do multi-step bookmark upserts (check-then-insert-or-update), update recipe_history, and require household ownership verification. The vote actions guard the voting window by status and do a delete-then-insert to replace all votes atomically. The plan finalize is one-way — it sets cycle status, marks the plan as finalized, and inserts recipe history for every entry — so covering its guard conditions (cook-only, non-empty, cycle ownership) is high value. A debug finding worth noting: the finalizePlan action calls redirect() at the end; mocking next/navigation to vi.fn() (no-op) is the right approach since Next.js uses redirect as a throw internally.
Also completed earlier in the session: tests for the cadence-nudge cron route (7 tests, all 5 nudge types covered). A subtle test design insight came from this: the feedback nudge test must return a non-empty active-cycles result for the first weekly_cycles query to prevent the early-return guard from firing before the code even reaches the finalized-cycle loop.
Error handling and UX fixes (carried forward from previous session)
These were committed earlier but worth noting: feedback-section.tsx now rolls back optimistic state and shows an error message if submitMealFeedback or markMealSkipped fails. The shopping list handleAdd now shows errors instead of swallowing them. addShoppingItem and deleteShoppingItem now revalidate the specific shopping page instead of /. The alert dialogs for destructive voting, plan finalization, and opening voting replaced the window.confirm calls throughout.
Scout findings filed
Scout ran and found two significant gaps beyond what was already tracked. Issue #322 (past weeks list has no feedback summary — no average rating or “ate N/7 meals” for finalized weeks) and #318 (AI suggestions have no loading or error state in the candidate selector) were filed as needs-prep. Both need design decisions before they’re grindable.
Completed
- #315 — Add tests for /api/cron/cadence-nudge route
- #319 — Meal feedback ratings not passed to rankCandidates
- #320 — Add tests for feedback/actions.ts
- #321 — Add tests for vote/actions.ts
- #323 — Add tests for plan/actions.ts
(Committed in a prior session in the same context: #309, #311, #312, #313, #314, #316, #317)
Release progress
No milestones have issues assigned yet — all milestones show 0 open issues, which reflects the issue tracker structure rather than actual completion state. The March 2026 milestone exists but no issues are milestone-tagged.
Carry-over
- #318 (needs-prep) — AI suggestions in candidate selector have no error state or loading indicator. Filed but not prepped or executed.
- #322 (needs-prep) — Past weeks list shows no feedback summary. Questions: what data to show (average rating? meal count?), visual treatment, which cycle statuses to include.
- #302 (ready-for-prep) — Vote requests (type/recipe) are stored but never surfaced to the cook. This is a meaningful feature gap — the cook can’t see what family members asked for when building the plan.
- #308 (ready-for-prep) — No browseable cooking history — no way to see what was planned/cooked across past weeks at the household level.
Risks
No new risks. Test coverage is now meaningfully better for the three most dangerous server actions (finalize, vote, feedback).
Flags and watch-outs
npm auditshows 10 vulnerabilities (9 moderate, 1 high) — all inpicomatchvia dev dependencies (vite, vitest). No production exposure. Low priority but worth a cleanup pass.- The
revalidatePath("/")calls insettings/actions.tsandfamily/[id]/onboarding/actions.tsare still present — these are broad but likely intentional for their contexts (household setup is a one-time flow).
Next session
- Prep #318 — AI suggestions error/loading state in candidate selector. Read
web/src/app/(app)/weeks/[id]/candidates/and thesuggestDinnersaction to spec it. - Prep #302 — Vote requests surfacing. Read the
weekly_requeststable usage and where cook-facing UI is in the plan builder to decide where to surface it. - Exec #318 or #302 — whichever is simpler after prep.
- Exec #308 — cooking history view. This one may need a new
/historypage or a tab on the recipes page.
Why customer tools are organized wrong
This article reveals a fundamental flaw in how customer support tools are designed—organizing by interaction type instead of by customer—and explains why this fragmentation wastes time and obscures the full picture you need to help users effectively.
Infrastructure shapes thought
The tools you build determine what kinds of thinking become possible. On infrastructure, friction, and building deliberately for thought rather than just throughput.
Server-side dashboard architecture: Why moving data fetching off the browser changes everything
How choosing server-side rendering solved security, CORS, and credential management problems I didn't know I had.
The work of being available now
A book on AI, judgment, and staying human at work.
The practice of work in progress
Practical essays on how work actually gets done.
Designed to learn, built to ignore
The most dangerous organizational failures don't throw errors. They look fine, return results, and quietly stay frozen at the moment of their creation.
The variable that was never wired in
The gap between having a solution and using a solution is one of the most persistent failure modes in organizations. You see the escaped variable. You see the risk register. You assume the work is done.
Your empty queue isn't a problem
Dropping a column from a production database is the organizational equivalent of admitting you were wrong. Five projects cleared their queues on the same day, and the bottleneck that emerged wasn't execution — it was taste.