Frontend: B4 — Cook mode (full-screen step-by-step) #25

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

Summary

Full-screen cooking mode. One step at a time, large readable text, tap-anywhere to advance. Kitchen-critical — the user has wet hands and is standing at a stove.

Journey: J3 — Cook tonight
Role: Planner
Screen: B4
Context: Mobile, hands busy, kitchen environment

Layout — IDENTICAL on all breakpoints

This is the only screen that uses the same layout on mobile, tablet, and desktop. No sidebar, no tabs, no navigation chrome on any breakpoint.

  • Topbar: Exit button (error red, left) + progress indicator (center) + empty right
  • Progress bar: 4px height, --green fill, width = step/total percentage
  • Body: Centered vertically and horizontally
    • Step number: Fraunces 56px (mobile) / 72px (desktop), weight 300, --green-light color
    • Step text: exactly 16px, line-height 1.75 (NON-NEGOTIABLE)
    • Tap hint: muted text below
  • Max text width: 260px (mobile) / 320px (tablet) / 400px (desktop)

Critical Design Constraints

  • Body text MUST be 16px with 1.75 line-height — readability when hands are dirty
  • Entire body is the tap target — no precise tapping required
  • Screen wake lock: navigator.wakeLock.request('screen') on enter, release on exit

Behavior

  • Tap anywhere → advance to next step
  • Exit button → dismiss with confirmation dialog
  • Final step shows "Done — mark as cooked" action
  • "Mark as cooked" → INSERT cooking_log with today's date → return to C1
  • The cooking log feeds the variety algorithm (J2 suggestions)

Acceptance Criteria

  • Full-screen on all breakpoints — NO sidebar, tabs, or nav chrome
  • Step text: 16px, line-height 1.75 (non-negotiable)
  • Tap anywhere advances to next step
  • Progress bar shows step N of M
  • Screen wake lock active during cook mode
  • Final step: "Mark as cooked" logs to cooking history
  • Exit with confirmation dialog
## Summary Full-screen cooking mode. One step at a time, large readable text, tap-anywhere to advance. Kitchen-critical — the user has wet hands and is standing at a stove. **Journey:** J3 — Cook tonight **Role:** Planner **Screen:** B4 **Context:** Mobile, hands busy, kitchen environment ## Layout — IDENTICAL on all breakpoints This is the **only screen** that uses the same layout on mobile, tablet, and desktop. No sidebar, no tabs, no navigation chrome on any breakpoint. - **Topbar**: Exit button (error red, left) + progress indicator (center) + empty right - **Progress bar**: 4px height, `--green` fill, width = step/total percentage - **Body**: Centered vertically and horizontally - Step number: Fraunces 56px (mobile) / 72px (desktop), weight 300, `--green-light` color - Step text: **exactly 16px, line-height 1.75** (NON-NEGOTIABLE) - Tap hint: muted text below - **Max text width**: 260px (mobile) / 320px (tablet) / 400px (desktop) ## Critical Design Constraints - Body text MUST be 16px with 1.75 line-height — readability when hands are dirty - Entire body is the tap target — no precise tapping required - Screen wake lock: `navigator.wakeLock.request('screen')` on enter, release on exit ## Behavior - Tap anywhere → advance to next step - Exit button → dismiss with confirmation dialog - Final step shows "Done — mark as cooked" action - "Mark as cooked" → INSERT `cooking_log` with today's date → return to C1 - The cooking log feeds the variety algorithm (J2 suggestions) ## Acceptance Criteria - [ ] Full-screen on all breakpoints — NO sidebar, tabs, or nav chrome - [ ] Step text: 16px, line-height 1.75 (non-negotiable) - [ ] Tap anywhere advances to next step - [ ] Progress bar shows step N of M - [ ] Screen wake lock active during cook mode - [ ] Final step: "Mark as cooked" logs to cooking history - [ ] Exit with confirmation dialog
marcel added the kind/featurepriority/high labels 2026-04-02 11:30:09 +02:00
Author
Owner

Spec file: specs/frontend/j3-cook-tonight.html — screen B4 (KITCHEN CRITICAL) with mobile + desktop previews (identical layout), agent table with non-negotiable constraints (16px/1.75 body text, wake lock, tap-anywhere), and LLM implementation guide.

**Spec file:** [`specs/frontend/j3-cook-tonight.html`](../specs/frontend/j3-cook-tonight.html) — screen B4 (KITCHEN CRITICAL) with mobile + desktop previews (identical layout), agent table with non-negotiable constraints (16px/1.75 body text, wake lock, tap-anywhere), and LLM implementation guide.
Author
Owner

👨‍💻 Kai — Frontend Engineer

B4 is unusual — it's the simplest layout in the app (single column, identical across breakpoints) but has the most critical non-visual requirements: wake lock, tap-anywhere interaction, and a write operation at the end. Let me think through the implementation.

Component structure:

Single page, minimal decomposition needed: CookModeHeader (exit + progress), StepDisplay (step number + text + hint), CookModeFooter (only visible on final step). Maybe 3 files total. The simplicity is intentional — don't over-split this one.

Tap-anywhere interaction:

  • The entire body needs to be a tap target. I'm thinking a <button> or <div role="button"> covering the full viewport (minus the topbar exit button). The tricky part: the exit button must not advance the step when tapped — it needs to stop event propagation from the overlay click handler.
  • On desktop, "tap anywhere" works the same way (click anywhere). Keyboard: should Space or ArrowRight also advance? The spec doesn't say, but I'd add keyboard support for accessibility — especially since a keyboard user standing at a desk is a real scenario.

Screen wake lock:

  • navigator.wakeLock.request('screen') — I'll call this in $effect() on mount, not onMount(), since we're in Svelte 5.
  • Wake lock can be released by the browser when the tab is hidden. I need a document.addEventListener('visibilitychange', ...) handler to re-acquire it when the tab comes back into focus. If I don't, the screen will still sleep after the user checks their phone.
  • Wake lock is not universally supported (Firefox desktop doesn't support it). I'll wrap in a try/catch and fail silently — the feature degrades gracefully.
  • On exit (confirmation dialog confirmed), I must release() the lock explicitly.

Confirmation dialog on exit:

  • The spec says "dismiss with confirmation dialog." Is this a native window.confirm() (simple but ugly) or a custom modal component? Given the kitchen context, native confirm might actually be preferable — it doesn't require building a modal. But if a custom modal already exists in the design system, I'd use that.

"Mark as cooked" → INSERT cooking_log:

  • This is a form action. I'll use a SvelteKit +page.server.ts action (not a client-side fetch) so it works even if JS is slow. The action receives the recipe ID (or plan slot ID?), inserts the log entry, then redirects back to C1.
  • Question: what's the payload? Is it { recipe_id, date } or { plan_slot_id, date }? If a recipe is cooked outside of the plan, does that also feed the variety score?

State management:

  • currentStep: $state(0) — simple integer, incremented on tap
  • totalSteps: number — derived from the steps array length
  • Steps data comes from the recipe loaded server-side. I need to confirm: does navigating to B4 require a recipe ID in the URL (e.g., /cook/[recipeId]), or does it receive the full recipe data via some other mechanism (store, URL state)?

One concern: The spec says "Final step shows 'Done — mark as cooked' action." If a user taps the final step area, does it advance to a "done" state, or does the "Mark as cooked" button appear and the user must tap it explicitly? I'd make it explicit — accidental logging would be annoying.

## 👨‍💻 Kai — Frontend Engineer B4 is unusual — it's the simplest layout in the app (single column, identical across breakpoints) but has the most critical non-visual requirements: wake lock, tap-anywhere interaction, and a write operation at the end. Let me think through the implementation. **Component structure:** Single page, minimal decomposition needed: `CookModeHeader` (exit + progress), `StepDisplay` (step number + text + hint), `CookModeFooter` (only visible on final step). Maybe 3 files total. The simplicity is intentional — don't over-split this one. **Tap-anywhere interaction:** - The entire body needs to be a tap target. I'm thinking a `<button>` or `<div role="button">` covering the full viewport (minus the topbar exit button). The tricky part: the exit button must not advance the step when tapped — it needs to stop event propagation from the overlay click handler. - On desktop, "tap anywhere" works the same way (click anywhere). Keyboard: should `Space` or `ArrowRight` also advance? The spec doesn't say, but I'd add keyboard support for accessibility — especially since a keyboard user standing at a desk is a real scenario. **Screen wake lock:** - `navigator.wakeLock.request('screen')` — I'll call this in `$effect()` on mount, not `onMount()`, since we're in Svelte 5. - Wake lock can be released by the browser when the tab is hidden. I need a `document.addEventListener('visibilitychange', ...)` handler to re-acquire it when the tab comes back into focus. If I don't, the screen will still sleep after the user checks their phone. - Wake lock is not universally supported (Firefox desktop doesn't support it). I'll wrap in a `try/catch` and fail silently — the feature degrades gracefully. - On exit (confirmation dialog confirmed), I must `release()` the lock explicitly. **Confirmation dialog on exit:** - The spec says "dismiss with confirmation dialog." Is this a native `window.confirm()` (simple but ugly) or a custom modal component? Given the kitchen context, native confirm might actually be preferable — it doesn't require building a modal. But if a custom modal already exists in the design system, I'd use that. **"Mark as cooked" → INSERT cooking_log:** - This is a form action. I'll use a SvelteKit `+page.server.ts` action (not a client-side fetch) so it works even if JS is slow. The action receives the recipe ID (or plan slot ID?), inserts the log entry, then redirects back to C1. - Question: what's the payload? Is it `{ recipe_id, date }` or `{ plan_slot_id, date }`? If a recipe is cooked outside of the plan, does that also feed the variety score? **State management:** - `currentStep: $state(0)` — simple integer, incremented on tap - `totalSteps: number` — derived from the steps array length - Steps data comes from the recipe loaded server-side. I need to confirm: does navigating to B4 require a recipe ID in the URL (e.g., `/cook/[recipeId]`), or does it receive the full recipe data via some other mechanism (store, URL state)? **One concern:** The spec says "Final step shows 'Done — mark as cooked' action." If a user taps the final step area, does it advance to a "done" state, or does the "Mark as cooked" button appear and the user must tap it explicitly? I'd make it explicit — accidental logging would be annoying.
Author
Owner

🔧 Backend Engineer

B4 is lightweight on the backend — one write endpoint and one read. But the cooking_log entry it creates feeds the variety algorithm, so the data model matters a lot here.

The write: POST /api/cooking-log

  • What's the minimum payload? I'd expect { planSlotId: UUID } or { recipeId: UUID, cookedOn: LocalDate }. The plan slot approach is cleaner — it links the log entry to the specific plan, which is more queryable for the variety algorithm.
  • Idempotency: what happens if the user taps "Mark as cooked" twice (double-submit, network retry)? Should this be a no-op (upsert by (household_id, recipe_id, cooked_on)), or return a 409? I'd lean toward idempotent upsert — in a kitchen, a confused second tap shouldn't show an error.
  • Authorization: the endpoint must verify the planSlotId (or recipeId) belongs to the user's household. Don't accept arbitrary IDs from the client.

The cooking_log table:

The variety algorithm depends on this. I'd expect:

CREATE TABLE cooking_log (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  household_id UUID NOT NULL REFERENCES household(id),
  recipe_id UUID NOT NULL REFERENCES recipe(id),
  cooked_on DATE NOT NULL,
  created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
  UNIQUE (household_id, recipe_id, cooked_on)
);
  • The UNIQUE constraint enforces the idempotency requirement at the DB level.
  • cooked_on is a DATE (not TIMESTAMPTZ) — do we agree on that? The variety algorithm cares about which day something was cooked, not the exact time.

The variety algorithm:

  • The issue says "The cooking log feeds the variety algorithm (J2 suggestions)." How does it feed it? Is the variety score computed from cooking_log for a rolling window (e.g., last 30 days), or only from the current week's plan?
  • If it's a rolling window, the GET /api/weeks/{year}/{week} response needs to join cooking_log for historical data — that could be a heavier query than it looks. Worth discussing the algorithm before the endpoint is built.

The read: navigating to B4

  • B4 needs the full recipe steps. Where do steps come from — a steps JSONB column on recipe, or a normalized recipe_step table? If it's normalized, the API needs to return steps ordered by their sequence number.
  • Does navigating to B4 require a plan_slot_id (to link the log correctly) or just a recipe_id? If someone opens cook mode from the recipe list without a plan slot, can they still log it?

One edge case: What if the recipe has no steps (the spec says steps are optional at save time in B3)? B4 can't show an empty step-by-step view. Does the "Cook now" button get hidden on B2 if there are no steps, or does B4 handle this gracefully?

## 🔧 Backend Engineer B4 is lightweight on the backend — one write endpoint and one read. But the `cooking_log` entry it creates feeds the variety algorithm, so the data model matters a lot here. **The write: `POST /api/cooking-log`** - What's the minimum payload? I'd expect `{ planSlotId: UUID }` or `{ recipeId: UUID, cookedOn: LocalDate }`. The plan slot approach is cleaner — it links the log entry to the specific plan, which is more queryable for the variety algorithm. - Idempotency: what happens if the user taps "Mark as cooked" twice (double-submit, network retry)? Should this be a no-op (upsert by `(household_id, recipe_id, cooked_on)`), or return a 409? I'd lean toward idempotent upsert — in a kitchen, a confused second tap shouldn't show an error. - Authorization: the endpoint must verify the `planSlotId` (or `recipeId`) belongs to the user's household. Don't accept arbitrary IDs from the client. **The `cooking_log` table:** The variety algorithm depends on this. I'd expect: ```sql CREATE TABLE cooking_log ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), household_id UUID NOT NULL REFERENCES household(id), recipe_id UUID NOT NULL REFERENCES recipe(id), cooked_on DATE NOT NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT now(), UNIQUE (household_id, recipe_id, cooked_on) ); ``` - The UNIQUE constraint enforces the idempotency requirement at the DB level. - `cooked_on` is a `DATE` (not `TIMESTAMPTZ`) — do we agree on that? The variety algorithm cares about which day something was cooked, not the exact time. **The variety algorithm:** - The issue says "The cooking log feeds the variety algorithm (J2 suggestions)." How does it feed it? Is the variety score computed from `cooking_log` for a rolling window (e.g., last 30 days), or only from the current week's plan? - If it's a rolling window, the `GET /api/weeks/{year}/{week}` response needs to join `cooking_log` for historical data — that could be a heavier query than it looks. Worth discussing the algorithm before the endpoint is built. **The read: navigating to B4** - B4 needs the full recipe steps. Where do steps come from — a `steps` JSONB column on `recipe`, or a normalized `recipe_step` table? If it's normalized, the API needs to return steps ordered by their sequence number. - Does navigating to B4 require a `plan_slot_id` (to link the log correctly) or just a `recipe_id`? If someone opens cook mode from the recipe list without a plan slot, can they still log it? **One edge case:** What if the recipe has no steps (the spec says steps are optional at save time in B3)? B4 can't show an empty step-by-step view. Does the "Cook now" button get hidden on B2 if there are no steps, or does B4 handle this gracefully?
Author
Owner

🧪 QA Engineer

B4 looks simple on the surface, but it has some genuinely tricky test scenarios — async browser APIs, stateful progression, and a write at the end that feeds a downstream algorithm.

Happy paths:

  • Recipe with N steps: navigate to cook mode, verify step 1 is shown, tap N times, verify "Mark as cooked" appears on final step, tap it, verify redirect to C1 and cooking_log entry exists in the database
  • Progress bar: after each tap, verify the bar width matches currentStep / totalSteps * 100%
  • Exit: tap exit, confirm dialog appears, confirm → redirect to C1, no cooking_log entry created

Edge cases that concern me:

  • Recipe with 1 step: step 1 is also the final step. Does "Mark as cooked" appear immediately? The tap-anywhere handler and the "Mark as cooked" button appearing simultaneously could be confusing — needs a clear test.
  • Recipe with 0 steps: this should not be reachable, but if it is, the component must not crash. What's the defined behavior? Error state? Redirect back?
  • Double-submit on "Mark as cooked": rapid taps on the final step action — does the backend handle it idempotently? The frontend should disable the button after the first submit.
  • Wake lock acquisition failure: navigator.wakeLock.request rejects (unsupported browser, permission denied, low battery on some devices). Does the component handle the rejection gracefully and continue working?
  • Tab hidden + restored: wake lock released by browser when tab loses focus. On tab restore, does the component re-acquire the lock? Test this by simulating visibilitychange event.
  • Exit without confirming: tap exit, tap "Cancel" in the dialog — confirm the user stays on the current step with no state lost.
  • Browser back button: bypasses the confirmation dialog. Does the wake lock get released? This is a known edge case for full-screen web apps.

Component tests (Svelte):

  • Render step 1 of 5: verify step number "1", correct step text, progress bar at 20%
  • Simulate tap: verify step advances to "2", progress bar at 40%
  • Tap to final step: verify "Mark as cooked" action appears
  • readonly mode: not applicable here (B4 is planner-only), but verify the route is guarded
  • Wake lock: mock navigator.wakeLock and verify request('screen') is called on mount, release() is called on exit

Integration tests:

  • POST /api/cooking-log with valid planSlotId → 201, entry in DB
  • Same request twice → idempotent (200 or 201, no duplicate entry)
  • POST /api/cooking-log with planSlotId from a different household → 403
  • POST /api/cooking-log unauthenticated → 401

E2E (critical — this is a core user journey):

  • Full cook flow: log in, navigate to C1, open cook mode for a meal, step through all steps, mark as cooked, verify redirect to C1 — @critical
  • Exit flow: enter cook mode, tap exit, confirm, verify back on C1

One question I need answered before writing the component tests: does the step progression happen by advancing a currentStep index, or does the component actually remove/replace DOM elements? The test strategy differs slightly depending on how it's implemented.

## 🧪 QA Engineer B4 looks simple on the surface, but it has some genuinely tricky test scenarios — async browser APIs, stateful progression, and a write at the end that feeds a downstream algorithm. **Happy paths:** - Recipe with N steps: navigate to cook mode, verify step 1 is shown, tap N times, verify "Mark as cooked" appears on final step, tap it, verify redirect to C1 and `cooking_log` entry exists in the database - Progress bar: after each tap, verify the bar width matches `currentStep / totalSteps * 100%` - Exit: tap exit, confirm dialog appears, confirm → redirect to C1, no `cooking_log` entry created **Edge cases that concern me:** - **Recipe with 1 step**: step 1 is also the final step. Does "Mark as cooked" appear immediately? The tap-anywhere handler and the "Mark as cooked" button appearing simultaneously could be confusing — needs a clear test. - **Recipe with 0 steps**: this should not be reachable, but if it is, the component must not crash. What's the defined behavior? Error state? Redirect back? - **Double-submit on "Mark as cooked"**: rapid taps on the final step action — does the backend handle it idempotently? The frontend should disable the button after the first submit. - **Wake lock acquisition failure**: `navigator.wakeLock.request` rejects (unsupported browser, permission denied, low battery on some devices). Does the component handle the rejection gracefully and continue working? - **Tab hidden + restored**: wake lock released by browser when tab loses focus. On tab restore, does the component re-acquire the lock? Test this by simulating `visibilitychange` event. - **Exit without confirming**: tap exit, tap "Cancel" in the dialog — confirm the user stays on the current step with no state lost. - **Browser back button**: bypasses the confirmation dialog. Does the wake lock get released? This is a known edge case for full-screen web apps. **Component tests (Svelte):** - Render step 1 of 5: verify step number "1", correct step text, progress bar at 20% - Simulate tap: verify step advances to "2", progress bar at 40% - Tap to final step: verify "Mark as cooked" action appears - `readonly` mode: not applicable here (B4 is planner-only), but verify the route is guarded - Wake lock: mock `navigator.wakeLock` and verify `request('screen')` is called on mount, `release()` is called on exit **Integration tests:** - `POST /api/cooking-log` with valid `planSlotId` → 201, entry in DB - Same request twice → idempotent (200 or 201, no duplicate entry) - `POST /api/cooking-log` with `planSlotId` from a different household → 403 - `POST /api/cooking-log` unauthenticated → 401 **E2E (critical — this is a core user journey):** - Full cook flow: log in, navigate to C1, open cook mode for a meal, step through all steps, mark as cooked, verify redirect to C1 — @critical - Exit flow: enter cook mode, tap exit, confirm, verify back on C1 One question I need answered before writing the component tests: does the step progression happen by advancing a `currentStep` index, or does the component actually remove/replace DOM elements? The test strategy differs slightly depending on how it's implemented.
Author
Owner

🔒 Sable — Security Engineer

B4 is simpler than C1 from a security standpoint, but the "Mark as cooked" write and the full-screen, navigation-free layout create a few specific concerns worth addressing explicitly.

Authorization on the write endpoint:

  • POST /api/cooking-log must validate that the authenticated user belongs to the same household as the referenced plan slot or recipe. This is not optional — it's a direct write to the cooking_log table, and if the planSlotId is not validated against the session's household, a user could log cooking events for another household's recipes.
  • The endpoint must require an active session (401 if missing) and planner role (403 if member). Members should not be able to log cooking events.
  • Confirm: is there a route guard in hooks.server.ts that prevents members from navigating to B4 at all? If not, and a member somehow reaches /cook/[recipeId], the page will render but the "Mark as cooked" action must still fail at the API level with 403.

No navigation chrome = CSRF surface consideration:

  • B4 removes all navigation UI intentionally. This means there's no CSRF token embedded in a standard form in the usual way. Confirm that the POST /api/cooking-log action uses the SvelteKit CSRF protection mechanism (either form action with the built-in token, or a custom header like X-Requested-With for JSON endpoints). The absence of a nav doesn't remove the CSRF surface.

Wake lock API:

  • navigator.wakeLock is a browser API that requires the page to be focused and visible. It does not have security implications for our app beyond needing to handle errors gracefully (which Kai is already planning). No concerns here from a security perspective.

Input validation:

  • The planSlotId or recipeId sent to POST /api/cooking-log must be validated as a valid UUID format before any database query. Malformed UUIDs sent by a manipulated client should return 400 (bad request), not a 500 from the database.
  • The cookedOn date (if client-provided): should this come from the client at all? I'd prefer the backend derive the date from the server clock at the time of the request. A client sending cookedOn: "1970-01-01" to backdate a cooking log entry could manipulate the variety algorithm. Server-side timestamp is safer.

Information leakage on error:

  • If "Mark as cooked" fails (network error, 403, 500), the error message shown to the user should be generic ("Something went wrong — try again") and not expose internal details.

One open item: The confirmation dialog on exit — is it a native window.confirm() or a custom modal? If it's a custom modal rendered in-page, confirm it has role="dialog", aria-modal="true", and focus is trapped inside it. From a security standpoint, a custom modal dismissible by pressing Escape is fine, but focus must not leak to the background.

## 🔒 Sable — Security Engineer B4 is simpler than C1 from a security standpoint, but the "Mark as cooked" write and the full-screen, navigation-free layout create a few specific concerns worth addressing explicitly. **Authorization on the write endpoint:** - `POST /api/cooking-log` must validate that the authenticated user belongs to the same household as the referenced plan slot or recipe. This is not optional — it's a direct write to the `cooking_log` table, and if the `planSlotId` is not validated against the session's household, a user could log cooking events for another household's recipes. - The endpoint must require an active session (401 if missing) and planner role (403 if member). Members should not be able to log cooking events. - Confirm: is there a route guard in `hooks.server.ts` that prevents members from navigating to B4 at all? If not, and a member somehow reaches `/cook/[recipeId]`, the page will render but the "Mark as cooked" action must still fail at the API level with 403. **No navigation chrome = CSRF surface consideration:** - B4 removes all navigation UI intentionally. This means there's no CSRF token embedded in a standard form in the usual way. Confirm that the `POST /api/cooking-log` action uses the SvelteKit CSRF protection mechanism (either form action with the built-in token, or a custom header like `X-Requested-With` for JSON endpoints). The absence of a nav doesn't remove the CSRF surface. **Wake lock API:** - `navigator.wakeLock` is a browser API that requires the page to be focused and visible. It does not have security implications for our app beyond needing to handle errors gracefully (which Kai is already planning). No concerns here from a security perspective. **Input validation:** - The `planSlotId` or `recipeId` sent to `POST /api/cooking-log` must be validated as a valid UUID format before any database query. Malformed UUIDs sent by a manipulated client should return 400 (bad request), not a 500 from the database. - The `cookedOn` date (if client-provided): should this come from the client at all? I'd prefer the backend derive the date from the server clock at the time of the request. A client sending `cookedOn: "1970-01-01"` to backdate a cooking log entry could manipulate the variety algorithm. Server-side timestamp is safer. **Information leakage on error:** - If "Mark as cooked" fails (network error, 403, 500), the error message shown to the user should be generic ("Something went wrong — try again") and not expose internal details. **One open item:** The confirmation dialog on exit — is it a native `window.confirm()` or a custom modal? If it's a custom modal rendered in-page, confirm it has `role="dialog"`, `aria-modal="true"`, and focus is trapped inside it. From a security standpoint, a custom modal dismissible by pressing `Escape` is fine, but focus must not leak to the background.
Author
Owner

🎨 Atlas — UI/UX Designer

B4 is the screen I'm most protective of. The design constraints are not aesthetic choices — they are functional requirements for a kitchen environment. Here's what I'm watching for.

The non-negotiables (I will flag any deviation):

  • Step text: 16px, line-height 1.75 — this is specified in the issue and the spec. 14px or 1.5 line-height is not acceptable for a user whose hands are wet and who is reading from 60cm away. Kai should not use text-sm (14px) here — text-base (16px) only.
  • Max text width: 260px mobile / 320px tablet / 400px desktop. This prevents the line length from becoming unreadable at large viewport widths. Must be enforced with max-w-[260px] (or equivalent token) on the step text container.
  • Step number: Fraunces 56px mobile / 72px desktop, weight 300, --green-light color. This large number is the primary visual anchor — it tells the user at a glance where they are without reading. Don't reduce the size.

Tap target:

  • The entire body is the tap target, per spec. On mobile this means the full viewport minus the topbar. The tap hint text ("Tap anywhere to continue") should use DM Sans, muted color, smaller size (13px or 12px). It should not compete visually with the step text.
  • On desktop, the cursor should be cursor-pointer over the body area to communicate that clicking advances the step.

Exit button:

  • The spec says "error red, left." I assume this is --color-error. Confirm the exact hex/token. The exit button should be visually distinct and immediately findable — but not so large that it gets accidentally tapped while reading. A 40×40px minimum tap target with the icon centered is appropriate.

Progress bar:

  • 4px height, --green fill. Very thin — intentional. It's ambient feedback, not primary UI. Placed at the very top below the topbar header. Confirm it spans the full viewport width (not padded).

Final step state:

  • When the user reaches the final step, "Mark as cooked" appears. What's the design? Is it a full-width primary button below the step text? Does the tap-anywhere behavior stop when the final step is reached, requiring an explicit button tap? I'd strongly recommend the explicit button — accidental logging in a multi-meal week would silently corrupt the variety score.
  • The "Done" state after marking as cooked: is there a brief success moment (green flash, checkmark) before the redirect to C1, or is it an immediate redirect? A 500ms success micro-animation would feel more satisfying and confirm the action completed.

Breakpoint behavior:

  • The spec explicitly states this is the only screen with identical layout across all breakpoints. That means no sidebar, no tab bar, no bottom nav on any breakpoint. Confirm with Kai that the global navigation component (hooks or layout) is fully suppressed on B4's route. This likely requires a different +layout.svelte for the cook mode route tree.
## 🎨 Atlas — UI/UX Designer B4 is the screen I'm most protective of. The design constraints are not aesthetic choices — they are functional requirements for a kitchen environment. Here's what I'm watching for. **The non-negotiables (I will flag any deviation):** - Step text: **16px, line-height 1.75** — this is specified in the issue and the spec. 14px or 1.5 line-height is not acceptable for a user whose hands are wet and who is reading from 60cm away. Kai should not use `text-sm` (14px) here — `text-base` (16px) only. - Max text width: 260px mobile / 320px tablet / 400px desktop. This prevents the line length from becoming unreadable at large viewport widths. Must be enforced with `max-w-[260px]` (or equivalent token) on the step text container. - Step number: Fraunces 56px mobile / 72px desktop, weight 300, `--green-light` color. This large number is the primary visual anchor — it tells the user at a glance where they are without reading. Don't reduce the size. **Tap target:** - The entire body is the tap target, per spec. On mobile this means the full viewport minus the topbar. The tap hint text ("Tap anywhere to continue") should use DM Sans, muted color, smaller size (13px or 12px). It should not compete visually with the step text. - On desktop, the cursor should be `cursor-pointer` over the body area to communicate that clicking advances the step. **Exit button:** - The spec says "error red, left." I assume this is `--color-error`. Confirm the exact hex/token. The exit button should be visually distinct and immediately findable — but not so large that it gets accidentally tapped while reading. A 40×40px minimum tap target with the icon centered is appropriate. **Progress bar:** - 4px height, `--green` fill. Very thin — intentional. It's ambient feedback, not primary UI. Placed at the very top below the topbar header. Confirm it spans the full viewport width (not padded). **Final step state:** - When the user reaches the final step, "Mark as cooked" appears. What's the design? Is it a full-width primary button below the step text? Does the tap-anywhere behavior stop when the final step is reached, requiring an explicit button tap? I'd strongly recommend the explicit button — accidental logging in a multi-meal week would silently corrupt the variety score. - The "Done" state after marking as cooked: is there a brief success moment (green flash, checkmark) before the redirect to C1, or is it an immediate redirect? A 500ms success micro-animation would feel more satisfying and confirm the action completed. **Breakpoint behavior:** - The spec explicitly states this is the only screen with identical layout across all breakpoints. That means no sidebar, no tab bar, no bottom nav on any breakpoint. Confirm with Kai that the global navigation component (hooks or layout) is fully suppressed on B4's route. This likely requires a different `+layout.svelte` for the cook mode route tree.
Sign in to join this conversation.