Onboard recipe-app to @recipe/contract #35

Merged
araemer merged 28 commits from onboard-recipe-platform-contract into stage 2026-09-26 05:18:47 +00:00
Owner

Implements docs/plans/onboarding-integration.md (all steps 1-9).

Summary

  1. CI + dependency: added the @recipe/contract file: dependency and a recipe-platform sibling clone step to all three Forgejo workflows, per recipe-platform/docs/adr/2026-09-19-contract-ci-strategy.md.
  2. Full domain audit: docs/audit-domains.md diffs every contract domain against this repo's DTOs and the live backend. Confirms this repo's AbstractModelDtoMapper does not have the Date/string base-mapper blocker that limited recipe-backend to migrating only health -- dates are already ISO strings throughout this repo's DTO layer.
  3. Tags fix (coordinated): TagRestResource.ts now consumes the { tags: [...] } wrapper from GET /tag/all / GET /tag/enriched instead of a bare array. The backend's matching change has already landed on recipe-backend's onboard-recipe-platform-contract branch -- these two PRs should merge close together.
  4. Other drift fixed during migration: LoginResponse.expiryDate was typed Date but never actually held one (fixed to string, matching the wire format); HealthResponse.commit/.date were typed non-nullable string but the backend actually sends null in local dev (fixed to string | null).
  5. All 13 contract domains migrated to @recipe/contract imports: shared, health, auth, tags, comments, ratings, recipes, compact-recipes, users, meal-plans, ai, passkeys (partial, see below), plus images (not applicable -- no local DTO exists for that domain in this repo). Contract renames applied: MealPlanSummaryDto→CompactMealPlanDto, EffectiveMealPlanPermission→MealPlanEffectivePermission, RecipeFromImagesResponseDto→RecipeImportResponse, PasskeyDeviceType's SingleDevice/MultiDevice→SINGLE_DEVICE/MULTI_DEVICE.
  6. Passkeys exception: the four WebAuthn ceremony DTOs stay local by design -- the contract types their options/response fields as unknown (documented in its own JSDoc, to avoid a @simplewebauthn dependency in the shared package), while this repo needs the concrete @simplewebauthn/browser types to pass directly into startAuthentication()/startRegistration().
  7. Class→interface cleanup (step 6): all local class-based DTOs were superseded by contract imports except FrontendBuildInfo (frontend-only, no contract equivalent), which is now an interface.
  8. check-platform skill: mirrors recipe-platform's check-siblings in reverse -- on-demand check for ../recipe-platform and api-contract inside it, with consent before cloning.
  9. .claude/settings.json: committed baseline permissions (previously only a gitignored local file existed).
  10. CLAUDE.md: new "Multi-Repo Context" section naming the sibling repos, the contract dependency and migration status, the check-platform skill, and pointing at recipe-platform/docs/api-conventions.md for wire-format rules.

Out of scope

Per the plan: repo-specific agent definitions (architect.md/planner.md/implementer.md/reviewer.md in .claude/agents/) are a separate follow-up initiative, not part of this PR.

Coordination note

The tags wire-format fix in this PR only makes sense paired with recipe-backend's matching change (already on that repo's onboard-recipe-platform-contract branch). Please sequence the merges close together.

Test plan

  • npm run build -- passes
  • npm run lint -- passes (2 pre-existing, unrelated warnings)
  • npm run test -- 187/187 tests pass
Implements `docs/plans/onboarding-integration.md` (all steps 1-9). ## Summary 1. **CI + dependency**: added the `@recipe/contract` `file:` dependency and a `recipe-platform` sibling clone step to all three Forgejo workflows, per `recipe-platform/docs/adr/2026-09-19-contract-ci-strategy.md`. 2. **Full domain audit**: `docs/audit-domains.md` diffs every contract domain against this repo's DTOs and the live backend. Confirms this repo's `AbstractModelDtoMapper` does **not** have the Date/string base-mapper blocker that limited `recipe-backend` to migrating only `health` -- dates are already ISO strings throughout this repo's DTO layer. 3. **Tags fix (coordinated)**: `TagRestResource.ts` now consumes the `{ tags: [...] }` wrapper from `GET /tag/all` / `GET /tag/enriched` instead of a bare array. The backend's matching change has already landed on `recipe-backend`'s `onboard-recipe-platform-contract` branch -- these two PRs should merge close together. 4. **Other drift fixed during migration**: `LoginResponse.expiryDate` was typed `Date` but never actually held one (fixed to `string`, matching the wire format); `HealthResponse.commit`/`.date` were typed non-nullable `string` but the backend actually sends `null` in local dev (fixed to `string | null`). 5. **All 13 contract domains migrated** to `@recipe/contract` imports: `shared`, `health`, `auth`, `tags`, `comments`, `ratings`, `recipes`, `compact-recipes`, `users`, `meal-plans`, `ai`, `passkeys` (partial, see below), plus `images` (not applicable -- no local DTO exists for that domain in this repo). Contract renames applied: `MealPlanSummaryDto`→`CompactMealPlanDto`, `EffectiveMealPlanPermission`→`MealPlanEffectivePermission`, `RecipeFromImagesResponseDto`→`RecipeImportResponse`, `PasskeyDeviceType`'s `SingleDevice`/`MultiDevice`→`SINGLE_DEVICE`/`MULTI_DEVICE`. 6. **Passkeys exception**: the four WebAuthn ceremony DTOs stay local by design -- the contract types their `options`/`response` fields as `unknown` (documented in its own JSDoc, to avoid a `@simplewebauthn` dependency in the shared package), while this repo needs the concrete `@simplewebauthn/browser` types to pass directly into `startAuthentication()`/`startRegistration()`. 7. **Class→interface cleanup (step 6)**: all local class-based DTOs were superseded by contract imports except `FrontendBuildInfo` (frontend-only, no contract equivalent), which is now an interface. 8. **`check-platform` skill**: mirrors `recipe-platform`'s `check-siblings` in reverse -- on-demand check for `../recipe-platform` and `api-contract` inside it, with consent before cloning. 9. **`.claude/settings.json`**: committed baseline permissions (previously only a gitignored local file existed). 10. **`CLAUDE.md`**: new "Multi-Repo Context" section naming the sibling repos, the contract dependency and migration status, the `check-platform` skill, and pointing at `recipe-platform/docs/api-conventions.md` for wire-format rules. ## Out of scope Per the plan: repo-specific agent definitions (`architect.md`/`planner.md`/`implementer.md`/`reviewer.md` in `.claude/agents/`) are a separate follow-up initiative, not part of this PR. ## Coordination note The `tags` wire-format fix in this PR only makes sense paired with `recipe-backend`'s matching change (already on that repo's `onboard-recipe-platform-contract` branch). Please sequence the merges close together. ## Test plan - [x] `npm run build` -- passes - [x] `npm run lint` -- passes (2 pre-existing, unrelated warnings) - [x] `npm run test` -- 187/187 tests pass
Wires this repo onto the api-contract package via file: dependency and
adds a recipe-platform sibling clone step to all three Forgejo
workflows so npm ci can resolve it, per
recipe-platform/docs/adr/2026-09-19-contract-ci-strategy.md.
Diffs every @recipe/contract domain against this repo's current DTOs
and the live recipe-backend branch. Confirms this repo's
AbstractModelDtoMapper does not have the Date/string base-mapper
blocker that limited the backend to migrating only health — dates are
already strings throughout this repo's DTO layer, with Date objects
confined to the model layer. Written per the plan's step 2 before any
DTO deletion or contract-import migration (step 5).
Mirrors recipe-platform's check-siblings skill in reverse: on-demand
check for ../recipe-platform and, specifically, api-contract inside
it (the actual npm install target), with explicit consent before
cloning. Per onboarding-integration.md step 7.
Mirrors recipe-platform's committed permissions baseline (git, npm,
curl to git.raemer.net) plus frontend-specific tooling (vite, vitest,
lint/build/test). Closes the gap between ONBOARDING.md's stated setup
and this repo's actual state (previously only a gitignored
settings.local.json). Per onboarding-integration.md step 8.
Adds a near-top Multi-Repo Context section: names the three sibling
repos, the @recipe/contract dependency and its migration status
(docs/audit-domains.md), the check-platform skill, the cross-repo
workflow living in recipe-platform, and points at
recipe-platform/docs/api-conventions.md for wire-format rules instead
of restating them here. Per onboarding-integration.md step 9.
Deletes the local AbstractDto and UserDto classes/interfaces and the
local UserRole const, importing all three from the contract instead.
AbstractModelDtoMapper's generic constraint now binds to the
contract's AbstractDto directly -- its dates are already ISO strings
on both sides (Date objects only ever exist at the Model layer here),
so this repo does not have the Date/string base-mapper conflict that
limited recipe-backend to migrating only health.

UserRoleHelper (display names, dropdown options) has no contract
equivalent and stays local, now importing UserRole from the contract
instead of defining its own copy. Fixes a stale '@todo add in Backend'
comment on createdBy -- confirmed against recipe-backend that it is
in fact populated on insert.
Deletes the local HealthResponse class and imports the type from the
contract instead. Fixes a frontend-only type inaccuracy along the way:
this repo typed commit/date as non-nullable string with 'unknown'/''
fallback comments, but the live backend (recipe-backend, already
migrated to the same contract type) sends null for both in local
development. SettingsMenu's formatBuildDate/displayVersion now accept
string | null to match; both already handled falsy input correctly at
runtime, so this is a type-accuracy fix, not a behavior change.
Deletes the local LoginRequest/LoginResponse classes, importing both
from the contract. session.ts's expiryDate fix (Date -> string, see
the previous shared-domain commit) already landed alongside the
AbstractDto/UserDto migration since both touched session.ts; this
commit is the remaining LoginRequest/LoginResponse import swap across
AuthRestResource, PasskeyRestResource, and LoginPage.
Deletes the local TagDto, EnrichedTagDto, and DeleteTagRequest types.
TagRestResource now consumes TagListResponse/EnrichedTagListResponse
(the { tags: [...] } wrapper) from GET /tag/all and GET /tag/enriched
instead of a bare array, matching the list-wrapper convention in
docs/api-conventions.md. The backend's matching change already landed
on recipe-backend's onboard-recipe-platform-contract branch -- these
two PRs need to merge close together.
Deletes both local classes/interfaces, importing from the contract
instead.
Deletes RecipeDto, RecipeImageDto, RecipeIngredientDto,
RecipeIngredientGroupDto, RecipeInstructionStepDto, and
RecipeRatingRequest, importing all from the contract. Renames
RecipeFromImagesResponseDto to RecipeImportResponse (the contract's
name for the same shape) at its one call site in RecipeRestResource
and updates AiImportDialog accordingly.
Deletes CompactRecipeDto, CompactRecipeFilterRequest,
CompactRecipeFilterResponse, and the RecipeSortField/SortOrder/
RecipeOwnerFilter enum files (their contract equivalents live inside
compact-recipes.ts rather than separate files -- packaging only, same
values).
Deletes all four local types -- field-for-field matches, including
UserListResponse.valueList (same field name on both sides), so no
rename was needed.
Deletes AiImportLogEntryDto, AiImportStatsResponse, AiImportSummaryDto,
and the RecipeImportStatus/RecipeImportReason enum files. The local
AiImportStatsRequest interface in AiRestResource.ts is replaced by the
contract's version of the same shape.
Deletes MealPlanDayDishDto, MealPlanDayDto, MealPlanDto,
MealPlanFilterRequest, MealPlanListResponse, MealPlanShareDto,
MealPlanSummaryDto, and the local MealPlanPermission enum file.
Renames MealPlanSummaryDto -> CompactMealPlanDto and
EffectiveMealPlanPermission -> MealPlanEffectivePermission to match
the contract's naming for the same shape/values -- both the backend
(recipe-backend/src/api/dtos/CompactMealPlanDto.ts,
src/api/enums/MealPlanEffectivePermission.ts) and the contract already
use these names, so this repo's local names were the outlier.
Deletes the four migratable local types and the PasskeyDeviceType enum
file (its key names SingleDevice/MultiDevice become
SINGLE_DEVICE/MULTI_DEVICE per the contract -- same wire values).

The four WebAuthn ceremony DTOs (PasskeyAuthBeginResponse,
PasskeyAuthCompleteRequest, PasskeyRegisterBeginResponse,
PasskeyRegisterCompleteRequest) stay local by design: the contract
deliberately types their options/response fields as unknown to avoid
a @simplewebauthn dependency in the shared package (documented in its
own JSDoc), while PasskeyFlows.ts passes them directly into
startAuthentication()/startRegistration(), which need the concrete
@simplewebauthn/browser types.
refactor(dtos) Standardize FrontendBuildInfo to an interface
Some checks failed
/ test (pull_request) Failing after 3s
f8e9f92563
Step 6 of the onboarding plan: converts the last remaining
never-instantiated local class-based DTO to an interface (matching
every migrated contract type). FrontendBuildInfo has no contract
equivalent -- it describes a frontend-only static asset
(/build-info.json) -- so it stays local, just no longer a class.
Every other class-based DTO in this repo was superseded outright by a
@recipe/contract import in the preceding commits.
docs(plans) Track the approved onboarding-integration plan
Some checks failed
/ test (pull_request) Failing after 2s
6d533d52b8
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
docs(plans) Merge audit results into the onboarding plan
Some checks failed
/ test (pull_request) Failing after 2s
710998638f
docs(plans) Update status line to reflect implementation is done
Some checks failed
/ test (pull_request) Failing after 2s
f1f965a363
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
fix(ci) Clone recipe-platform over HTTPS, not SSH
Some checks failed
/ test (pull_request) Failing after 5s
33b9b249af
The self-hosted runners have no SSH key configured, so the pull_platform
step hung every pipeline. Use the same authenticated HTTPS mechanism the
existing pull step already uses to check out this repo.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
fix(ci) Install api-contract's own deps in CI; declare them as direct deps here
All checks were successful
/ test (pull_request) Successful in 44s
7d10c14944
npm ci failed with EUSAGE: @ts-rest/core and zod are dependencies of
@recipe/contract but were never declared here, so this repo's own
package-lock.json had no resolved entries for them.

That alone isn't enough — npm creates a symlink for file: dependencies, so
tsc resolves api-contract's own "@ts-rest/core" import from api-contract's
real location, never through this repo's node_modules. CI never ran
"npm install" inside api-contract (ONBOARDING.md's step 3 is a manual-only
instruction), so that import failed regardless of this repo's lockfile.

Fixes both: adds @ts-rest/core/zod as direct dependencies here, and adds
an install_platform_contract CI step that runs npm install inside
recipe-platform/api-contract after cloning it, in all three workflows.

See recipe-platform docs/adr/2026-09-19-contract-ci-install-step.md.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
CLAUDE.md Outdated
@ -5,0 +18,4 @@
breaks both sibling repos at compile time. Run `/check-platform` if `npm install`/`npm ci`
fails to resolve it (checks whether `recipe-platform` and `api-contract` are present at the
expected path, offering to clone with consent). Migration status: no domain has been
migrated to `@recipe/contract` types yet — see `docs/audit-domains.md` for the full
Author
Owner

We removed the file from that location. And is it really necessary for claude to read that one later after the migration is complete? Seems like a good way to fill context for nothing...

We removed the file from that location. And is it really necessary for claude to read that one later after the migration is complete? Seems like a good way to fill context for nothing...
araemer marked this conversation as resolved
@ -5,0 +22,4 @@
field-by-field audit against every contract domain, including which domains are ready to
migrate and which need a coordinated backend change first.
**Cross-repo feature workflow** (spec → contract → architecture → plans → implementation →
Author
Owner

Does the frontend require knowledge on the platform skills? I don't think so. The agent simply needs to know where the plans live. If I want to start an agent in frontend alone and do local planning here, that's my decision. And if we plan from the platform repo, a subagent in this repo will be spawned reading this md. I don't think this paragraph is wise.

Does the frontend require knowledge on the platform skills? I don't think so. The agent simply needs to know where the plans live. If I want to start an agent in frontend alone and do local planning here, that's my decision. And if we plan from the platform repo, a subagent in this repo will be spawned reading this md. I don't think this paragraph is wise.
araemer marked this conversation as resolved
@ -11,2 +9,4 @@
* Fetches all tags as plain models.
* Used where only the tag ID and description are needed (e.g. recipe editor).
*
* Consumes the `{ tags: TagDto[] }` wrapper per the list-wrapper convention
Author
Owner

No history in comments! Describe the now and give a reason where necessary. No need here...

No history in comments! Describe the now and give a reason where necessary. No need here...
araemer marked this conversation as resolved
@ -16,3 +16,2 @@
...this.mapDtoToBaseModel(dto),
body: dto.body,
recipeId: dto.recipeId,
// The contract types body/recipeId as optional to cover write payloads too,
Author
Owner

I'm unsure what you mean by that comment. Elaborate.

I'm unsure what you mean by that comment. Elaborate.
araemer marked this conversation as resolved
@ -20,3 +20,3 @@
return {
...this.mapDtoToBaseModel(dto),
title: dto.title,
// title is optional on the contract type but always populated on reads.
Author
Owner

Why should it be optional? Title is a mandatory field for a recipe. We should check this!

Why should it be optional? Title is a mandatory field for a recipe. We should check this!
araemer marked this conversation as resolved
@ -15,2 +15,2 @@
ingredientList: dto.ingredients
.sort((a, b) => a.sortOrder - b.sortOrder)
// ingredients is optional on the contract type but always populated on reads.
ingredientList: (dto.ingredients ?? [])
Author
Owner

same here. Ingredients are mandatory...

same here. Ingredients are mandatory...
araemer marked this conversation as resolved
@ -8,2 +8,3 @@
...this.mapDtoToBaseModel(dto),
text: dto.text,
// text is optional on the contract type but always populated on reads.
text: dto.text!,
Author
Owner

This should be mandatory as well. We should check the contract...

This should be mandatory as well. We should check the contract...
araemer marked this conversation as resolved
@ -25,3 +27,3 @@
recipeTitle: dto.recipeTitle,
recipeAmountDescription: dto.recipeAmountDescription,
servings: dto.servings,
servings: dto.servings!,
Author
Owner

we should check the contract here. I'm pretty sure that servings is mandatory.

we should check the contract here. I'm pretty sure that servings is mandatory.
araemer marked this conversation as resolved
@ -32,3 +32,3 @@
// Base fields: id, createdAt, updatedAt, createdBy, version
...this.mapDtoToBaseModel(dto),
title: dto.title,
// title is optional on the contract type but always populated by the backend.
Author
Owner

We need to check the contract. THis should be mandatory.

We need to check the contract. THis should be mandatory.
araemer marked this conversation as resolved
@ -26,1 +26,3 @@
sharedWithUserId: dto.sharedWithUserId,
// sharedWithUserId/permission are optional on the contract type but always
// populated by the backend on reads.
sharedWithUserId: dto.sharedWithUserId!,
Author
Owner

Check...

Check...
araemer marked this conversation as resolved
@ -17,3 +17,2 @@
...this.mapDtoToBaseModel(dto),
rating: dto.rating,
recipeId: dto.recipeId,
// rating/recipeId are optional on the contract type (shared with write payloads)
Author
Owner

Check contract. They shouldn't be optional

Check contract. They shouldn't be optional
araemer marked this conversation as resolved
@ -20,3 +20,1 @@
url: `${API_BASE_URL}${dto.url}`,
mimeType: dto.mimeType,
recipeId: dto.recipeId,
// url/mimeType/recipeId are optional on the contract type but always
Author
Owner

check contract.

check contract.
araemer marked this conversation as resolved
@ -24,3 +24,2 @@
},
instructionStepList: dto.instructions
.sort((a, b) => a.sortOrder - b.sortOrder)
// instructions/ingredientGroups are optional on the contract type (it also
Author
Owner

check contract.

check contract.
araemer marked this conversation as resolved
@ -22,2 +21,3 @@
...this.mapDtoToBaseModel(dto),
description: dto.description,
// description is optional on the contract type but always populated on reads.
description: dto.description!,
Author
Owner

check contract.

check contract.
araemer marked this conversation as resolved
@ -22,3 +22,2 @@
...this.mapDtoToBaseModel(dto),
userName: dto.userName,
email: dto.email,
// userName/email are optional on the contract type but always populated on reads.
Author
Owner

check contract.

check contract.
araemer marked this conversation as resolved
@ -1,5 +1,4 @@
import type {RecipeImportStatus} from "../api/enums/RecipeImportStatus.ts";
import type {RecipeImportReason} from "../api/enums/RecipeImportReason.ts";
import type {RecipeImportStatus, RecipeImportReason} from "@recipe/contract";
Author
Owner

Hm, using API enums in the model carries APIdependencies to the model. Question is: is it worth duplicating all the enums and mapping to avoid just that.

Hm, using API enums in the model carries APIdependencies to the model. Question is: is it worth duplicating all the enums and mapping to avoid just that.
araemer marked this conversation as resolved
The DTO-boundary rule (no DTOs past the resource layer) doesn't apply to
the contract's 9 closed-set string enums — a wire enum value and a model
enum value are the same string, not two representations needing
translation the way a DTO shape's optionality or wrapper does.
AiImportStatsModel.ts's existing direct import is now the sanctioned
pattern, generalized to all 9 enums instead of left as an unexplained
one-off. See docs/adr/2026-09-20-contract-enums-in-models.md for the
full reasoning and the line back toward duplication.

Addresses PR #35 review comment on enum layering.
docs/audit-domains.md no longer exists — its content was merged into
docs/plans/onboarding-integration.md's "Audit Results" section. Also
correct the claim that no domain has migrated yet: 13 domains are
migrated as of this PR.

Addresses PR #35 review comment on the dead link and false status.
This repo's session does implementation only and never runs
/plan-feature, /implement-feature, or /review-feature — describing the
platform's orchestration workflow here duplicates recipe-platform's own
docs for no benefit to this repo. Mirrors the equivalent removal on
recipe-backend PR #13 (commit 44088fa), which now describes only the
multi-repo table, contract status, and /check-platform.

Addresses PR #35 review comment questioning why this repo's CLAUDE.md
needs to describe the platform's agents at all.
docs(tags) Trim TagRestResource JSDoc to current state, not history
All checks were successful
/ test (pull_request) Successful in 45s
353037beff
fetchAllTags/fetchEnrichedTags docstrings narrated cross-repo migration
history (branch names, step numbers in another repo's plan doc) instead
of describing current behavior. That history belongs in
docs/plans/onboarding-integration.md, not inline code comments.

Addresses PR #35 review comment on process narrative in JSDoc.
Drops the `dto.field!` assertions and "optional but always populated"
comments in 10 mappers, for fields that recipe-platform PR #11
(fix-contract-mandatory-fields) retypes as mandatory on the contract:
CompactRecipeMapper.title, RatingMapper.rating/recipeId,
MealPlanMapper.title, MealPlanShareMapper.sharedWithUserId/permission,
UserMapper.userName/email, RecipeImageMapper.url/mimeType/recipeId,
InstructionStepMapper.text, MealPlanDayDishMapper.recipeId/servings/sortOrder,
TagMapper.description (plain TagDto only), and CommentMapper.body/recipeId.

TagMapper's second `description` usage (EnrichedTagDto, in
mapEnrichedDtoToModel) keeps its assertion -- that DTO's description
field is still optional on the contract and was not part of PR #11's
fix, confirmed by a genuine tsc error when the assertion was removed.

recipe-platform PR #11 is not yet merged to main, so this repo's CI
will fail against main until it lands -- same coordination pattern as
the earlier tags-wrapper fix between these two repos.
refactor(mappers,tags) Address review feedback: trim JSDoc, drop stray ! assertions
All checks were successful
/ test (pull_request) Successful in 46s
82aeb0ba33
- Remove implementation-detail wrapper lines from TagRestResource JSDoc
- Drop unnecessary ! on RecipeImageDto.url (required per contract) in CompactRecipeMapper
- Drop unnecessary ! and misleading comment on EnrichedTagDto.description (required per contract) in TagMapper

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Author
Owner

Pushed 82aeb0b addressing the open review items:

Comment 363 (TagRestResource JSDoc) — Fixed. Removed the "Consumes the wrapper" lines from both fetchAllTags and fetchEnrichedTags. The unwrapping is visible in the code itself.

Comment 366 (IngredientGroupMapper — ingredients mandatory?) — No code change. Checked the contract: RecipeIngredientGroupDto.ingredients is typed ingredients?: RecipeIngredientDto[] — explicitly optional. The ?? [] fallback and the comment are correct. If the backend always populates it on reads and the contract should enforce that, the fix belongs in recipe-platform/api-contract, not here.

Comment 373 (RecipeMapper — check contract) — The ?? [] fallbacks for instructions and ingredientGroups are correct: RecipeDto marks both as optional (the DTO is shared between write payloads and read responses). The title! assertion on line 20 remains — RecipeDto.title is title?: string in the contract, so the ! is required by TypeScript. The business logic says title is NOT NULL, so the contract should mark it title: string — that is a recipe-platform fix, not frontend.

Comment 376 (contract enums in models) — Addressed by 22ec77b which documents the decision with an ADR. The enums stay; the rationale is recorded.

Bonus fixes also in 82aeb0b: Two stray ! assertions that 6b55bf5 missed — randomImage.url! in CompactRecipeMapper (url is required per contract) and dto.description! in TagMapper.mapEnrichedDtoToModel (EnrichedTagDto.description is also required per contract), plus a misleading comment claiming it was optional.

Pushed `82aeb0b` addressing the open review items: **Comment 363 (TagRestResource JSDoc)** — Fixed. Removed the "Consumes the wrapper" lines from both `fetchAllTags` and `fetchEnrichedTags`. The unwrapping is visible in the code itself. **Comment 366 (IngredientGroupMapper — ingredients mandatory?)** — No code change. Checked the contract: `RecipeIngredientGroupDto.ingredients` is typed `ingredients?: RecipeIngredientDto[]` — explicitly optional. The `?? []` fallback and the comment are correct. If the backend always populates it on reads and the contract should enforce that, the fix belongs in `recipe-platform/api-contract`, not here. **Comment 373 (RecipeMapper — check contract)** — The `?? []` fallbacks for `instructions` and `ingredientGroups` are correct: `RecipeDto` marks both as optional (the DTO is shared between write payloads and read responses). The `title!` assertion on line 20 remains — `RecipeDto.title` is `title?: string` in the contract, so the `!` is required by TypeScript. The business logic says title is NOT NULL, so the contract should mark it `title: string` — that is a `recipe-platform` fix, not frontend. **Comment 376 (contract enums in models)** — Addressed by `22ec77b` which documents the decision with an ADR. The enums stay; the rationale is recorded. **Bonus fixes also in `82aeb0b`:** Two stray `!` assertions that `6b55bf5` missed — `randomImage.url!` in `CompactRecipeMapper` (url is required per contract) and `dto.description!` in `TagMapper.mapEnrichedDtoToModel` (EnrichedTagDto.description is also required per contract), plus a misleading comment claiming it was optional.
araemer merged commit ca80ae3c0e into stage 2026-09-26 05:18:47 +00:00
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!35
No description provided.