Frontend: B2 — Recipe detail view #24

Closed
opened 2026-04-02 11:27:39 +02:00 by marcel · 6 comments
Owner

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)

  • Green-tint hero banner (24px padding, 20px bottom padding)
    • Back link
    • Title: Fraunces 24px
    • Pills row: time, effort, serves
    • "Cook now" button
  • Below hero: ingredients section + steps section (stacked)

Desktop (> 1024px)

  • Sidebar (224px) + topbar (breadcrumb + Edit button)
  • Full-width hero banner (--green-tint bg, 32px padding)
    • Name: Fraunces 28px weight 400
    • Pills: time, effort, serves, tags
    • Description text
    • "Cook now" button (right side, flex-shrink: 0)
  • Below hero: 2-column content
    • Left (flex:1, border-right, 24px padding): ingredients list
    • Right (flex:1, 24px padding): numbered steps

Ingredients

  • Eyebrow label: "Ingredients"
  • Rows: quantity + ingredient name (no remove button — read-only)
  • Scaled to saved serving count (no adjustment in this view)

Steps

  • Numbered circles (28px) + step text (14px body, 1.6 line-height)

Hero Variants

  • Without image: --green-tint bg, dark text
  • With image: image bg with dark overlay, white text

Behavior

  • "Cook now" button → navigates to B4 (cook mode)
  • Edit button (desktop topbar) → navigates to B3 (edit form, prefilled)
  • Back link → returns to B1

Acceptance Criteria

  • Mobile: hero banner + stacked ingredients/steps
  • Desktop: hero + 2-column ingredients|steps
  • Hero supports both image and no-image variants
  • "Cook now" → B4, Edit → B3, Back → B1
## 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) - Green-tint hero banner (24px padding, 20px bottom padding) - Back link - Title: Fraunces 24px - Pills row: time, effort, serves - "Cook now" button - Below hero: ingredients section + steps section (stacked) ### Desktop (> 1024px) - Sidebar (224px) + topbar (breadcrumb + Edit button) - Full-width hero banner (`--green-tint` bg, 32px padding) - Name: Fraunces 28px weight 400 - Pills: time, effort, serves, tags - Description text - "Cook now" button (right side, flex-shrink: 0) - Below hero: 2-column content - Left (flex:1, border-right, 24px padding): ingredients list - Right (flex:1, 24px padding): numbered steps ## Ingredients - Eyebrow label: "Ingredients" - Rows: quantity + ingredient name (no remove button — read-only) - Scaled to saved serving count (no adjustment in this view) ## Steps - Numbered circles (28px) + step text (14px body, 1.6 line-height) ## Hero Variants - **Without image**: `--green-tint` bg, dark text - **With image**: image bg with dark overlay, white text ## Behavior - "Cook now" button → navigates to B4 (cook mode) - Edit button (desktop topbar) → navigates to B3 (edit form, prefilled) - Back link → returns to B1 ## Acceptance Criteria - [ ] Mobile: hero banner + stacked ingredients/steps - [ ] Desktop: hero + 2-column ingredients|steps - [ ] Hero supports both image and no-image variants - [ ] "Cook now" → B4, Edit → B3, Back → B1
marcel added the kind/featurepriority/medium labels 2026-04-02 11:30:08 +02:00
Author
Owner

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.

**Spec file:** [`specs/frontend/j3-cook-tonight.html`](../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.
Author
Owner

👨‍💻 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 a ContentLayout component that handles the 2-column split.

Hero variants — image vs no-image:

  • No-image: --green-tint bg, dark text. Straightforward.
  • With-image: image background + dark overlay + white text. Questions:
    • Is the image a user-uploaded file? What dimensions and aspect ratio are expected? Does the backend serve a URL, or do I get a base64 blob?
    • The dark overlay — is there a defined opacity (e.g., rgba(0,0,0,0.45))? Or is the overlay token from the design system? I need exact values from Atlas.
    • Text contrast over arbitrary images is inherently tricky. Is white text always used, or does it adapt based on the image? I'll assume always-white per the spec, but want to confirm.

Navigation:

  • "Cook now" → B4: I'll use goto('/cook/[recipeId]') or a plain anchor. Does B4 need the plan slot context, or just the recipe ID? This affects the URL structure.
  • "Edit" → B3 prefilled: a link to /recipes/[recipeId]/edit. Straightforward.
  • "Back" → B1: is this 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

  • The spec says "Scaled to saved serving count (no adjustment in this view)." Does the API return already-scaled ingredient quantities, or raw quantities + a scale factor that the frontend applies? If frontend-calculated, I use $derived() to compute displayed quantities.
  • Are quantities always integers, or can they be fractions (0.5 cups, 1/3 tsp)? How should fractions render?

Steps: numbered circles

  • "Numbered circles (28px)" — are these --green filled circles with white numbers, or just bordered circles? The spec mentions circles but not their color. Need Atlas to clarify.
  • Step text: 14px body, 1.6 line-height. This is text-sm in Tailwind. Confirmed different from B4's non-negotiable 16px — B2 is read-before-cook, not mid-cook.

SSR:

  • All data loaded in +page.server.ts via GET /api/recipes/[recipeId]. No client-side fetching needed.
  • If the recipe doesn't exist, +page.server.ts throws a error(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 description field on the recipe entity, or is it derived from the steps/ingredients? The recipe form (B3) doesn't mention a description field explicitly.

## 👨‍💻 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 a `ContentLayout` component that handles the 2-column split. **Hero variants — image vs no-image:** - No-image: `--green-tint` bg, dark text. Straightforward. - With-image: image background + dark overlay + white text. Questions: - Is the image a user-uploaded file? What dimensions and aspect ratio are expected? Does the backend serve a URL, or do I get a base64 blob? - The dark overlay — is there a defined opacity (e.g., `rgba(0,0,0,0.45)`)? Or is the overlay token from the design system? I need exact values from Atlas. - Text contrast over arbitrary images is inherently tricky. Is white text always used, or does it adapt based on the image? I'll assume always-white per the spec, but want to confirm. **Navigation:** - "Cook now" → B4: I'll use `goto('/cook/[recipeId]')` or a plain anchor. Does B4 need the plan slot context, or just the recipe ID? This affects the URL structure. - "Edit" → B3 prefilled: a link to `/recipes/[recipeId]/edit`. Straightforward. - "Back" → B1: is this `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** - The spec says "Scaled to saved serving count (no adjustment in this view)." Does the API return already-scaled ingredient quantities, or raw quantities + a scale factor that the frontend applies? If frontend-calculated, I use `$derived()` to compute displayed quantities. - Are quantities always integers, or can they be fractions (0.5 cups, 1/3 tsp)? How should fractions render? **Steps: numbered circles** - "Numbered circles (28px)" — are these `--green` filled circles with white numbers, or just bordered circles? The spec mentions circles but not their color. Need Atlas to clarify. - Step text: 14px body, 1.6 line-height. This is `text-sm` in Tailwind. Confirmed different from B4's non-negotiable 16px — B2 is read-before-cook, not mid-cook. **SSR:** - All data loaded in `+page.server.ts` via `GET /api/recipes/[recipeId]`. No client-side fetching needed. - If the recipe doesn't exist, `+page.server.ts` throws a `error(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 `description` field on the recipe entity, or is it derived from the steps/ingredients? The recipe form (B3) doesn't mention a description field explicitly.
Author
Owner

🔧 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}

  • Response must include: recipe name, description (if it exists — see below), serves count, cook time, prep time, effort tag, category tags, ingredient list (quantity + name), steps (ordered by sequence), hero image URL (nullable).
  • The ingredient quantities should come pre-scaled to the saved serving count, or the response should include a serves field so the frontend can scale if needed. I'd return raw quantities + serves and let the frontend handle display scaling — it's simpler and the frontend can round/format as it sees fit.
  • Steps: if stored in a normalized recipe_step table, the query needs ORDER BY step_order ASC. Don't rely on insertion order.

Recipe schema gaps I noticed:

  • The desktop layout shows a "Description text" field in the hero. B3 (the add/edit form) does not include a description field in its spec. Is description in 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.
  • Hero image: is this stored as a file reference (path/URL to object storage or filesystem), or as a blob in the database? We should not store blobs in PostgreSQL for this — a file path or a signed URL to object storage is the right approach. This affects the DDL.

Authorization:

  • Who can view a recipe? Planners and members of the same household, presumably. Can a user from a different household access this recipe if they know the UUID? If recipes are household-scoped, every GET /api/recipes/{recipeId} must validate the recipe belongs to the requesting user's household.
  • If recipes can be "public" (shared across households — e.g., a recipe library), the access model is different. The v1 spec doesn't mention a shared library, so I'd assume household-scoped and enforce accordingly.

Performance:

  • B2 requires name + serves + times + tags + ingredients + steps in one response. If ingredients and steps are in separate tables, this is a 3-table join (or 3 separate queries). With JPA, be careful of the N+1 problem — use @EntityGraph or a custom JPQL query with JOIN FETCH to load everything in one round-trip.
  • Recipe detail pages are read-heavy. Consider adding a @Cacheable annotation on the service method if this becomes a bottleneck, but don't optimize prematurely.

What I need clarified:

  1. Is description a first-class field on the recipe entity?
  2. Are recipes household-scoped or globally accessible?
  3. Is hero_image a URL/path or a blob?
  4. Are ingredient quantities stored raw, or pre-scaled?
## 🔧 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}`** - Response must include: recipe name, description (if it exists — see below), serves count, cook time, prep time, effort tag, category tags, ingredient list (quantity + name), steps (ordered by sequence), hero image URL (nullable). - The ingredient quantities should come pre-scaled to the saved serving count, or the response should include a `serves` field so the frontend can scale if needed. I'd return raw quantities + `serves` and let the frontend handle display scaling — it's simpler and the frontend can round/format as it sees fit. - Steps: if stored in a normalized `recipe_step` table, the query needs `ORDER BY step_order ASC`. Don't rely on insertion order. **Recipe schema gaps I noticed:** - The desktop layout shows a "Description text" field in the hero. B3 (the add/edit form) does not include a description field in its spec. Is `description` in 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. - Hero image: is this stored as a file reference (path/URL to object storage or filesystem), or as a blob in the database? We should not store blobs in PostgreSQL for this — a file path or a signed URL to object storage is the right approach. This affects the DDL. **Authorization:** - Who can view a recipe? Planners and members of the same household, presumably. Can a user from a different household access this recipe if they know the UUID? If recipes are household-scoped, every `GET /api/recipes/{recipeId}` must validate the recipe belongs to the requesting user's household. - If recipes can be "public" (shared across households — e.g., a recipe library), the access model is different. The v1 spec doesn't mention a shared library, so I'd assume household-scoped and enforce accordingly. **Performance:** - B2 requires name + serves + times + tags + ingredients + steps in one response. If ingredients and steps are in separate tables, this is a 3-table join (or 3 separate queries). With JPA, be careful of the N+1 problem — use `@EntityGraph` or a custom JPQL query with `JOIN FETCH` to load everything in one round-trip. - Recipe detail pages are read-heavy. Consider adding a `@Cacheable` annotation on the service method if this becomes a bottleneck, but don't optimize prematurely. **What I need clarified:** 1. Is `description` a first-class field on the `recipe` entity? 2. Are recipes household-scoped or globally accessible? 3. Is `hero_image` a URL/path or a blob? 4. Are ingredient quantities stored raw, or pre-scaled?
Author
Owner

🧪 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:

  • Recipe without image: --green-tint hero renders, name visible, pills show time/effort/serves, ingredients list populated, steps numbered and in order, "Cook now" and "Edit" (desktop) buttons present
  • Recipe with image: hero renders with image background, dark overlay applied, white text legible
  • "Cook now" → navigates to B4 with correct recipe ID
  • "Edit" (desktop topbar) → navigates to B3 with recipe data prefilled
  • "Back" → navigates to B1 (recipe list)

Edge cases:

  • Recipe with no description: desktop hero renders without description block, no empty placeholder visible
  • Recipe with no steps: does the steps section render empty, show a placeholder, or is "Cook now" hidden? This depends on the design decision — but we need a test for whichever behavior is chosen
  • Recipe with 1 ingredient: ingredient list renders a single row, no layout issues
  • Recipe with many ingredients (20+): long list renders correctly, no overflow issues
  • Serving count of 1 vs 4: if ingredient quantities scale to 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)
  • Very long recipe name: does the Fraunces 24px title wrap gracefully in the mobile hero?
  • Special characters in recipe name or ingredient names (e.g., "Crème brûlée", "1/2 tsp"): render correctly without escaping artifacts

Component tests:

  • RecipeHero: renders no-image variant with correct background color; renders image variant with <img> or background-image and overlay; name visible in both cases
  • IngredientList: 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 data

Integration tests:

  • GET /api/recipes/{recipeId} as authenticated user (same household) → 200 with complete data
  • GET /api/recipes/{recipeId} as unauthenticated → 401
  • GET /api/recipes/{recipeId} with recipe from a different household → 403 (or 404 if we're not revealing existence)
  • GET /api/recipes/{nonExistentId} → 404
  • GET /api/recipes/{malformedId} (not a valid UUID) → 400

E2E:

  • Navigate from B1 → B2: click a recipe in the list, verify detail page loads correctly — @smoke
  • B2 → B4: click "Cook now", verify cook mode loads for the correct recipe
## 🧪 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:** - Recipe without image: `--green-tint` hero renders, name visible, pills show time/effort/serves, ingredients list populated, steps numbered and in order, "Cook now" and "Edit" (desktop) buttons present - Recipe with image: hero renders with image background, dark overlay applied, white text legible - "Cook now" → navigates to B4 with correct recipe ID - "Edit" (desktop topbar) → navigates to B3 with recipe data prefilled - "Back" → navigates to B1 (recipe list) **Edge cases:** - Recipe with no description: desktop hero renders without description block, no empty placeholder visible - Recipe with no steps: does the steps section render empty, show a placeholder, or is "Cook now" hidden? This depends on the design decision — but we need a test for whichever behavior is chosen - Recipe with 1 ingredient: ingredient list renders a single row, no layout issues - Recipe with many ingredients (20+): long list renders correctly, no overflow issues - Serving count of 1 vs 4: if ingredient quantities scale to `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) - Very long recipe name: does the Fraunces 24px title wrap gracefully in the mobile hero? - Special characters in recipe name or ingredient names (e.g., "Crème brûlée", "1/2 tsp"): render correctly without escaping artifacts **Component tests:** - `RecipeHero`: renders no-image variant with correct background color; renders image variant with `<img>` or `background-image` and overlay; name visible in both cases - `IngredientList`: 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 data **Integration tests:** - `GET /api/recipes/{recipeId}` as authenticated user (same household) → 200 with complete data - `GET /api/recipes/{recipeId}` as unauthenticated → 401 - `GET /api/recipes/{recipeId}` with recipe from a different household → 403 (or 404 if we're not revealing existence) - `GET /api/recipes/{nonExistentId}` → 404 - `GET /api/recipes/{malformedId}` (not a valid UUID) → 400 **E2E:** - Navigate from B1 → B2: click a recipe in the list, verify detail page loads correctly — @smoke - B2 → B4: click "Cook now", verify cook mode loads for the correct recipe
Author
Owner

🔒 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:

  • The core question: are recipes household-scoped or global? If household-scoped, GET /api/recipes/{recipeId} must validate the recipe's household_id matches the authenticated user's household. Without this check, a user who knows (or guesses) a UUID can view any household's recipes.
  • Even if recipe IDs are UUIDs (and they should be), don't rely on UUID unguessability as a security control. Enforce household membership in the query.
  • If there's any scenario where a recipe could be "shared" publicly, that needs explicit modeling (a is_public flag) and explicit access control — not an implicit fallback.

Image handling — the most interesting attack surface on B2:

  • If the hero image is a user-uploaded file served from a URL, where is it hosted? If it's served from the same domain (/uploads/...), that path must not allow directory traversal or serving of arbitrary files.
  • If images are stored in object storage (S3-compatible), are the URLs pre-signed with expiry, or are they public permanent URLs? Public permanent URLs for user-generated content are a privacy leak — once shared, they never expire.
  • The image URL in the API response must point to a controlled CDN or proxy, not a raw user-controllable URL that could reference an external server (SSRF vector if the backend fetches images server-side, though on B2 the browser fetches it directly).

XSS surface:

  • Recipe name, description, ingredient names, step text — all user-generated content. These must be rendered as plain text in Svelte (which escapes by default), not via {@html}. The image URL, if rendered as a CSS background-image or <img src>, must be validated to be a relative path or a trusted domain — an attacker who controls the image URL field could inject javascript: URIs in older browsers (though modern browsers block this).
  • Confirm with Kai: no {@html} for any user-generated text fields on B2.

Information leakage:

  • The API response for B2 must not include fields like created_by_user_id, internal database IDs beyond what the frontend needs, or any fields from other households' data.
  • A 404 vs 403 distinction: if I request a recipe from another household and get a 404, I learn the recipe doesn't exist (from my perspective). If I get a 403, I learn it exists but I can't access it. For a private household app, returning 404 for both "not found" and "not authorized" is the safer choice (less information exposure).

No concerns with the navigation links — they're standard anchor tags, no security implications.

## 🔒 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:** - The core question: are recipes household-scoped or global? If household-scoped, `GET /api/recipes/{recipeId}` must validate the recipe's `household_id` matches the authenticated user's household. Without this check, a user who knows (or guesses) a UUID can view any household's recipes. - Even if recipe IDs are UUIDs (and they should be), don't rely on UUID unguessability as a security control. Enforce household membership in the query. - If there's any scenario where a recipe could be "shared" publicly, that needs explicit modeling (a `is_public` flag) and explicit access control — not an implicit fallback. **Image handling — the most interesting attack surface on B2:** - If the hero image is a user-uploaded file served from a URL, where is it hosted? If it's served from the same domain (`/uploads/...`), that path must not allow directory traversal or serving of arbitrary files. - If images are stored in object storage (S3-compatible), are the URLs pre-signed with expiry, or are they public permanent URLs? Public permanent URLs for user-generated content are a privacy leak — once shared, they never expire. - The image URL in the API response must point to a controlled CDN or proxy, not a raw user-controllable URL that could reference an external server (SSRF vector if the backend fetches images server-side, though on B2 the browser fetches it directly). **XSS surface:** - Recipe name, description, ingredient names, step text — all user-generated content. These must be rendered as plain text in Svelte (which escapes by default), not via `{@html}`. The image URL, if rendered as a CSS `background-image` or `<img src>`, must be validated to be a relative path or a trusted domain — an attacker who controls the image URL field could inject `javascript:` URIs in older browsers (though modern browsers block this). - Confirm with Kai: no `{@html}` for any user-generated text fields on B2. **Information leakage:** - The API response for B2 must not include fields like `created_by_user_id`, internal database IDs beyond what the frontend needs, or any fields from other households' data. - A 404 vs 403 distinction: if I request a recipe from another household and get a 404, I learn the recipe doesn't exist (from my perspective). If I get a 403, I learn it exists but I can't access it. For a private household app, returning 404 for both "not found" and "not authorized" is the safer choice (less information exposure). **No concerns with the navigation links** — they're standard anchor tags, no security implications.
Author
Owner

🎨 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:

  • No-image variant: --green-tint bg, dark text. This is consistent with our surface token usage. No issues.
  • Image variant: "image bg with dark overlay, white text." What's the overlay? I need to define this as a token or a documented value — 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.
  • The overlay approach means text is always white in the image variant. That's fine for the hero name, but what about the pills (time, effort, serves)? Are they white text on a semi-transparent dark background pill? Or do they use the same green token as the no-image variant? The spec says "Pills: time, effort, serves, tags" in the desktop image hero but doesn't specify the pill style for the image variant.

Typography on B2:

  • Mobile hero title: Fraunces 24px. Desktop: Fraunces 28px weight 400. Note: the spec specifies weight 400 for desktop — that's different from the default Fraunces usage elsewhere. Confirm this is intentional.
  • Step text: 14px body, 1.6 line-height. This is text-sm with a custom line height. I want to verify: is leading-relaxed (1.625) close enough, or does it need to be exactly 1.6?
  • Numbered circles (28px): what's the fill color and text color for the step number circles? The spec says "numbered circles (28px)" but doesn't specify the visual treatment. My expectation: --green fill, white number, DM Sans 13px — but I need to confirm.

Desktop 2-column layout:

  • "Left (flex:1, border-right, 24px padding): ingredients" — the border-right uses --color-border (1px). Confirmed.
  • "Right (flex:1, 24px padding): numbered steps" — no border-right on the right column. Clean.
  • What's the minimum column width before the 2-column layout collapses? The spec says "Desktop (>1024px)" but on a 1025px viewport, two flex:1 columns with 24px padding each will be fairly narrow. Is there a min-width on the columns?

"Cook now" button placement:

  • Mobile: in the hero below the pills. Desktop: "right side, flex-shrink: 0." That means it's in the same flex row as the recipe name/description. At what viewport width does this start looking crowded if the recipe name is long? Worth testing in the spec file.

Missing spec item:

  • The "Edit" button on desktop topbar: what's the button style? Primary? Ghost? The standard button rules say 13px, weight 500, tracking 0.04em — but is it a filled button or an outline/ghost? I'd lean ghost for an edit action on a detail page.
## 🎨 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:** - **No-image variant**: `--green-tint` bg, dark text. This is consistent with our surface token usage. No issues. - **Image variant**: "image bg with dark overlay, white text." What's the overlay? I need to define this as a token or a documented value — `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. - The overlay approach means text is always white in the image variant. That's fine for the hero name, but what about the pills (time, effort, serves)? Are they white text on a semi-transparent dark background pill? Or do they use the same green token as the no-image variant? The spec says "Pills: time, effort, serves, tags" in the desktop image hero but doesn't specify the pill style for the image variant. **Typography on B2:** - Mobile hero title: Fraunces 24px. Desktop: Fraunces 28px weight 400. Note: the spec specifies weight 400 for desktop — that's different from the default Fraunces usage elsewhere. Confirm this is intentional. - Step text: 14px body, 1.6 line-height. This is `text-sm` with a custom line height. I want to verify: is `leading-relaxed` (1.625) close enough, or does it need to be exactly 1.6? - Numbered circles (28px): what's the fill color and text color for the step number circles? The spec says "numbered circles (28px)" but doesn't specify the visual treatment. My expectation: `--green` fill, white number, DM Sans 13px — but I need to confirm. **Desktop 2-column layout:** - "Left (flex:1, border-right, 24px padding): ingredients" — the border-right uses `--color-border` (1px). Confirmed. - "Right (flex:1, 24px padding): numbered steps" — no border-right on the right column. Clean. - What's the minimum column width before the 2-column layout collapses? The spec says "Desktop (>1024px)" but on a 1025px viewport, two `flex:1` columns with 24px padding each will be fairly narrow. Is there a `min-width` on the columns? **"Cook now" button placement:** - Mobile: in the hero below the pills. Desktop: "right side, flex-shrink: 0." That means it's in the same flex row as the recipe name/description. At what viewport width does this start looking crowded if the recipe name is long? Worth testing in the spec file. **Missing spec item:** - The "Edit" button on desktop topbar: what's the button style? Primary? Ghost? The standard button rules say 13px, weight 500, tracking 0.04em — but is it a filled button or an outline/ghost? I'd lean ghost for an edit action on a detail page.
Sign in to join this conversation.