fix(cooking-session): premier passage responsive dédié à l'écran de cuisine (#89)
Some checks failed
CI / build (push) Successful in 2m35s
CI / lint (push) Successful in 2m38s
CI / intent-service-test (push) Successful in 5m8s
CI / e2e (push) Successful in 9m17s
CI / test (push) Failing after 16m14s
CI / build-android (push) Has been skipped

cooking-session-page.scss n'avait encore aucune règle @media. Audit
dédié à cet écran (minuteur inexistant sur cette page — rien à faire
de ce côté, liste d'étapes, actions) :

- Bug d'overflow réel dans l'onglet Recettes : `.cooking-recipes__name`
  n'avait aucune borne dans sa ligne flex (contrairement au
  `.recipe-chip__name` équivalent côté planning) — un nom de recette
  long pouvait repousser le badge de portions et le chevron hors de la
  case (silencieusement coupés par le `overflow: hidden` de la carte
  parente). Corrigé avec le même traitement flex:1/min-width:0/
  ellipsis que son équivalent.

- Cibles tactiles sous le breakpoint phone : les cartes de tâches (`.
  cooking-task`, l'action principale et répétée de tout l'onglet Plan),
  les nœuds du stepper, les onglets, la ligne de l'onglet Recettes et
  les boutons Précédent/Suivant passent tous à --space-md pour dépasser
  confortablement les 48dp minimums d'Android (l'app est Android-only)
  même dans le pire cas (une tâche à une seule ligne, sans ustensiles).

- Piège rencontré : surcharger `.cooking-task` par un simple sélecteur
  de classe ne fonctionnait pas silencieusement — la règle globale
  partagée `label:has(> input[type="checkbox"])` (global.scss) a une
  spécificité CSS plus élevée qu'il n'y paraît (l'argument de :has()
  compte comme un élément + un attribut). Corrigé en réutilisant le
  même niveau d'imbrication que `.cooking-phase__tasks .cooking-task
  --done` juste au-dessus dans le même fichier, qui contournait déjà
  ce problème pour la même raison.

Couverture Cypress : nouveau describe "Cooking session page (phone)"
dans cooking-session-page.cy.ts (2 tests — padding effectivement
appliqué + troncature du nom de recette sans pousser le badge hors
champ). Suite complète : biome check, tsc -b --force, build, 146/146
e2e, 71/71 composants — tous verts.

Closes #89

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Nicolas 2026-09-07 16:49:08 +02:00
parent 23bc19b482
commit 2b5ae6722a
2 changed files with 143 additions and 0 deletions

View file

@ -3,6 +3,8 @@
// `GET /cooking-session` behavior (including the optimizer) against a real
// database.
import { PHONE_VIEWPORT } from "../support/viewports";
const authenticatedProfile = {
id: 1,
firstName: "Alice",
@ -421,3 +423,72 @@ describe("Cooking session page", () => {
cy.contains("Impossible de charger").should("be.visible");
});
});
// Ticket #89 — this page's first dedicated responsive pass (it previously
// had no `@media` rule at all, see cooking-session-page.scss's own header
// comment). No layout is restructured on phone (the flex-column shell and
// the two horizontally-scrolling rows already degrade safely) — this covers
// the two things that actually needed fixing: comfortable touch targets on
// the screen's own controls, and a real flex overflow bug in the Recettes
// tab (a long recipe name had no upper bound and could crowd its portions
// badge/chevron out of the row).
describe("Cooking session page (phone)", () => {
beforeEach(() => {
cy.setViewport("phone");
cy.clock(TODAY, ["Date"]);
cy.intercept("GET", "**/auth/me", { statusCode: 200, body: authenticatedProfile });
});
it("gives the task cards and stepper nodes a taller, more tappable padding than on desktop", () => {
cy.intercept("GET", /\/cooking-session\?/, { statusCode: 200, body: planFixture() });
cy.visit("/cuisiner?date=2026-08-17");
cy.get(".cooking-task")
.first()
.should(($el) => {
// --space-md (16px) on phone vs. the shared checkbox-label rule's
// own --space-xs (4px) app-wide default — see cooking-session-page
// .scss's phone block.
expect(getComputedStyle($el[0]).paddingTop).to.equal("16px");
});
cy.get(".cooking-stepper__node")
.first()
.should(($el) => {
expect(getComputedStyle($el[0]).paddingTop).to.equal("16px");
});
// The document itself never scrolls sideways, same global rule every
// other page is held to.
cy.document()
.its("documentElement.scrollWidth")
.should("be.lte", PHONE_VIEWPORT.width + 1);
});
it("truncates a long recipe name in the Recettes tab instead of pushing its portions badge out of view", () => {
const longName =
"Un très long nom de recette qui ne devrait surtout pas pousser le badge de portions hors de l'écran";
cy.intercept("GET", /\/cooking-session\?/, {
statusCode: 200,
body: {
startDate: "2026-08-17T00:00:00.000Z",
finishDate: "2026-08-23T00:00:00.000Z",
recipes: [{ recipeId: 1, name: longName, portions: 4 }],
phases: [],
},
});
cy.visit("/cuisiner?date=2026-08-17");
cy.contains(".cooking-session-page__tab", "Recettes").click();
cy.get(".cooking-recipes__name").should(($el) => {
const s = getComputedStyle($el[0]);
expect(s.textOverflow).to.equal("ellipsis");
expect(s.whiteSpace).to.equal("nowrap");
// The name's own box is clipped narrower than its full text — proof
// the truncation is actually engaged, not just declared.
expect($el[0].scrollWidth).to.be.greaterThan($el[0].clientWidth);
});
// ...and the badge survives, still on screen, not clipped away by the
// card's own `overflow: hidden`.
cy.contains(".cooking-recipes__portions", "4 portions").should("be.visible");
});
});

View file

@ -13,6 +13,8 @@
// on the theme.
// =============================================================================
@use "../../styles/breakpoints" as bp;
.cooking-session-page {
height: 100%;
display: flex;
@ -134,12 +136,24 @@
cursor: pointer;
}
// `flex: 1; min-width: 0` + truncation — without it, a long recipe name
// has no upper bound in this flex row and can crowd the portions badge
// and chevron off to the side (or past the card's own `overflow: hidden`
// on `&__item`, silently clipping them) instead of the name itself
// truncating. Same fix as `.recipe-chip__name` (planning-page.scss) for
// the identical shape of bug.
&__name {
flex: 1;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-weight: 600;
color: var(--color-text);
}
&__portions {
flex-shrink: 0;
padding: 0.1rem var(--space-sm);
border-radius: var(--radius-pill);
background: var(--color-surface-alt);
@ -486,3 +500,61 @@
color: var(--color-text-muted);
}
}
// --- Phone (issue #89, first responsive pass) -------------------------------
// Nothing here reorganizes the layout — the flex-column shell and the two
// horizontally-scrolling rows (`&__tabs`, `.cooking-stepper`, both the same
// generic `overflow-x: auto` pattern used throughout the app) already
// degrade safely on a narrow screen, and every text block wraps within its
// own flex/block container (no fixed widths). What was actually missing:
// comfortable touch targets on this screen's own controls — the task cards
// especially, since ticking one off is THE primary, repeated, one-handed
// action of the whole "Plan" tab. Same audit `planning-page.scss` got in
// #88, applied here to this page's own elements.
@include bp.phone {
// The task card is a shared "selectable card" checkbox label
// (global.scss's `label:has(> input[type="checkbox"])`, padding
// `--space-xs --space-sm` app-wide — fine with a mouse, too tight to tap
// reliably mid-cooking). `--space-md` all around clears Android's 48dp
// minimum touch target (this app is Android-only, see the root README)
// even in the worst case — a one-line headline with no utensils and no
// "Mutualisé" badge (~25px of text): 25 + 2×16 = 57px. Bumped here, not
// in the global rule: this page's task cards are the one place site-wide
// where quick, repeated tapping actually matters. Scoped under
// `.cooking-phase__tasks` (not a bare `.cooking-task` selector) for the
// same specificity reason as `&__tasks .cooking-task--done` above: a
// plain single class loses to `label:has(> input[type="checkbox"])`'s
// own specificity (a `:has()` argument counts its inner element +
// attribute), so it silently never applied without this.
.cooking-phase__tasks .cooking-task {
padding: var(--space-md);
}
// Same reasoning for the stepper's own nodes: the enlarged 1.75rem
// (28px) marker below is this node's tallest element, so `--space-md`
// padding puts its total height at 28 + 2×16 = 60px, comfortably past
// 48dp rather than landing exactly on it.
.cooking-stepper__node {
padding: var(--space-md);
}
.cooking-stepper__marker {
width: 1.75rem;
height: 1.75rem;
}
// Tab bar, Recettes summary row, Prev/Next — same treatment, so every
// tappable control on this page ends up consistently comfortable rather
// than fixing only the one most obviously broken.
.cooking-session-page__tab {
padding: var(--space-md);
}
.cooking-recipes__row {
padding: var(--space-md);
}
.cooking-plan__nav-btn {
padding: var(--space-md) var(--space-lg);
}
}