feat(contract) #001 Shopping lists: epic E01, spec A and contract #20

Merged
araemer merged 11 commits from 001-shopping-lists into main 2026-10-02 16:23:29 +00:00
Owner

Epic E01 (shopping lists), its first spec (spec A), and the GATE A output for spec A. The epic, the spec and GATE A are approved.

Epic and spec

  • docs/epics/E01-shopping-lists.md: the idea, split into six specs (A lists, B filling from a meal plan, C ingredient catalog, D units, E shop layouts, F offline mode), with dependencies, order and the decisions that span them.
  • docs/features/001-shopping-lists.md: spec A. Named lists with a per-user default, sharing at read-write or full access, items with name, amount, unit and comment, ticking into a done section, manual reordering, moving between lists, and "why is this here?".

GATE A: contract and ADRs

Contract (api-contract/src/)

  • shopping-lists.ts (new): the shopping list domain. List actions live under /shopping-list/:id (GET, add-item with a server-side duplicate check, add-share, reorder, remove-ticked, set-default). Item actions use /shopping-list-item/:itemId/… (edit, tick, untick, move, delete), and share actions use /shopping-list-share/:shareId/…. GET /shopping-list/all is the overview.
  • shared.ts: CompactUserDto, next to UserDto.
  • errors.ts (new): ErrorCode and ErrorResponse { error, code }.
  • users.ts: GET /user/list-shareable, a share picker that every user can call, returning CompactUserDto.

Cross-cutting ADRs

  • docs/adr/2026-09-27-shopping-list-contract.md: default list, item reasons (with a source, extensible for spec B), duplicate check, version covering content only, reorder, tickedBy, owner in the overview.
  • docs/adr/2026-09-27-required-error-code.md: every error carries a required machine-readable code.
  • docs/adr/2026-09-27-compact-user-dto.md: other users appear only as CompactUserDto.
  • docs/adr/2026-09-30-get-reads-and-shallow-nesting.md (from review): reads without a body use GET; sub-resources use shallow nesting, with at most one id per path.

docs/api-conventions.md is updated to match. It also writes down the existing naming practice: reduced views are named Compact<Entity>Dto and live next to their full DTO.

Spec changes made during the discussion

  • No automatic default list. A missing default always shows the overview with a prompt to choose one.
  • The overview shows a shared list's owner, not whoever shared it.
  • A ticked item shows who ticked it off and when, in the same hover or tap as "why is this here?".
  • Affected Repos: recipe-backend and recipe-app are both affected.

Follow-up issues found along the way

  • araemer/recipe-app#46: non-admins cannot share meal plans; meal plan shares expose the full UserDto.
  • #22: comments, ratings and tags expose other users' email and role.
  • #21: feature request for a nickname and a choice of display name.
  • #23: legacy routes that break the new GET and shallow-nesting conventions.

Workflow

  • The architect and po agents now end their assessment with a numbered list of open questions, as headlines only, and then discuss them one at a time. For the PO, the list is a living agenda that answers may change visibly. See .claude/agent-adr/architect/2026-09-27-architect-one-question-at-a-time.md and .claude/agent-adr/po/2026-09-27-po-one-question-at-a-time.md.
  • The architect replaces the template's instruction paragraph when it fills in Affected Repos. See .claude/agent-adr/architect/2026-09-30-architect-affected-repos-cleanup.md.
  • New review-contract skill: a read-only contract review in a clean sub-agent (context: fork), checked against api-conventions, the documentation standard and the cross-cutting ADRs. The architect runs it before GATE A and decides per finding whether to act, reject or ask. plan-feature covers the case where the architect cannot start it, with a standing permission to post the report as a plain PR comment. See .claude/skills/review-contract/adr/2026-09-30-review-contract.md.
  • The first two review-contract runs on this PR led to the contract fixes in bddeaa9: 409 RECIPIENT_ALREADY_HAS_ACCESS, complete error documentation, trimming of unit and comment, and an idempotent move.

Next

Merge, then run the repo architects for recipe-backend and recipe-app (/plan-feature, step 3).

🤖 Generated with Claude Code

Epic E01 (shopping lists), its first spec (spec A), and the GATE A output for spec A. The epic, the spec and GATE A are approved. ## Epic and spec - `docs/epics/E01-shopping-lists.md`: the idea, split into six specs (A lists, B filling from a meal plan, C ingredient catalog, D units, E shop layouts, F offline mode), with dependencies, order and the decisions that span them. - `docs/features/001-shopping-lists.md`: spec A. Named lists with a per-user default, sharing at read-write or full access, items with name, amount, unit and comment, ticking into a done section, manual reordering, moving between lists, and "why is this here?". ## GATE A: contract and ADRs **Contract (`api-contract/src/`)** - `shopping-lists.ts` (new): the shopping list domain. List actions live under `/shopping-list/:id` (`GET`, `add-item` with a server-side duplicate check, `add-share`, `reorder`, `remove-ticked`, `set-default`). Item actions use `/shopping-list-item/:itemId/…` (edit, tick, untick, move, delete), and share actions use `/shopping-list-share/:shareId/…`. `GET /shopping-list/all` is the overview. - `shared.ts`: `CompactUserDto`, next to `UserDto`. - `errors.ts` (new): `ErrorCode` and `ErrorResponse { error, code }`. - `users.ts`: `GET /user/list-shareable`, a share picker that every user can call, returning `CompactUserDto`. **Cross-cutting ADRs** - `docs/adr/2026-09-27-shopping-list-contract.md`: default list, item reasons (with a `source`, extensible for spec B), duplicate check, `version` covering content only, reorder, `tickedBy`, owner in the overview. - `docs/adr/2026-09-27-required-error-code.md`: every error carries a required machine-readable `code`. - `docs/adr/2026-09-27-compact-user-dto.md`: other users appear only as `CompactUserDto`. - `docs/adr/2026-09-30-get-reads-and-shallow-nesting.md` (from review): reads without a body use GET; sub-resources use shallow nesting, with at most one id per path. `docs/api-conventions.md` is updated to match. It also writes down the existing naming practice: reduced views are named `Compact<Entity>Dto` and live next to their full DTO. **Spec changes made during the discussion** - No automatic default list. A missing default always shows the overview with a prompt to choose one. - The overview shows a shared list's owner, not whoever shared it. - A ticked item shows who ticked it off and when, in the same hover or tap as "why is this here?". - Affected Repos: `recipe-backend` and `recipe-app` are both affected. **Follow-up issues found along the way** - araemer/recipe-app#46: non-admins cannot share meal plans; meal plan shares expose the full `UserDto`. - #22: comments, ratings and tags expose other users' email and role. - #21: feature request for a nickname and a choice of display name. - #23: legacy routes that break the new GET and shallow-nesting conventions. ## Workflow - The `architect` and `po` agents now end their assessment with a numbered list of open questions, as headlines only, and then discuss them one at a time. For the PO, the list is a living agenda that answers may change visibly. See `.claude/agent-adr/architect/2026-09-27-architect-one-question-at-a-time.md` and `.claude/agent-adr/po/2026-09-27-po-one-question-at-a-time.md`. - The `architect` replaces the template's instruction paragraph when it fills in Affected Repos. See `.claude/agent-adr/architect/2026-09-30-architect-affected-repos-cleanup.md`. - New `review-contract` skill: a read-only contract review in a clean sub-agent (`context: fork`), checked against api-conventions, the documentation standard and the cross-cutting ADRs. The architect runs it before GATE A and decides per finding whether to act, reject or ask. `plan-feature` covers the case where the architect cannot start it, with a standing permission to post the report as a plain PR comment. See `.claude/skills/review-contract/adr/2026-09-30-review-contract.md`. - The first two `review-contract` runs on this PR led to the contract fixes in bddeaa9: 409 `RECIPIENT_ALREADY_HAS_ACCESS`, complete error documentation, trimming of `unit` and `comment`, and an idempotent move. ## Next Merge, then run the repo architects for `recipe-backend` and `recipe-app` (`/plan-feature`, step 3). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
feat(workflow) Add epics above feature specs for ideas too large for one spec
All checks were successful
/ typecheck-contract (pull_request) Successful in 18s
0c4cd77010
The po agent splits a large idea into specs, records the split, order and
cross-spec decisions in docs/epics/E<NN>-<slug>.md, and keeps the epic
current as each spec is written. The architect reads a spec's epic so the
contract does not block a later spec. Every phase after the spec still
works on one spec.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
feat(workflow) Approve an epic at its own gate before any of its specs
All checks were successful
/ typecheck-contract (pull_request) Successful in 19s
c0580cbcc1
The po agent writes the epic once the split is settled and stops at an
epic gate; spec discussions start only after the epic is approved. A
later change to the split or order goes back through the gate, while
new cross-spec decisions are approved with the spec they came up in.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
docs(features) #001 Add shopping lists epic E01 and spec A
All checks were successful
/ typecheck-contract (pull_request) Successful in 18s
fc45d84be8
Epic E01 splits the shopping-list idea into six specs (lists, filling from
a meal plan, ingredient catalog, units, shop layouts, offline mode) and
records the decisions that span them. Spec 001 is spec A: named lists
with a per-user default, sharing, items, ticking, manual order.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
docs(features) #001 Describe epic E01's goal in prose and add epic-level use cases
All checks were successful
/ typecheck-contract (pull_request) Successful in 19s
8d9b8880cd
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
docs(features) #001 Align epic E01 with the epic template
All checks were successful
/ typecheck-contract (pull_request) Successful in 18s
3283c051a2
Rename Idea to Goal, fit the goal into three paragraphs, and name the
delivering specs in each use case heading.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
docs(features) #001 Match names tolerating regular German plural endings
All checks were successful
/ typecheck-contract (pull_request) Successful in 18s
82bb2f64ba
Spec A's duplicate check treats names differing only by a regular plural
ending as the same. Epic E01 lets B use the same match but ask the user
for plural-only matches instead of combining silently.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- api-contract: new shopping-lists domain, POST /user/list-shareable,
  CompactUserDto, ErrorCode and ErrorResponse
- ADRs: shopping list contract design, required machine-readable error
  code, CompactUserDto for other users and Compact naming
- api-conventions: new error envelope rule, Compact naming and
  other-users rules
- spec 001: no automatic default list, overview shows the owner,
  Affected Repos filled in (GATE A)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
feat(workflow) Let the architect discuss open questions one at a time
All checks were successful
/ typecheck-contract (pull_request) Successful in 24s
7fb2ea0b45
The architect now ends its assessment with a numbered headline list of
open questions and then goes through them one by one, instead of
presenting every question with its options in one message.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
araemer changed title from WIP: docs(features) #001 Shopping lists epic E01 and spec A to feat(contract) #001 Shopping lists: epic E01, spec A and contract 2026-09-27 19:09:04 +00:00
feat(workflow) Let the PO discuss open questions one at a time
All checks were successful
/ typecheck-contract (pull_request) Successful in 19s
d41ff46dc1
The po agent now ends its assessment with a numbered headline list of
open questions and asks them one by one, independent ones included,
matching the architect. The list is a living agenda that answers may
change visibly.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ -0,0 +53,4 @@
is short.
- The architect keeps a running numbered list. Decisions are summarised again in full at
the start of Phase 3, as before.
- The `po` agent is unchanged. Whether it should work the same way is a separate decision.
Author
Owner

We already changed it. A reference to the PO ADR would be better than this claim

We already changed it. A reference to the PO ADR would be better than this claim
araemer marked this conversation as resolved
@ -66,0 +76,4 @@
* Read-only: never sent in a request. Does not extend AbstractDto — it is a reference to a
* user, not an editable entity, so `version` does not apply.
*/
export interface CompactUserDto {
Author
Owner

Why is the CompactUserDto located here instead of in its own file?

Why is the CompactUserDto located here instead of in its own file?
araemer marked this conversation as resolved
@ -66,0 +103,4 @@
* never from the `error` text.
* See `docs/adr/2026-09-27-required-error-code.md`.
*/
export const ErrorCode = {
Author
Owner

Same here. Why aren't ErrorCode and ErrorResponse separate files?

Same here. Why aren't ErrorCode and ErrorResponse separate files?
araemer marked this conversation as resolved
@ -0,0 +40,4 @@
* Kind of a reason why an item is on a list — see `ShoppingListItemReasonDto`.
* Designed to grow: spec B of epic E01 adds a meal-plan kind.
*/
export const ShoppingListItemReasonKind = {
Author
Owner

Wouldn't ShoppingListItemSource be better. Id don't like the name

Wouldn't ShoppingListItemSource be better. Id don't like the name
araemer marked this conversation as resolved
@ -0,0 +50,4 @@
/**
* Result of POST /shopping-list/:id/item/add.
*/
export const AddShoppingListItemOutcome = {
Author
Owner

Somehow, I don't like Outcome. wouldn't status be better?

Somehow, I don't like Outcome. wouldn't status be better?
araemer marked this conversation as resolved
@ -0,0 +107,4 @@
/** Whether the item is ticked off. Always present. Read-only — changed via the tick/untick routes. */
ticked: boolean;
/** ISO-8601 timestamp of the last tick. Present only when `ticked` is true. Read-only. */
tickedAt?: string;
Author
Owner

Should we add tickedBy?

Should we add tickedBy?
araemer marked this conversation as resolved
@ -0,0 +227,4 @@
version: number;
}
/** Request body for POST /shopping-list/:id/item/:itemId/move. */
Author
Owner

I don't like this type of path with two ids in it. Wouldn't
/shopping-list/:id/move-item with both itemId and targetListId in the body be better?

I don't like this type of path with two ids in it. Wouldn't /shopping-list/:id/move-item with both itemId and targetListId in the body be better?
araemer marked this conversation as resolved
@ -0,0 +283,4 @@
},
/**
* POST /shopping-list/:id/load
Author
Owner

If this has no body, why do we use POST?

If this has no body, why do we use POST?
araemer marked this conversation as resolved
@ -0,0 +381,4 @@
},
/**
* POST /shopping-list/:id/item/add
Author
Owner

Open discussion? item/add or add-item. Usually, I prefer the latter as we are talking about modifying the shopping-list so why go two levels down? Is there a need for grouping items?

Open discussion? item/add or add-item. Usually, I prefer the latter as we are talking about modifying the shopping-list so why go two levels down? Is there a need for grouping items?
araemer marked this conversation as resolved
@ -0,0 +410,4 @@
},
/**
* POST /shopping-list/:id/item/:itemId/edit
Author
Owner

I don't like these calls with two ids. let's discuss.

I don't like these calls with two ids. let's discuss.
araemer marked this conversation as resolved
@ -0,0 +434,4 @@
},
/**
* POST /shopping-list/:id/item/:itemId/tick
Author
Owner

see above

see above
araemer marked this conversation as resolved
feat(contract) #001 Apply review: shallow nesting, GET reads, tickedBy
Some checks failed
/ typecheck-contract (pull_request) Has been cancelled
514bcc76f5
- New convention and ADR: reads without a body use GET; sub-resources
  use shallow nesting with at most one id per path
- shopping-lists: item and share actions under /shopping-list-item and
  /shopping-list-share, add-item/add-share on the list, GET for all and
  by id, AddShoppingListItemStatus, ShoppingListItemSource, tickedBy
- users: GET /user/list-shareable
- errors.ts: ErrorCode and ErrorResponse split out of shared.ts; item
  route codes dropped, the app reloads the list instead
- spec 001: show who ticked an item off and when
- ADRs for spec 001 and the architect ADR corrected before merge

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ -0,0 +16,4 @@
`TagDto.createdByUser`. `UserDto` carries `email` and `role`, so every reader of a comment and
every recipient of a shared meal plan receives other users' email addresses.
The name for the reduced type came up too. A first proposal was `UserSummaryDto`. The contract
Author
Owner

skip this paragraph. We are following a project rule here. No need to mention it.

skip this paragraph. We are following a project rule here. No need to mention it.
araemer marked this conversation as resolved
@ -0,0 +22,4 @@
## Decision
A reduced view of an entity is named `Compact<Entity>Dto`. Any response that shows a user
Author
Owner

The first sentence should not be part of this ADR. It's on naming of compact representations not on users.

The first sentence should not be part of this ADR. It's on naming of compact representations not on users.
araemer marked this conversation as resolved
@ -0,0 +191,4 @@
## Affected Repos
Filled in by the cross-cutting architect and approved at GATE A — leave as is when writing
Author
Owner

Can't we remove this part once we filled it in?

Can't we remove this part once we filled it in?
araemer marked this conversation as resolved
docs(adr) #001 Apply review: trim CompactUserDto ADR, clean Affected Repos
Some checks failed
/ typecheck-contract (pull_request) Has been cancelled
c29ee780a1
- CompactUserDto ADR covers only how other users are exposed; the
  Compact naming rule stays in api-conventions without an ADR link
- spec 001: drop the template's instruction paragraph in Affected Repos
- architect agent replaces that paragraph when filling the section,
  with an agent ADR

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Author
Owner

Replies to the review comments (reviews 1 and 2), in order. Forgejo's API cannot reply inside a comment thread, so everything is collected here.

Review 1

1. architect-one-question-at-a-time.md: "A reference to the PO ADR would be better than this claim"
Fixed in 514bcc7. The last consequence now points to .claude/agent-adr/po/2026-09-27-po-one-question-at-a-time.md instead of claiming the PO is unchanged.

2. shared.ts: "Why is the CompactUserDto located here instead of in its own file?"
Kept in shared.ts on purpose: a compact DTO lives in the same file as its full DTO, as CompactMealPlanDto sits next to MealPlanDto. UserDto is in shared.ts because auth.ts and meal-plans.ts import it. The placement rule is now written down in docs/api-conventions.md ("Reduced views are named Compact<Entity>Dto").

3. shared.ts: "Why aren't ErrorCode and ErrorResponse separate files?"
Agreed. Moved to the new api-contract/src/errors.ts in 514bcc7.

4. shopping-lists.ts: "item/add or add-item?"
Now POST /shopping-list/:id/add-item (514bcc7). It is an action on the list, in the same form as reorder and remove-ticked, and the /item/ grouping only served the two-id paths, which are gone.

5. shopping-lists.ts: "I don't like these calls with two ids. let's discuss."
Discussed and decided. There is a new convention with an ADR: shallow nesting, at most one id per path (docs/adr/2026-09-30-get-reads-and-shallow-nesting.md). Actions on one item use its own id: POST /shopping-list-item/:itemId/edit. Your variant with the list id in the path and the item id in the body was considered and is recorded in the ADR as not adopted. The app tells "list gone" from "item gone" by reloading the list after an item error.

6. shopping-lists.ts, tick: "see above"
Same fix: POST /shopping-list-item/:itemId/tick and /untick.

7. shopping-lists.ts: "Outcome — wouldn't status be better?"
Agreed. Now AddShoppingListItemStatus in a status field, consistent with RecipeImportStatus (514bcc7).

8. shopping-lists.ts: "ShoppingListItemSource would be better"
Agreed. Now enum ShoppingListItemSource in the field source (514bcc7). The DTO stays ShoppingListItemReasonDto, because the whole entry answers "why is this here?". Spec B's nested block is renamed to mealPlan in the ADR, to avoid "source" twice.

9. shopping-lists.ts: "Should we add tickedBy?"
Added (514bcc7): tickedBy: CompactUserDto, present only while the item is ticked. The first tick counts, untick clears it, and a move keeps it. Spec 001 now shows "ticked off by on " in the same hover or tap as "why is this here?".

10. shopping-lists.ts, move: "/shopping-list/:id/move-item with itemId and targetListId in the body?"
Resolved by the shallow-nesting decision (see 5): POST /shopping-list-item/:itemId/move { targetListId }. The ADR records why the item id stays in the path: it is the addressed resource, and every item action would otherwise need its own …-item verb on the list.

11. shopping-lists.ts: "If this has no body, why do we use POST?"
It shouldn't. New convention (same ADR): reads without a request body use GET. Now GET /shopping-list/:id, GET /shopping-list/all and GET /user/list-shareable. POST stays for filter queries with a typed body. Older bodyless POST reads are tracked in #23.

Review 2

12. compact-user-dto.md: "The first sentence should not be part of this ADR"
Removed in c29ee78, together with the naming alternative and the title part. The ADR now covers only how other users are exposed. The Compact…Dto naming rule stays in docs/api-conventions.md without an ADR, since it records existing practice.

13. compact-user-dto.md: "skip this paragraph"
Removed in c29ee78.

14. 001-shopping-lists.md: "Can't we remove this part once we filled it in?"
Removed in c29ee78. The architect agent now replaces that instruction paragraph whenever it fills in Affected Repos (.claude/agent-adr/architect/2026-09-30-architect-affected-repos-cleanup.md).

🤖 Generated with Claude Code

Replies to the review comments (reviews 1 and 2), in order. Forgejo's API cannot reply inside a comment thread, so everything is collected here. ### Review 1 **1. `architect-one-question-at-a-time.md`: "A reference to the PO ADR would be better than this claim"** Fixed in 514bcc7. The last consequence now points to `.claude/agent-adr/po/2026-09-27-po-one-question-at-a-time.md` instead of claiming the PO is unchanged. **2. `shared.ts`: "Why is the CompactUserDto located here instead of in its own file?"** Kept in `shared.ts` on purpose: a compact DTO lives in the same file as its full DTO, as `CompactMealPlanDto` sits next to `MealPlanDto`. `UserDto` is in `shared.ts` because `auth.ts` and `meal-plans.ts` import it. The placement rule is now written down in `docs/api-conventions.md` ("Reduced views are named `Compact<Entity>Dto`"). **3. `shared.ts`: "Why aren't ErrorCode and ErrorResponse separate files?"** Agreed. Moved to the new `api-contract/src/errors.ts` in 514bcc7. **4. `shopping-lists.ts`: "item/add or add-item?"** Now `POST /shopping-list/:id/add-item` (514bcc7). It is an action on the list, in the same form as `reorder` and `remove-ticked`, and the `/item/` grouping only served the two-id paths, which are gone. **5. `shopping-lists.ts`: "I don't like these calls with two ids. let's discuss."** Discussed and decided. There is a new convention with an ADR: shallow nesting, at most one id per path (`docs/adr/2026-09-30-get-reads-and-shallow-nesting.md`). Actions on one item use its own id: `POST /shopping-list-item/:itemId/edit`. Your variant with the list id in the path and the item id in the body was considered and is recorded in the ADR as not adopted. The app tells "list gone" from "item gone" by reloading the list after an item error. **6. `shopping-lists.ts`, tick: "see above"** Same fix: `POST /shopping-list-item/:itemId/tick` and `/untick`. **7. `shopping-lists.ts`: "Outcome — wouldn't status be better?"** Agreed. Now `AddShoppingListItemStatus` in a `status` field, consistent with `RecipeImportStatus` (514bcc7). **8. `shopping-lists.ts`: "ShoppingListItemSource would be better"** Agreed. Now enum `ShoppingListItemSource` in the field `source` (514bcc7). The DTO stays `ShoppingListItemReasonDto`, because the whole entry answers "why is this here?". Spec B's nested block is renamed to `mealPlan` in the ADR, to avoid "source" twice. **9. `shopping-lists.ts`: "Should we add tickedBy?"** Added (514bcc7): `tickedBy: CompactUserDto`, present only while the item is ticked. The first tick counts, untick clears it, and a move keeps it. Spec 001 now shows "ticked off by <user> on <date>" in the same hover or tap as "why is this here?". **10. `shopping-lists.ts`, move: "/shopping-list/:id/move-item with itemId and targetListId in the body?"** Resolved by the shallow-nesting decision (see 5): `POST /shopping-list-item/:itemId/move { targetListId }`. The ADR records why the item id stays in the path: it is the addressed resource, and every item action would otherwise need its own `…-item` verb on the list. **11. `shopping-lists.ts`: "If this has no body, why do we use POST?"** It shouldn't. New convention (same ADR): reads without a request body use GET. Now `GET /shopping-list/:id`, `GET /shopping-list/all` and `GET /user/list-shareable`. POST stays for filter queries with a typed body. Older bodyless POST reads are tracked in #23. ### Review 2 **12. `compact-user-dto.md`: "The first sentence should not be part of this ADR"** Removed in c29ee78, together with the naming alternative and the title part. The ADR now covers only how other users are exposed. The `Compact…Dto` naming rule stays in `docs/api-conventions.md` without an ADR, since it records existing practice. **13. `compact-user-dto.md`: "skip this paragraph"** Removed in c29ee78. **14. `001-shopping-lists.md`: "Can't we remove this part once we filled it in?"** Removed in c29ee78. The architect agent now replaces that instruction paragraph whenever it fills in Affected Repos (`.claude/agent-adr/architect/2026-09-30-architect-affected-repos-cleanup.md`). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
- review-contract skill: read-only, runs with context: fork in a fresh
  general-purpose sub-agent, checks api-contract/src/ against
  api-conventions, the documentation standard and the cross-cutting ADRs
- architect runs it before GATE A and decides per finding: act, reject
  or ask; reports "clean contract review not run" if it cannot spawn it
- plan-feature fallback: surface it or run the review itself, with a
  standing permission to post the report as a plain PR comment
- skill, agent and plan-feature ADRs

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
fix(contract) #001 Apply review-contract findings
All checks were successful
/ typecheck-contract (pull_request) Successful in 21s
bddeaa9596
- 409 RECIPIENT_ALREADY_HAS_ACCESS for sharing with someone who
  already has access; CONFLICT keeps its locking-only meaning
- complete 400 documentation, required id/version on response-only
  DTOs, always-present and required wording in JSDoc
- trim unit and comment, empty counts as absent
- moving an item to its current list is an idempotent no-op
- error statuses of existing routes are documented when the route
  itself changes, not when its domain gains a route

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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-platform!20
No description provided.