Maestro task 70a903ef-107c-444e-b3b0-fb3d37e8fb7d. Replaces the /dinner/:id placeholder with mock 1k: the 250 px washed photo (striped placeholder when there is none) under a round back button, a sheet overlapping by 28 px with radius-32 top corners, tags, the Bagel title, the teal "Why it was picked" box when a reason exists, ingredients with right-aligned quantities and dividers, numbered steps, a "Recipe from {site}" link opening in a new tab, and a sticky bottom bar with "Add to this week" and the heart. A failed-grounding dinner shows "Fix recipe" and does not pretend to have ingredients or steps.
The real API contract, honoured deliberately:source may carry only site_name — the raw source URL is never returned, because a pasted URL can carry a token — so attribution renders only when it exists rather than as a broken or guessed link; and an ingredient whose name is null shows its raw_text rather than an empty row.
Deviations from the mock, stated rather than hidden: the app's bottom navigation stays visible on this screen although mock 1k shows none (the shell tests expect the nav on /dinner; the action bar sits above it); the "Side quest" tag uses the shared Tag colour (grape-100) rather than the mock's grape-200; and "Fix recipe" opens the Regular dinners list instead of that dinner's fix sheet, because the page has no deep link for it. A shared primitive changed — web/src/ui/IconButton.tsx (with its css and test) for the round back button. An existing test moved route — App.test.tsx's offline-reload test used /dinner/:id as its placeholder and now uses /cook-from-pantry.
Behaviour: a dinner whose recipe is still being found is re-read every 3 seconds until it is found or fails, and the query runs even when the browser reports offline so the service worker's saved copy shows, or an honest error appears, instead of an endless skeleton.
Not verified: tablet and desktop layouts (a simple centred column for now; the tablet Kitchen reading mode is a later task) and safe-area insets on a real device.
The developer reports npm run typecheck, npm test and npm run build all pass. No Go file changed.
## Mobile recipe detail (1k) (Feature 15, task 2)
Maestro task `70a903ef-107c-444e-b3b0-fb3d37e8fb7d`. Replaces the `/dinner/:id` placeholder with mock 1k: the 250 px washed photo (striped placeholder when there is none) under a round back button, a sheet overlapping by 28 px with radius-32 top corners, tags, the Bagel title, the teal "Why it was picked" box when a reason exists, ingredients with right-aligned quantities and dividers, numbered steps, a "Recipe from {site}" link opening in a new tab, and a sticky bottom bar with "Add to this week" and the heart. A failed-grounding dinner shows "Fix recipe" and does not pretend to have ingredients or steps.
**The real API contract, honoured deliberately:** `source` may carry only `site_name` — the raw source URL is never returned, because a pasted URL can carry a token — so attribution renders only when it exists rather than as a broken or guessed link; and an ingredient whose `name` is null shows its `raw_text` rather than an empty row.
**Deviations from the mock, stated rather than hidden:** the app's bottom navigation stays visible on this screen although mock 1k shows none (the shell tests expect the nav on `/dinner`; the action bar sits above it); the "Side quest" tag uses the shared `Tag` colour (grape-100) rather than the mock's grape-200; and "Fix recipe" opens the Regular dinners list instead of that dinner's fix sheet, because the page has no deep link for it. **A shared primitive changed** — `web/src/ui/IconButton.tsx` (with its css and test) for the round back button. **An existing test moved route** — `App.test.tsx`'s offline-reload test used `/dinner/:id` as its placeholder and now uses `/cook-from-pantry`.
**Behaviour:** a dinner whose recipe is still being found is re-read every 3 seconds until it is found or fails, and the query runs even when the browser reports offline so the service worker's saved copy shows, or an honest error appears, instead of an endless skeleton.
**Not verified:** tablet and desktop layouts (a simple centred column for now; the tablet Kitchen reading mode is a later task) and safe-area insets on a real device.
The developer reports `npm run typecheck`, `npm test` and `npm run build` all pass. No Go file changed.
Replace the /dinner/:id placeholder with DinnerDetailPage, reading
GET /api/v1/dinners/{id} through useDinnerDetail: a 250 px full-bleed
washed photo (striped placeholder without one) with a round back button
that goes Home from a deep link; a sheet overlapping by 28 px with the
Regular or Side quest, difficulty and time tags, the 28 px Bagel title,
the teal "Why it was picked" box, ingredients in recipe order with
quantities right-aligned (raw_text when a line has no name), numbered
steps and "Recipe from {site}" when the server names a site. The API
sends no source link, so the credit is text, not a link.
A dinner whose recipe was not found links to Settings -> Regular
dinners instead of showing a recipe; a pending one says it is still
searching and is read again until it lands. A 404 is a "Dinner not
found" state; other failures retry. The sticky bar renders "Add to this
week" and the heart (showing is_favorite) unavailable until task
07423ba7 wires them. IconButton gains a 52 px size for the heart.
Maestro task 70a903ef-107c-444e-b3b0-fb3d37e8fb7d.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The detail page now fills the shell's scroll area (min-height: 100% of
a stretched flex item) and the sheet grows into the room left, so on a
short or failed recipe Add to this week and the heart rest at the
bottom of the screen; over a long recipe the bar still sticks there.
useDinnerDetail reads through apiRead and keeps whether the answer was
the service worker's saved copy. A pending dinner is polled only while
the network status says the server can be reached and the last answer
was not a saved copy; a saved pending copy is read again once the
connection comes back. TanStack Query clears the interval on unmount.
The credit stays plain text. source.url is parsed when present (https,
no userinfo, query or fragment, else the read fails) and makes "Recipe
from {site}" a new-tab link; today's API never sends it.
Maestro task 70a903ef-107c-444e-b3b0-fb3d37e8fb7d.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Mobile recipe detail (1k) (Feature 15, task 2)
Maestro task
70a903ef-107c-444e-b3b0-fb3d37e8fb7d. Replaces the/dinner/:idplaceholder with mock 1k: the 250 px washed photo (striped placeholder when there is none) under a round back button, a sheet overlapping by 28 px with radius-32 top corners, tags, the Bagel title, the teal "Why it was picked" box when a reason exists, ingredients with right-aligned quantities and dividers, numbered steps, a "Recipe from {site}" link opening in a new tab, and a sticky bottom bar with "Add to this week" and the heart. A failed-grounding dinner shows "Fix recipe" and does not pretend to have ingredients or steps.The real API contract, honoured deliberately:
sourcemay carry onlysite_name— the raw source URL is never returned, because a pasted URL can carry a token — so attribution renders only when it exists rather than as a broken or guessed link; and an ingredient whosenameis null shows itsraw_textrather than an empty row.Deviations from the mock, stated rather than hidden: the app's bottom navigation stays visible on this screen although mock 1k shows none (the shell tests expect the nav on
/dinner; the action bar sits above it); the "Side quest" tag uses the sharedTagcolour (grape-100) rather than the mock's grape-200; and "Fix recipe" opens the Regular dinners list instead of that dinner's fix sheet, because the page has no deep link for it. A shared primitive changed —web/src/ui/IconButton.tsx(with its css and test) for the round back button. An existing test moved route —App.test.tsx's offline-reload test used/dinner/:idas its placeholder and now uses/cook-from-pantry.Behaviour: a dinner whose recipe is still being found is re-read every 3 seconds until it is found or fails, and the query runs even when the browser reports offline so the service worker's saved copy shows, or an honest error appears, instead of an endless skeleton.
Not verified: tablet and desktop layouts (a simple centred column for now; the tablet Kitchen reading mode is a later task) and safe-area insets on a real device.
The developer reports
npm run typecheck,npm testandnpm run buildall pass. No Go file changed.Replace the /dinner/:id placeholder with DinnerDetailPage, reading GET /api/v1/dinners/{id} through useDinnerDetail: a 250 px full-bleed washed photo (striped placeholder without one) with a round back button that goes Home from a deep link; a sheet overlapping by 28 px with the Regular or Side quest, difficulty and time tags, the 28 px Bagel title, the teal "Why it was picked" box, ingredients in recipe order with quantities right-aligned (raw_text when a line has no name), numbered steps and "Recipe from {site}" when the server names a site. The API sends no source link, so the credit is text, not a link. A dinner whose recipe was not found links to Settings -> Regular dinners instead of showing a recipe; a pending one says it is still searching and is read again until it lands. A 404 is a "Dinner not found" state; other failures retry. The sticky bar renders "Add to this week" and the heart (showing is_favorite) unavailable until task 07423ba7 wires them. IconButton gains a 52 px size for the heart. Maestro task 70a903ef-107c-444e-b3b0-fb3d37e8fb7d. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>Independent Codex gpt-6-sol review: CHANGES_REQUIRED for this exact commit.
The detail page now fills the shell's scroll area (min-height: 100% of a stretched flex item) and the sheet grows into the room left, so on a short or failed recipe Add to this week and the heart rest at the bottom of the screen; over a long recipe the bar still sticks there. useDinnerDetail reads through apiRead and keeps whether the answer was the service worker's saved copy. A pending dinner is polled only while the network status says the server can be reached and the last answer was not a saved copy; a saved pending copy is read again once the connection comes back. TanStack Query clears the interval on unmount. The credit stays plain text. source.url is parsed when present (https, no userinfo, query or fragment, else the read fails) and makes "Recipe from {site}" a new-tab link; today's API never sends it. Maestro task 70a903ef-107c-444e-b3b0-fb3d37e8fb7d. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>Independent Codex gpt-6-sol review: APPROVED for this exact commit.