Commit Graph

17 Commits

Author SHA1 Message Date
Arnaud 13128df19f feat: locale-based generation, describe field for batch cooking; fix bulk-bar width and sourceUrl not saving
- AI recipe generation always used the app's own locale rather than a
  separate language picker — removed the picker, generation and the
  saved recipe's language now both follow useLocale() directly.
- Bulk-select action bar had an ungated max-w-md that capped it at 448px
  even past the sm: breakpoint where sm:w-auto was supposed to let it
  grow to content — added sm:max-w-none so desktop never scrolls.
- Batch-cooking generation had no free-text "describe" field like the
  standard recipe generator does — added one (BatchCookFields, both the
  standalone dialog and the AI dialog's batch tab), threaded through the
  API route and generateBatchCook's prompt as additional guidance.
- Recipes imported from a URL never actually got their sourceUrl
  persisted — CreateRecipeSchema didn't declare the field, so Zod
  silently stripped it before it ever reached the insert. The detail
  page's "Source: <hostname>" link already existed but was dead code
  for every real import until now.

Verified all 4 live: generate dialog has no language selector, batch
tab has a working describe textarea, bulk-select bar renders at full
content width with zero horizontal overflow on desktop, and a recipe
created with sourceUrl now round-trips and renders its source link.
2026-07-12 15:06:48 +02:00
Arnaud 546ba98d2f fix: photos lost on save, multi-upload only kept last file, thumbnails 400
recipe-form/API routes were never sending/persisting photos on create+update.
PhotoUploader accumulated uploads via a stale closure over the photos prop, so
only the last file of a multi-select survived. Once both were fixed, thumbnails
still 400'd because Next's image optimizer blocks upstream hosts resolving to
private/loopback IPs (localhost:9000 MinIO) — skip optimization for
storage-hosted photos since they're already web-sized user uploads.
2026-07-12 13:16:37 +02:00
Arnaud 2682eba2be fix: presigned upload URLs, card badge alignment, filter menu UX, floating save
- Presigned upload URLs were signed against STORAGE_ENDPOINT (the
  internal docker hostname, e.g. http://minio:9000) instead of a
  browser-reachable URL — uploads/edits with photos were broken in
  prod. Now signed with a separate client bound to STORAGE_PUBLIC_URL,
  which also needed threading through as a Docker build arg (CSP
  headers are computed at build time) and into compose.prod.yml/
  .env.production (gitignored, not committed).
- recipe-form.tsx used the wrong i18n namespace for the visibility
  label (t("recipeForm") instead of t_recipe("recipe")), causing
  MISSING_MESSAGE in French.
- Compact recipe-list view: batch-cook rows showed no dish count, and
  the date/difficulty columns shifted per-row depending on whether a
  difficulty badge was present — reserved a fixed-width slot for it.
- All three card icon badges (batch-cook, favorite, visibility) now
  share the same muted color instead of the batch badge standing out.
- Recipes filter dropdown: clicking a filter option closed the whole
  menu, and the tag text input couldn't be typed into (the menu's
  roving-focus/type-ahead handling was intercepting keystrokes) — menu
  items now stay open on click, and the tag input stops event
  propagation so it behaves like a normal text field.
- Recipe form: added a floating save/cancel bar so long recipes don't
  require scrolling back down to save.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-12 10:53:12 +02:00
Arnaud 1baf5ce20e fix: recipe card badge placement, missing tooltips, untranslated label
- Batch-cook indicator moved from next to the title into the icon row
  alongside favorite/visibility (all three now get a tooltip, matching
  the existing favorite-button pattern).
- Favorite button's tooltip text was hardcoded English ("Save"/
  "Saved") instead of using the translation keys already used for its
  aria-label.
- Shortened the batch-cook generate dialog's "Generating…" label
  (fr.json edited by user) — the long text was the real cause of the
  modal overlap, not the dialog structure.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-12 10:29:13 +02:00
Arnaud 002f14ced0 feat: batch-cook shopping list (already worked) + leftover expiry reminders
Shopping list add already worked generically for batch-cook recipes —
no code needed there.

New: mark a specific batch-cook dish as "cooked today", track its
fridge expiry (cookingHistory.batchDishId), surface a "Leftovers
expiring soon" widget on the pantry page, and send a daily push+email
reminder via a new /api/internal/cron/leftover-reminders endpoint
(mirrors the weekly-digest cron pattern; doesn't use the social
notifications table, which requires a non-null actor and isn't built
for self-reminders).

Also fixes, from user-reported bugs:
- Recipe cards showed no batch-cook badge/dish-count/prep-time in some
  views — added dishCount + prepMins/cookMins (now generated by the AI
  and persisted) to the card component and /recipes query.
- Batch-cook descriptions occasionally contained raw markdown
  (**bold**) — added explicit "plain prose only" prompt instructions
  and a stripMarkdown() defensive fallback at render time.
- Truncated/cut-off descriptions — the generateObject call had no
  maxOutputTokens set, so long structured responses could get cut off
  mid-field; now capped explicitly at 8000.
- Generate dialogs (batch-cook + the main AI dialog) could show
  buttons unreachable once the progress bar appeared mid-generation —
  restructured so the action row is pinned outside the scrollable
  content area, not affected by content height changes.
- /api/internal/* routes were being redirected to /login by middleware
  before their own CRON_SECRET check ever ran (pre-existing bug,
  affected the weekly-digest cron too) — added to PUBLIC_PATHS.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-12 10:03:52 +02:00
Arnaud 646c97128d refactor: fold batch-cooking into the main recipes list and Generate dialog
Batch-cook recipes no longer live in a separate /batch-cooking section:
- Removed the dedicated page/route and its nav button.
- /recipes shows both kinds together, with a chef-hat badge marking
  batch sessions, and a filter to isolate/hide them.
- "Batch cooking" is now a third tab in the existing "Generate recipe
  with AI" dialog (alongside Describe/Photo) instead of a separate
  button — one AI entry point instead of three.
- Extracted the counters/difficulty/dietary form into BatchCookFields,
  shared between that dialog and the meal-planner's batch-cooking
  dialog.
- Also fixed a dialog resize issue (progress bar mounting mid-generate
  grew the dialog's height, which the positioning library didn't
  always handle) by reserving space up front and capping dialog height
  with a scroll fallback.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-12 09:31:04 +02:00
Arnaud e0d39c86ef fix: clicking the favorite button on recipe cards still navigated to the recipe
stopPropagation() alone doesn't cancel a Link's native anchor navigation —
that's the browser's default action for the click reaching the <a>, not
a bubbling JS handler propagation can block. Needed preventDefault() too.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-10 16:09:06 +02:00
Arnaud 0cb926bd26 fix: grid-card favorite button too prominent, move next to visibility icon
Was floating over the cover photo with a circular dark backdrop — too
heavy. Moved into the bottom meta row next to the visibility lock icon,
plain ghost icon button matching the recipe detail page's style exactly
(no background, same size/variant). List and compact rows were already
placed there with no backdrop, only the grid card needed this.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-10 15:50:56 +02:00
Arnaud 580d5bbf2f feat: favorite button on the My Recipes grid; fix mobile overflow in bulk-action bar
- Added a heart toggle to all three recipe-grid views (grid card overlay,
  list row, compact row), hidden while in multi-select mode. Detail page
  already had it; this was the other place it made sense.
- recipes/page.tsx now fetches which of the current page's recipes are
  already favorited and passes it through.
- FavoriteButton gained an optional iconClassName so the unfavorited heart
  reads on a photo overlay (was invisible in light mode against the dark
  backdrop — filled/favorited state was already fine since it's opaque red).
- Fixed the floating bulk-select action bar overflowing both edges of the
  viewport on mobile (screenshotted: centered with no max-width, wider
  than the screen, clipping "Supprimer" on one side and the selected-count
  label on the other). Now caps to viewport width, scrolls horizontally
  as a fallback, and drops button text labels below sm (icon + aria-label
  + tooltip only), matching the icon-row convention used elsewhere.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-10 15:37:23 +02:00
Arnaud 362f65656b fix: audit fixes — tier-quota bypass, webhook SSRF, auth hardening, pagination, a11y
Full audit (bugs/UI-UX/backend/feature-gap) turned up a money-leak AI quota
bypass, webhook SSRF, and a long tail of missing pagination/auth/a11y work.
Fixes land together since HANDOFF.md tracked them as one backlog.

- AI routes charge tier quota before generating; nutrition POST is author-only
- Webhook dispatch re-validates URL per delivery (SSRF/DNS-rebinding), treats
  redirects as failures; recipe.published now actually dispatches
- New indexes/unique constraints on recipes, meal-planning, comments FK cascade
- Recipe PUT/restore snapshot only inside the transaction, after validation
- Recipe DELETE cleans up S3 objects (recipe + review photos)
- Optimistic UI (favorite/star/follow/shopping-list) rolls back on failure
- Upload presign enforces file size cap + per-tier storage quota
- Route-level loading/error/not-found states across (app), admin, and root
- middleware.ts guards (app)/admin; requireAdmin checks DB role, not cached
  session; rate limiting applied to both session and API-key branches,
  bucketed per key; Stripe webhook dedupes by event id
- Pagination added to recipes, feed, profile, comments, pantry, admin tables
- Nav shows real avatar + profile link + dark-mode toggle; destructive actions
  standardized on AlertDialog
- Unsaved-changes guard + real ingredient/step validation on recipe form;
  canonical /recipes/[id] used in-app; next/image migration; aria-labels and
  alt text across icon buttons, avatars, recipe photos
- packages/api-types removed (zero callers, too drifted to safely rewire);
  openapi.ts and ai-keys error shape drift fixed; BYOK decrypt failures now
  surface instead of silently falling back to the platform key

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-09 21:50:35 +02:00
Arnaud a8406e9963 feat: add bulk "add to collection" action on recipes page
Select recipes → add to an existing collection or create a new one inline. Fixes duplicate "visibility" i18n key bug found during verification.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-09 15:06:34 +02:00
Arnaud 64f45ed74d feat: add list and compact view modes to recipes page
Grid was the only layout. Add a view-mode toggle (grid/list/compact)
persisted to localStorage:
- List: thumbnail + description + inline metadata, one per row
- Compact: dense single-line rows (thumb, title, servings/time/
  difficulty/date/visibility) for scanning large libraries
Selection mode and bulk actions work identically across all three.

Also fixes a real bug hit while wiring this up: "recipe.visibility"
was defined twice in the messages files — once as a flat string
("Visibility", used as the bulk-actions dropdown label) and once as
the {private,unlisted,public} label object. The object silently won
in JSON, so t("visibility") resolved to an object and next-intl threw
INSUFFICIENT_PATH the moment you opened that dropdown. Renamed the
flat one to visibilityMenuLabel.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-09 09:47:39 +02:00
Arnaud eb424d8c04 fix: mobile layout fixes, i18n coverage, and recipe share link
Mobile:
- Recipes search bar full-width on mobile instead of capped narrow
- Cook mode ingredients panel stacks above the step instead of
  squeezing it into a narrow column
- Version history Compare/Restore buttons wrap onto their own row
- Recipe edit ingredient fields wrap instead of forcing horizontal
  scroll on narrow viewports

i18n: translates remaining hardcoded strings across recipes
filter/sort, adapt-recipe and AI variations dialogs, the full
settings section (sidebar + 6 sub-pages + BYOK/model-prefs/
API-keys/webhooks managers), explore tab, collections (new/fork/
share dialogs), meal planning (planner, AI generation phases, new
shopping list, shared-plan view), photo import, recipe bulk-select
toolbar, and recipe action-button tooltips. Also fixes the recipes
page subtitle, which wasn't just unworded but missing its {count}
interpolation entirely — it always rendered as the bare word
"results" regardless of how many recipes existed.

Feature: adds a ShareRecipeButton that copies the public /r/{id}
link to the clipboard, with a notice when the recipe isn't Public
yet.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-02 15:13:51 +02:00
Arnaud 2154512e54 fix(i18n): translate remaining recipe card/detail strings, localize AI responses
Recipe cards, the recipe detail page, and meal planner still rendered raw
"{n} servings"/"{n}m cook"/"{n} srv" text and untranslated difficulty labels
outside the earlier i18n pass. Also wires the user's locale into AI features
that previously always answered in English regardless of app language:
recipe chat, ingredient substitution, recipe ideas, and generate-from-idea.
The recipe-language picker in the AI generate dialog now defaults to the
user's locale instead of always defaulting to English.
2026-07-02 08:25:51 +02:00
Arnaud 01fdbb880b feat(i18n): translate hardcoded strings across print, public recipe, and recipe management UI
Wires dozens of components and server pages to next-intl instead of hardcoded
English text, and adds a lightweight getMessages/formatMessage helper for
server components (print pages, public recipe page, cook metadata) since
next-intl/server isn't wired into this app's routing. Backfills missing
en/fr message keys that existing code already referenced but fr.json lacked.
2026-07-02 07:58:18 +02:00
Arnaud 8b57a3fd87 Update features and dependencies 2026-07-01 11:10:37 +02:00
Arnaud 84d6cfeb07 feat(recipes): full recipe CRUD with photos, print, version history
List, detail, new, edit pages. Server-side pagination, dietary tags, difficulty.
Photo upload to S3-compatible storage. Version history. Multi-select grid with
bulk delete/visibility. Print view. Delete confirmation dialog.
2026-07-01 08:10:11 +02:00