Release: shopping lists (spec 001), generic share dialog #54

Merged
araemer merged 38 commits from stage into main 2026-10-03 18:00:55 +00:00
Owner

Release of stage to main.

Do not merge yet: the build is red. The stage build for 2bbe21c failed (and so did the check on #53). npm run build type-checks against the contract on recipe-platform main, which already contains spec 002's change: CompactShoppingListDto now requires openItemsPreview and openItemCount. src/mappers/tests/CompactShoppingListMapper.test.ts builds that DTO without them and no longer compiles. The release build runs the same command, so it would fail too. It turns green once the app catches up with the spec 002 contract (at minimum that test fixture). The last green stage build was f697a95 (#52).

What's in it

  • #47 Shopping lists (spec 001): named lists shared per list, a default list, items with amount / unit / comment, tick off, reorder, move, duplicate prompt, live refresh while open. Also: ApiError carries the typed error code; contract enums may be imported by kind (ADR); quantities are formatted the same in recipes and shopping lists ("0,5" instead of "0.5", scaled amounts rounded); the meal plan's create and rename dialogs use the shared NameInputDialog.
  • #52 Generic share dialog: one ShareDialog for shopping lists and meal plans. Fixes #46: non-admins can share a meal plan (user list from list-shareable). Meal plans gain avatars and changing a share's level. Dialogs taller than the screen now scroll.
  • #53 Info icon on the shopping list overview's "choose a default list" hint (InfoBanner).
  • #48 Docs: push every commit; WIP: draft PRs.

Notes

  • main is 8 commits ahead of stage, all of them merge commits from earlier releases with no content of their own; the merge is clean.
  • The shopping lists need a backend that implements spec 001 (/shopping-list…, GET /user/list-shareable, the code on error responses).

🤖 Generated with Claude Code

Release of `stage` to `main`. > **Do not merge yet: the build is red.** The `stage` build for 2bbe21c failed (and so did the check on #53). `npm run build` type-checks against the contract on recipe-platform `main`, which already contains spec 002's change: `CompactShoppingListDto` now requires `openItemsPreview` and `openItemCount`. `src/mappers/tests/CompactShoppingListMapper.test.ts` builds that DTO without them and no longer compiles. The release build runs the same command, so it would fail too. It turns green once the app catches up with the spec 002 contract (at minimum that test fixture). The last green `stage` build was f697a95 (#52). ## What's in it - **#47 Shopping lists (spec 001):** named lists shared per list, a default list, items with amount / unit / comment, tick off, reorder, move, duplicate prompt, live refresh while open. Also: `ApiError` carries the typed error `code`; contract enums may be imported by kind (ADR); quantities are formatted the same in recipes and shopping lists ("0,5" instead of "0.5", scaled amounts rounded); the meal plan's create and rename dialogs use the shared `NameInputDialog`. - **#52 Generic share dialog:** one `ShareDialog` for shopping lists and meal plans. Fixes #46: non-admins can share a meal plan (user list from `list-shareable`). Meal plans gain avatars and changing a share's level. Dialogs taller than the screen now scroll. - **#53 Info icon** on the shopping list overview's "choose a default list" hint (`InfoBanner`). - **#48 Docs:** push every commit; `WIP:` draft PRs. ## Notes - `main` is 8 commits ahead of `stage`, all of them merge commits from earlier releases with no content of their own; the merge is clean. - The shopping lists need a backend that implements spec 001 (`/shopping-list…`, `GET /user/list-shareable`, the `code` on error responses). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
docs(adr) Add shopping list refresh ADR for spec 001
All checks were successful
/ test (pull_request) Successful in 1m9s
a5661d6382
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
docs(git) Push every commit and use WIP: draft PRs
All checks were successful
/ test (pull_request) Successful in 1m4s
0079866eb6
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
docs(plan) Add shopping list implementation plan (spec 001)
All checks were successful
/ test (pull_request) Successful in 1m2s
eb28899d1b
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ApiError now carries code: ErrorCode, read from the error body or derived
from the HTTP status when the body has no known code. The error body is read
once as text and then parsed, fixing the double-consume when it is not JSON.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
New ADR partially supersedes 2026-09-20-contract-enums-in-models (only its
explicit list of 9 enums). CLAUDE.md states the rule by kind and points
server-refreshed contexts at the shopping list refresh ADR.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Response-only mappers for lists, items, shares and compact users;
ShoppingListRestResource returning only models; GET /user/list-shareable;
item input parsing, display texts, error classification and optimistic
state transforms, each with tests. Name helpers and UserAvatar accept
CompactUserModel via DisplayableUser.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Entry, overview and list routes; ShoppingListProvider loads and refreshes
the list in place (polling, visibility, pull to refresh, settle-refresh)
with optimistic tick/remove/move/reorder/remove-ticked; item rows with tap
panel and drag handle, add form with duplicate prompt, edit/move/share
dialogs; PullToRefresh and NameInputDialog basics; menu entry.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Reviewed-on: #48
On desktop the three buttons wrapped into a ragged right-aligned stack in
the narrow dialog card; stack them full-width with the primary on top, as
on phones.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Entfernen in an item's panel and Erledigte entfernen now ask for
confirmation (DeleteModal), revising plan D8 after review. The item
confirmation lives on the page, not in the dnd-kit-transformed row.
Kept as its own commit so it can be reverted on its own.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
feat(shopping-list) give a full-access user's own share the usual controls
All checks were successful
/ test (pull_request) Successful in 1m4s
a2be4dfac8
Consistent with the meal plan share dialog: the user's own share row,
marked (Du), has the permission select and the remove button. Removing it
ends their access (the page goes to the overview with the access-lost
notice); losing FULL closes the share dialog.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
fix(shopping-list) keep the rename dialog's draft across polling
All checks were successful
/ test (pull_request) Successful in 1m4s
184e659029
The list page's rename dialog was keyed on the live list version, so a
rename by someone else reset the user's typing on the next poll, and the
save used the live version, so it never got the 409. The dialog now
snapshots name and version when it opens; renameList takes that version,
and a conflict re-seeds the dialog from the refreshed list.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
test(shopping-list) cover the error-message selection and form value helpers
All checks were successful
/ test (pull_request) Successful in 1m3s
977b2b22ae
itemToInputValues and EMPTY_ITEM_INPUT_VALUES move into
shoppingListItemInputUtils next to formatAmountForInput, with tests.
shoppingListMessages stays component-side and gets its own test, since it
decides when an error is shown and when navigation makes it unnecessary;
CLAUDE.md's Tier 1 scope lists it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
fix(shopping-list) don't flash "item gone" when the whole list is gone
All checks were successful
/ test (pull_request) Successful in 1m3s
dea07c397b
An item route's 404/403 set the item-gone message in the same tick as the
reload that finds the list deleted or inaccessible, so the popup could
paint before the page navigated to the overview. The page now shows its
error popup only while the list is READY (plan 10.4 / 10.7).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
fix(shopping-list) keep (Du) visible, use the plan's prompt text, document children
All checks were successful
/ test (pull_request) Successful in 1m5s
9a46c30495
- Share dialog: only the name truncates, so the (Du) marker stays visible
  on narrow phones.
- Overview: the choose-a-default prompt uses the plan's wording; the extra
  touch-specific sentence is gone.
- JSDoc on the children props of ShoppingListProvider and PullToRefresh.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
fix(shopping-list) centre the list column, make add primary, tone down remove-ticked
All checks were successful
/ test (pull_request) Successful in 1m9s
294ea7b63e
- Overview and list page bodies use ContentBody (centred max-w-2xl column,
  like the recipe pages) instead of spanning the full desktop width; px-0
  keeps the full width on phones.
- "Eintrag hinzufügen" uses the primary style, like "Schritt hinzufügen".
- "Erledigte entfernen" is a small link-style action (TextLinkButton) with
  a 44 px tap area instead of a full button.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
fix(shopping-list) light hover tooltip in the InfoTooltip style
All checks were successful
/ test (pull_request) Successful in 1m4s
55b616e0b7
The item hover was a dark grey bubble, unlike every other tooltip in the
app. It now uses InfoTooltip's white/bordered style, sizes to its lines
(one line per reason on desktop), and never exceeds the row width, so a
narrow desktop window doesn't scroll sideways.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
fix(shopping-list) show the item hover tooltip only after a short rest
All checks were successful
/ test (pull_request) Successful in 1m3s
06bff72488
The tooltip appeared the moment the pointer touched a row, so moving the
mouse across the list made bubbles flicker. It now appears after the
pointer rests on a row for 500 ms and hides immediately on leave
(DELAYED_HOVER_TOOLTIP_CLASS in config/layout.ts). Touch devices are
unchanged: no tooltip, the tap panel shows the same text.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
refactor(meal-plan) use NameInputDialog for creating and renaming meal plans
All checks were successful
/ test (pull_request) Successful in 1m9s
f49a258665
MealPlanCreateDialog and MealPlanRenameDialog were the same single-field
dialog as the new NameInputDialog (trimmed, non-empty, rename disabled
while unchanged). The meal plan page now uses NameInputDialog with its own
texts, and the two files are gone (follow-up from plan 001, D8).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Reviewed-on: #50
- Inputs in NameInputDialog and ShoppingListItemFields rely on the global
  input style from index.css instead of restating (and drifting from) it;
  the item fields keep only min-h-11 for the tap target.
- DisplayableUser becomes UserDisplayModel in src/models, a documented
  interface that UserModel and CompactUserModel both satisfy.
- Button renders type="button" and gains submitsForm; the add form uses
  Button for both actions instead of hand-composed native buttons.
- ShoppingListBadge becomes basics/Badge with a BadgeVariant.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SearchField, LinkInput and MealPlanDayNoteEditor restated the global
input/textarea style from index.css (border, background, focus ring).
They now keep only their deliberate differences: the search field's icon
padding and compact toolbar look, the link input's tiny toolbar size and
thinner ring, the note's small text and fixed size.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Badge gains success and neutral variants (dev console an/aus) and a
colorClassName override for domain palettes; the AI import status keeps
its colours in aiAnalyticsColors.ts, shared with the chart.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
HeaderSwitchTitle's plain title positioned the bee outside its box
(-right-12), so a long title that wrapped to the full width on a phone
pushed the bee past the screen edge. It now reserves the bee's width as
padding, as LogoHeading does; one-line titles look the same as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Reviewed-on: #51
- Text buttons, inputs and selects keep the app's defaults (Button ~40 px,
  global input ~42 px); the per-use min-h-11 on them is gone. The 44 px
  touch target applies only to small controls: IconButton's touchTarget
  prop for icon buttons in rows, and TOUCH_TARGET_CLASS /
  TOUCH_TARGET_HEIGHT_CLASS in config/layout.ts for the drag handle, the
  row body and the remove-ticked link. Revises plan step 10's blanket
  44 px rule.
- basics/Checkbox replaces the item row's hand-built checkbox.
- basics/CollapsibleSection replaces the done section's fold header.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
docs(plan) record the deviations from plan 001 in an append-only section
All checks were successful
/ test (pull_request) Successful in 1m3s
58611570a5
The approved plan stays unchanged; a Deviations section lists each place
the implementation departs from it (plan reference, what changed, why,
who decided), the status of the GATE C points, and the follow-up issues.
First example of a convention proposed for all plans in recipe-platform.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- ShoppingListErrorKind becomes ShoppingListErrorType (the app's const
  enums are named ...Type); action results report errorType.
- NOTICE_MESSAGES documents why router state carries a notice id.
- formatItemQuantity becomes formatQuantity in utils/quantityUtils and is
  used for recipe ingredients too: German decimal comma, scaled amounts
  rounded to three fraction digits.
- IngredientMapper turns the backend's null amount/unit into undefined.
  The backend sends null contrary to the contract's optional fields; the
  shared formatter exposed it as a crash of the recipe detail page during
  verification, before this was pushed.
- Plan deviations recorded.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
fix(meal-plan) share via list-shareable using a generic ShareDialog
All checks were successful
/ test (pull_request) Successful in 1m7s
024a8987d7
MealPlanShareDialog loaded its user list with fetchAllUsers, which is
admin-only, so non-admins could not share a meal plan (#46). Both share
dialogs now use a generic basics/ShareDialog with candidates from
GET /user/list-shareable (hooks/useShareableUsers). Each feature keeps its
permission levels, API calls and error texts in a thin wrapper. Meal plans
gain avatars and changing a share's level (saveMealPlanShare with id and
version), which the API already supported.

Fixes #46

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
fix(ui) let dialogs taller than the screen scroll
All checks were successful
/ test (pull_request) Successful in 1m5s
8d21e3b882
BasicDialog centred its card with items-center in a fixed overlay that
couldn't scroll, so a dialog taller than the screen (e.g. the share dialog
on a phone in landscape) was cut off at the top and bottom with no way to
reach the close button or the actions. The overlay now scrolls and the
card is centred with my-auto, which top-aligns it when it doesn't fit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Reviewed-on: #47
Reviewed-on: #52
feat(shopping-list) show the choose-a-default hint with an info icon
Some checks failed
/ test (pull_request) Failing after 46s
e0a948552c
The hint on the shopping list overview was a bare text card. It is now an
InfoBanner: the same neutral white card with an accent-coloured info icon.
The overview's deleted / access-lost notice uses the same component in its
amber warning look, so both boxes share one implementation.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Reviewed-on: #53
chore(ci) Build against the platform commit pinned in contract.ref
All checks were successful
/ test (pull_request) Successful in 1m3s
d30deae2ae
CI cloned recipe-platform at the head of main, so a contract merged there
for a feature this repo does not implement yet broke every build. The
workflows now check out the commit named in contract.ref after cloning.

Pinned: c69d1e3be45f716404d891be07776395ffda629b, the last platform main
commit before the spec 002 contract.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Merge pull request 'chore(ci) Build against the platform commit pinned in contract.ref' (#55) from contract-pin into stage
All checks were successful
/ build (push) Successful in 1m25s
/ test (pull_request) Successful in 1m33s
8dfb1b2a4c
Reviewed-on: #55
chore(ci) Check the contract pin in the PR pipeline
All checks were successful
/ test (pull_request) Successful in 1m9s
/ contract_pin (pull_request) Successful in 3s
1f709e8d7c
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Merge pull request 'chore(ci) Check the contract pin in the PR pipeline' (#57) from contract-pin-check into stage
All checks were successful
/ build (push) Successful in 1m10s
/ test (pull_request) Successful in 1m4s
/ contract_pin (pull_request) Successful in 2s
9eb0e3579c
Reviewed-on: #57
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
araemer/recipe-app!54
No description provided.