Frontend: B2 — Recipe detail view #24
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
View a recipe's full details before cooking. Hero header layout with ingredients and steps.
Journey: J1 (view) + J3 (pre-cook)
Role: Planner
Screen: B2
Layout
Mobile (< 768px)
Desktop (> 1024px)
--green-tintbg, 32px padding)Ingredients
Steps
Hero Variants
--green-tintbg, dark textBehavior
Acceptance Criteria
Spec file:
specs/frontend/j3-cook-tonight.html— screen B2 with mobile (hero banner + stacked) + desktop (hero + 2-col ingredients/steps) previews, agent table, and LLM implementation guide.👨💻 Kai — Frontend Engineer
B2 is a read-only detail view — no mutations, no role-gating complexity. That makes it one of the more straightforward screens to implement, but there are a few things I want to nail before I start.
Component decomposition:
RecipeHero(handles both image and no-image variants),IngredientList,StepList. The hero is the most complex piece. Everything else is essentially a styled list. On desktop I wrap them in aContentLayoutcomponent that handles the 2-column split.Hero variants — image vs no-image:
--green-tintbg, dark text. Straightforward.rgba(0,0,0,0.45))? Or is the overlay token from the design system? I need exact values from Atlas.Navigation:
goto('/cook/[recipeId]')or a plain anchor. Does B4 need the plan slot context, or just the recipe ID? This affects the URL structure./recipes/[recipeId]/edit. Straightforward.history.back()(fragile — doesn't always go to B1) or a hardcoded link to/recipes? I'd use the hardcoded link unless there's a design reason for dynamic back navigation.Ingredients: scaled to saved serving count
$derived()to compute displayed quantities.Steps: numbered circles
--greenfilled circles with white numbers, or just bordered circles? The spec mentions circles but not their color. Need Atlas to clarify.text-smin Tailwind. Confirmed different from B4's non-negotiable 16px — B2 is read-before-cook, not mid-cook.SSR:
+page.server.tsviaGET /api/recipes/[recipeId]. No client-side fetching needed.+page.server.tsthrows aerror(404, 'Recipe not found'). SvelteKit handles the error page automatically.One open question: The desktop spec shows "Description text" in the hero. Is this a separate
descriptionfield on the recipe entity, or is it derived from the steps/ingredients? The recipe form (B3) doesn't mention a description field explicitly.🔧 Backend Engineer
B2 is a pure read —
GET /api/recipes/{recipeId}. Simple on the surface, but a few things need thought.The endpoint:
GET /api/recipes/{recipeId}servesfield so the frontend can scale if needed. I'd return raw quantities +servesand let the frontend handle display scaling — it's simpler and the frontend can round/format as it sees fit.recipe_steptable, the query needsORDER BY step_order ASC. Don't rely on insertion order.Recipe schema gaps I noticed:
descriptionin the recipe entity or not? If it is, B3 needs a form field for it. If it isn't, the desktop hero layout spec needs updating.Authorization:
GET /api/recipes/{recipeId}must validate the recipe belongs to the requesting user's household.Performance:
@EntityGraphor a custom JPQL query withJOIN FETCHto load everything in one round-trip.@Cacheableannotation on the service method if this becomes a bottleneck, but don't optimize prematurely.What I need clarified:
descriptiona first-class field on therecipeentity?hero_imagea URL/path or a blob?🧪 QA Engineer
B2 is a read-only screen so there are no mutation paths to test — but the two hero variants, the navigation links, and the ingredient scaling logic give me enough to build a thorough suite.
Happy paths:
--green-tinthero renders, name visible, pills show time/effort/serves, ingredients list populated, steps numbered and in order, "Cook now" and "Edit" (desktop) buttons presentEdge cases:
serves, verify the displayed values are correct (e.g., a recipe for 4 shows "200g" when serves=4, and should show "50g" if somehow displayed for serves=1 — but the spec says no adjustment in B2, so quantities should be fixed)Component tests:
RecipeHero: renders no-image variant with correct background color; renders image variant with<img>orbackground-imageand overlay; name visible in both casesIngredientList: renders correct number of rows; each row has quantity and name; no remove button present (read-only)StepList: steps render in correct order; numbered circles show correct step number; step text matches dataIntegration tests:
GET /api/recipes/{recipeId}as authenticated user (same household) → 200 with complete dataGET /api/recipes/{recipeId}as unauthenticated → 401GET /api/recipes/{recipeId}with recipe from a different household → 403 (or 404 if we're not revealing existence)GET /api/recipes/{nonExistentId}→ 404GET /api/recipes/{malformedId}(not a valid UUID) → 400E2E:
🔒 Sable — Security Engineer
B2 is a read-only screen but there are meaningful security considerations around resource access, image handling, and what data the API response exposes.
Broken access control:
GET /api/recipes/{recipeId}must validate the recipe'shousehold_idmatches the authenticated user's household. Without this check, a user who knows (or guesses) a UUID can view any household's recipes.is_publicflag) and explicit access control — not an implicit fallback.Image handling — the most interesting attack surface on B2:
/uploads/...), that path must not allow directory traversal or serving of arbitrary files.XSS surface:
{@html}. The image URL, if rendered as a CSSbackground-imageor<img src>, must be validated to be a relative path or a trusted domain — an attacker who controls the image URL field could injectjavascript:URIs in older browsers (though modern browsers block this).{@html}for any user-generated text fields on B2.Information leakage:
created_by_user_id, internal database IDs beyond what the frontend needs, or any fields from other households' data.No concerns with the navigation links — they're standard anchor tags, no security implications.
🎨 Atlas — UI/UX Designer
B2 is a well-structured spec but there are a few gaps in the visual system that need resolution before Kai implements the hero variants and the content layout.
Hero variants — the main open item:
--green-tintbg, dark text. This is consistent with our surface token usage. No issues.rgba(0,0,0,0.45)is typical for legibility, but we should verify contrast of white text over it (the overlay needs to guarantee 4.5:1 for the recipe name). This is the one place on B2 where WCAG compliance depends on a runtime value (the image), not a fixed color. I'd recommend a minimum overlay opacity of 0.5 and a test image in the spec to validate.Typography on B2:
text-smwith a custom line height. I want to verify: isleading-relaxed(1.625) close enough, or does it need to be exactly 1.6?--greenfill, white number, DM Sans 13px — but I need to confirm.Desktop 2-column layout:
--color-border(1px). Confirmed.flex:1columns with 24px padding each will be fairly narrow. Is there amin-widthon the columns?"Cook now" button placement:
Missing spec item: