Replaces collections.isPublic (boolean) with collections.visibility
(private/unlisted/public/followers — same enum recipes use). Two-step
migration (0050 adds+backfills, 0051 drops isPublic) since drizzle-kit's
add+drop-in-one-diff rename heuristic needs an interactive prompt we
can't satisfy here.
New collectionVisibleToViewer(viewerId) in lib/visibility.ts mirrors the
existing recipe helper (author always sees own; public/unlisted visible
to anyone; followers-only via the same user_follows EXISTS pattern) —
used by the collection detail page, its print view, fork, and favorite,
replacing their old `or(isPublic, own)` checks.
Create/edit collection dialogs get the same 4-option visibility select
as the recipe form instead of a public/private checkbox.
Collection PDF export now generates a QR code (qrcode, same as the
recipe PDF) linking to /collections/{id}, shown only when visibility is
public/unlisted — same "would an anonymous scanner actually resolve
this" rule as the recipe QR.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Drag-reorder used verticalListSortingStrategy on a multi-column grid,
which computes wrong transforms for grid reflow — swapped to
rectSortingStrategy so cards actually animate live while dragging.
- Grip handle was rendered as a sibling of (not a descendant of) the
`group` element its `group-hover:opacity-100` depended on, so it was
permanently invisible. Fixed the DOM nesting and made it always
partially visible instead of hover-only.
- common.edit was missing from both locales (not just French) —
edit-collection-dialog.tsx was the first caller to hit it.
- Root cause of "generated in my language but Translate still shows":
generate-meal, meal-plan/generate, and adapt never set recipes.language
on the row they inserted, so the button's `!recipe.language || ...`
check always fell back to "show it". Fixed at all three insert sites.
- Translate dialog was entirely hardcoded English (title, description,
language names, buttons) despite i18n keys already existing for most of
it — now uses them, plus new translated language-name keys.
- Recipe tags now render on the recipe detail page (previously grid-card
only).
- Collection header actions converted to icon-only + tooltip, matching
the recipe page's pattern instead of icon+label buttons.
- Collections list search now also matches recipe titles inside each
collection, not just the collection's own name/description.
- Explore page links to /collections/explore next to its tabs.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Seven related improvements to collections:
- Drag-and-drop reorder (dnd-kit, same pattern as the shopping list) — new
collection_recipes.position column (migration 0049, backfilled from
existing added_at order so nothing jumps around on upgrade).
- Search collections by name/description (server-side, list page) and
search recipes within a collection (client-side filter, already loaded).
- Edit collection: name/description/tags/private notes via a new dialog;
new collections.notes + collections.tags columns.
- Delete collection with a choice to also delete its recipes — only ones
the deleting user actually owns, never recipes shared in by others.
- Collection detail (both owner and public view) now renders the same
RecipeGridCard used on /recipes, instead of the older, plainer RecipeCard.
- Collection list cards redesigned — photo-collage preview (first 4 recipe
covers/placeholders), tag badges, cleaner layout.
- Fixed the recipe count shown on a collection card: the query capped the
`recipes` relation at 1 for thumbnail purposes and then read `.length`
off that same capped array, so it never showed more than 1. Now a
proper grouped count query, separate from the thumbnail fetch.
New/changed endpoints documented in OpenAPI: PATCH /collections/{id}/reorder,
DELETE /collections/{id}?deleteRecipes, PUT /collections/{id}'s new
notes/tags fields.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>