Repository navigation
web: close the compare-prompt cluster (#80, #81, #82) - #84
Merged
Merged
Conversation
- #81: elementFromPoint probe in rank-surfaces.spec.ts proves the last dish row's Log button, not the pointer-events-none wrapper, receives the click at desktop viewport -- the trial-click gap PR #78's review found. Replaced onboarding.spec.ts's retired page.route("**/api/**") residency guard with the page.on("request") pattern. - #82: bottom-anchored spacer (sm:hidden, sized to the toast/prompt stack's own bind:clientHeight + the wrapper's bottom-5 offset) keeps the last dish row's Log button reachable while the prompt is up at narrow viewports, without touching desktop geometry or adding a new component. - #80: chooseCompare() now fire-and-forget syncs favorite dining halls via syncDiningHallRanks when signed in, mirroring /rank's choose() -- supersedes #67's stricter zero-server-calls constraint for this surface now that #67 is closed. New e2e coverage asserts the signed-in sync fires and, as a real residency guard (page.on("request"), not route-glob counting), that a signed-out compare makes no Supabase/cross-origin request. All three fixes are red-first: each new/changed spec was run against the pre-fix code and confirmed failing before the corresponding implementation change, then confirmed passing after.
This was referenced Aug 19, 2026
…g on #84); document the toast-shrink transient
This was referenced Aug 19, 2026
Closed
TSVRN9
added a commit
that referenced
this pull request
Aug 19, 2026
…sheet, nutrition label (#91) Rebuilds halls/[slug] per the v2 canvas (wave 3 of epic #87): - shared: parseCategoryItems now also captures the data-*-dv attributes (additive optional fields on NutritionFacts) — including the data-cholesterol_dv underscore quirk the real feed uses. web is unaffected: no existing NutritionFacts constructor sets the new fields, and no web spec asserts exact nutrition-object shape. - shared: openFoodFacts.ts gains searchProducts (cgi/search.pl, fields= slimmed) for the sheet's "Add something else" row — the issue named "shared's existing openFoodFacts client" as SEARCH-capable, but only lookupBarcode existed; extracted the nutriments→NutritionFacts mapper so both share the same per-serving/100g-fallback logic. Barcode scanning stays out of scope (native dep). - mobile/src/lib/plate.ts: pure in-memory plate math (add/increment, stepper set, totals via shared's computeDailyTotals, one LogEntry per plate row with servings = that row's count — matches how today.tsx already displays "Dish × N" and how rank.tsx dedupes its comparison pool by dish key). - mobile/src/lib/nutritionLabel.ts: FDA-label row order/formatting (DV% column, blank vs. no-DV-column distinction). - mobile/src/components/{NutritionLabel,PlateBar,PlateSheet}.tsx: RN Modals (not routed screens — MenuItem doesn't need to survive router search-param serialization, and neither needs a back-stack entry), each with a render smoke test. - mobile/src/app/halls/[slug].tsx: dish rows get a +/stepper and an info affordance; bottom bar appears once the plate is non-empty and opens the expanded sheet; the dish list's bottom padding tracks the bar's measured height (occlusion-bug class from PR #78/#84) and collapses to 0 once the plate empties, even with a stale measured height still in state. _layout.tsx untouched — no new routes. Closes #91
TSVRN9
added a commit
that referenced
this pull request
Aug 20, 2026
…sion, %DV absent/blank, OFF estimate marker Addresses all four REQUEST-CHANGES findings from the PR #106 review: 1. mobile/src/lib/hallMenu.test.tsx: red-first coverage for halls/[slug].tsx's plate wiring, which previously had zero tests. Individually confirmed each of the reviewer's three simultaneous mutations now fails at least one test: (a) PlateBar mounted unconditionally, (b) setPlate([]) deleted from logPlate, (c) the occlusion padding call replaced with a constant 0. Also covers: LOG writes one addEntry per plate row with servings = that row's count; the plate is retained (not cleared) on a partial addEntry failure; the SectionList's contentContainer paddingBottom tracks the bar's measured height. 2. halls/[slug].tsx: the LOG-failure banner is now anchored above the plate bar's measured height (same listBottomPadding reuse as the list's own padding), instead of sitting in-flow underneath the still-mounted, opaque bar -- the same occlusion-bug class as PR #78/#84, this time on the failure path the previous round added without a layout fix. 3. shared/src/umassDining.ts: getAttr previously returned "" for both an absent -dv attribute and a present-but-blank one, so dv() collapsed both to null -- meaning a mutated/misspelled attribute name (e.g. the cholesterol_dv underscore -> hyphen) was indistinguishable from a legitimately blank one. getAttrRaw now distinguishes absent (undefined) from present-but-blank (""), implementing the undefined/null contract NutritionFacts's *Dv fields already documented. Existing lookupBarcode output is untouched (dv() is only used by the new *Dv fields). 4. shared/src/openFoodFacts.ts's searchProducts docstring claimed a "marks servingSize as an estimate" mitigation that no UI actually read. Added mobile/src/lib/plate.ts's isEstimatedServing (reads the "per 100g" marker) and wired it into PlateSheet's search-result row and plate-item row, so a per-100g fallback shows "est. per 100g" instead of silently presenting 100g numbers as one serving. Docstring updated to point at the actual consumer. Verification: shared node --test 117/117 (was 116 -- new absent/blank %DV test), mobile tsc --noEmit clean, mobile jest 20 suites / 111 tests (was 15/91 -- new hallMenu.test.tsx, isEstimatedServing cases, and the estimate-marker PlateSheet cases). All four findings' new/changed tests individually mutation-verified (implementation reverted or a targeted mutation applied, confirmed red, restored, confirmed green).
TSVRN9
added a commit
that referenced
this pull request
Aug 20, 2026
…sheet, nutrition label (#91) Rebuilds halls/[slug] per the v2 canvas (wave 3 of epic #87): - shared: parseCategoryItems now also captures the data-*-dv attributes (additive optional fields on NutritionFacts) — including the data-cholesterol_dv underscore quirk the real feed uses. web is unaffected: no existing NutritionFacts constructor sets the new fields, and no web spec asserts exact nutrition-object shape. - shared: openFoodFacts.ts gains searchProducts (cgi/search.pl, fields= slimmed) for the sheet's "Add something else" row — the issue named "shared's existing openFoodFacts client" as SEARCH-capable, but only lookupBarcode existed; extracted the nutriments→NutritionFacts mapper so both share the same per-serving/100g-fallback logic. Barcode scanning stays out of scope (native dep). - mobile/src/lib/plate.ts: pure in-memory plate math (add/increment, stepper set, totals via shared's computeDailyTotals, one LogEntry per plate row with servings = that row's count — matches how today.tsx already displays "Dish × N" and how rank.tsx dedupes its comparison pool by dish key). - mobile/src/lib/nutritionLabel.ts: FDA-label row order/formatting (DV% column, blank vs. no-DV-column distinction). - mobile/src/components/{NutritionLabel,PlateBar,PlateSheet}.tsx: RN Modals (not routed screens — MenuItem doesn't need to survive router search-param serialization, and neither needs a back-stack entry), each with a render smoke test. - mobile/src/app/halls/[slug].tsx: dish rows get a +/stepper and an info affordance; bottom bar appears once the plate is non-empty and opens the expanded sheet; the dish list's bottom padding tracks the bar's measured height (occlusion-bug class from PR #78/#84) and collapses to 0 once the plate empties, even with a stale measured height still in state. _layout.tsx untouched — no new routes. Closes #91
TSVRN9
added a commit
that referenced
this pull request
Aug 20, 2026
…sion, %DV absent/blank, OFF estimate marker Addresses all four REQUEST-CHANGES findings from the PR #106 review: 1. mobile/src/lib/hallMenu.test.tsx: red-first coverage for halls/[slug].tsx's plate wiring, which previously had zero tests. Individually confirmed each of the reviewer's three simultaneous mutations now fails at least one test: (a) PlateBar mounted unconditionally, (b) setPlate([]) deleted from logPlate, (c) the occlusion padding call replaced with a constant 0. Also covers: LOG writes one addEntry per plate row with servings = that row's count; the plate is retained (not cleared) on a partial addEntry failure; the SectionList's contentContainer paddingBottom tracks the bar's measured height. 2. halls/[slug].tsx: the LOG-failure banner is now anchored above the plate bar's measured height (same listBottomPadding reuse as the list's own padding), instead of sitting in-flow underneath the still-mounted, opaque bar -- the same occlusion-bug class as PR #78/#84, this time on the failure path the previous round added without a layout fix. 3. shared/src/umassDining.ts: getAttr previously returned "" for both an absent -dv attribute and a present-but-blank one, so dv() collapsed both to null -- meaning a mutated/misspelled attribute name (e.g. the cholesterol_dv underscore -> hyphen) was indistinguishable from a legitimately blank one. getAttrRaw now distinguishes absent (undefined) from present-but-blank (""), implementing the undefined/null contract NutritionFacts's *Dv fields already documented. Existing lookupBarcode output is untouched (dv() is only used by the new *Dv fields). 4. shared/src/openFoodFacts.ts's searchProducts docstring claimed a "marks servingSize as an estimate" mitigation that no UI actually read. Added mobile/src/lib/plate.ts's isEstimatedServing (reads the "per 100g" marker) and wired it into PlateSheet's search-result row and plate-item row, so a per-100g fallback shows "est. per 100g" instead of silently presenting 100g numbers as one serving. Docstring updated to point at the actual consumer. Verification: shared node --test 117/117 (was 116 -- new absent/blank %DV test), mobile tsc --noEmit clean, mobile jest 20 suites / 111 tests (was 15/91 -- new hallMenu.test.tsx, isEstimatedServing cases, and the estimate-marker PlateSheet cases). All four findings' new/changed tests individually mutation-verified (implementation reverted or a targeted mutation applied, confirmed red, restored, confirmed green).
TSVRN9
added a commit
that referenced
this pull request
Aug 20, 2026
…sheet, nutrition label (#91) Rebuilds halls/[slug] per the v2 canvas (wave 3 of epic #87): - shared: parseCategoryItems now also captures the data-*-dv attributes (additive optional fields on NutritionFacts) — including the data-cholesterol_dv underscore quirk the real feed uses. web is unaffected: no existing NutritionFacts constructor sets the new fields, and no web spec asserts exact nutrition-object shape. - shared: openFoodFacts.ts gains searchProducts (cgi/search.pl, fields= slimmed) for the sheet's "Add something else" row — the issue named "shared's existing openFoodFacts client" as SEARCH-capable, but only lookupBarcode existed; extracted the nutriments→NutritionFacts mapper so both share the same per-serving/100g-fallback logic. Barcode scanning stays out of scope (native dep). - mobile/src/lib/plate.ts: pure in-memory plate math (add/increment, stepper set, totals via shared's computeDailyTotals, one LogEntry per plate row with servings = that row's count — matches how today.tsx already displays "Dish × N" and how rank.tsx dedupes its comparison pool by dish key). - mobile/src/lib/nutritionLabel.ts: FDA-label row order/formatting (DV% column, blank vs. no-DV-column distinction). - mobile/src/components/{NutritionLabel,PlateBar,PlateSheet}.tsx: RN Modals (not routed screens — MenuItem doesn't need to survive router search-param serialization, and neither needs a back-stack entry), each with a render smoke test. - mobile/src/app/halls/[slug].tsx: dish rows get a +/stepper and an info affordance; bottom bar appears once the plate is non-empty and opens the expanded sheet; the dish list's bottom padding tracks the bar's measured height (occlusion-bug class from PR #78/#84) and collapses to 0 once the plate empties, even with a stale measured height still in state. _layout.tsx untouched — no new routes. Closes #91
TSVRN9
added a commit
that referenced
this pull request
Aug 20, 2026
…sion, %DV absent/blank, OFF estimate marker Addresses all four REQUEST-CHANGES findings from the PR #106 review: 1. mobile/src/lib/hallMenu.test.tsx: red-first coverage for halls/[slug].tsx's plate wiring, which previously had zero tests. Individually confirmed each of the reviewer's three simultaneous mutations now fails at least one test: (a) PlateBar mounted unconditionally, (b) setPlate([]) deleted from logPlate, (c) the occlusion padding call replaced with a constant 0. Also covers: LOG writes one addEntry per plate row with servings = that row's count; the plate is retained (not cleared) on a partial addEntry failure; the SectionList's contentContainer paddingBottom tracks the bar's measured height. 2. halls/[slug].tsx: the LOG-failure banner is now anchored above the plate bar's measured height (same listBottomPadding reuse as the list's own padding), instead of sitting in-flow underneath the still-mounted, opaque bar -- the same occlusion-bug class as PR #78/#84, this time on the failure path the previous round added without a layout fix. 3. shared/src/umassDining.ts: getAttr previously returned "" for both an absent -dv attribute and a present-but-blank one, so dv() collapsed both to null -- meaning a mutated/misspelled attribute name (e.g. the cholesterol_dv underscore -> hyphen) was indistinguishable from a legitimately blank one. getAttrRaw now distinguishes absent (undefined) from present-but-blank (""), implementing the undefined/null contract NutritionFacts's *Dv fields already documented. Existing lookupBarcode output is untouched (dv() is only used by the new *Dv fields). 4. shared/src/openFoodFacts.ts's searchProducts docstring claimed a "marks servingSize as an estimate" mitigation that no UI actually read. Added mobile/src/lib/plate.ts's isEstimatedServing (reads the "per 100g" marker) and wired it into PlateSheet's search-result row and plate-item row, so a per-100g fallback shows "est. per 100g" instead of silently presenting 100g numbers as one serving. Docstring updated to point at the actual consumer. Verification: shared node --test 117/117 (was 116 -- new absent/blank %DV test), mobile tsc --noEmit clean, mobile jest 20 suites / 111 tests (was 15/91 -- new hallMenu.test.tsx, isEstimatedServing cases, and the estimate-marker PlateSheet cases). All four findings' new/changed tests individually mutation-verified (implementation reverted or a targeted mutation applied, confirmed red, restored, confirmed green).
TSVRN9
added a commit
that referenced
this pull request
Aug 20, 2026
… OFF search (#91) (#112) * mobile: hall menu v2 — plate selection, bottom summary bar, expanded sheet, nutrition label (#91) Rebuilds halls/[slug] per the v2 canvas (wave 3 of epic #87): - shared: parseCategoryItems now also captures the data-*-dv attributes (additive optional fields on NutritionFacts) — including the data-cholesterol_dv underscore quirk the real feed uses. web is unaffected: no existing NutritionFacts constructor sets the new fields, and no web spec asserts exact nutrition-object shape. - shared: openFoodFacts.ts gains searchProducts (cgi/search.pl, fields= slimmed) for the sheet's "Add something else" row — the issue named "shared's existing openFoodFacts client" as SEARCH-capable, but only lookupBarcode existed; extracted the nutriments→NutritionFacts mapper so both share the same per-serving/100g-fallback logic. Barcode scanning stays out of scope (native dep). - mobile/src/lib/plate.ts: pure in-memory plate math (add/increment, stepper set, totals via shared's computeDailyTotals, one LogEntry per plate row with servings = that row's count — matches how today.tsx already displays "Dish × N" and how rank.tsx dedupes its comparison pool by dish key). - mobile/src/lib/nutritionLabel.ts: FDA-label row order/formatting (DV% column, blank vs. no-DV-column distinction). - mobile/src/components/{NutritionLabel,PlateBar,PlateSheet}.tsx: RN Modals (not routed screens — MenuItem doesn't need to survive router search-param serialization, and neither needs a back-stack entry), each with a render smoke test. - mobile/src/app/halls/[slug].tsx: dish rows get a +/stepper and an info affordance; bottom bar appears once the plate is non-empty and opens the expanded sheet; the dish list's bottom padding tracks the bar's measured height (occlusion-bug class from PR #78/#84) and collapses to 0 once the plate empties, even with a stale measured height still in state. _layout.tsx untouched — no new routes. Closes #91 * mobile: don't clear the plate or lose data on a partial logPlate failure (#91 review pass) logPlate looped storage.addEntry across the plate's entries with no error handling; a failure partway through would still clear the plate and report success. Now catches, leaves the plate/sheet untouched, and surfaces the failure via the existing banner -- with a ponytail note on the known retry-duplicates-committed-rows ceiling (toLogEntries mints fresh ids per call, so a retry doesn't replace what already committed). * mobile: PR #106 review fixes — hallMenu wiring coverage, banner occlusion, %DV absent/blank, OFF estimate marker Addresses all four REQUEST-CHANGES findings from the PR #106 review: 1. mobile/src/lib/hallMenu.test.tsx: red-first coverage for halls/[slug].tsx's plate wiring, which previously had zero tests. Individually confirmed each of the reviewer's three simultaneous mutations now fails at least one test: (a) PlateBar mounted unconditionally, (b) setPlate([]) deleted from logPlate, (c) the occlusion padding call replaced with a constant 0. Also covers: LOG writes one addEntry per plate row with servings = that row's count; the plate is retained (not cleared) on a partial addEntry failure; the SectionList's contentContainer paddingBottom tracks the bar's measured height. 2. halls/[slug].tsx: the LOG-failure banner is now anchored above the plate bar's measured height (same listBottomPadding reuse as the list's own padding), instead of sitting in-flow underneath the still-mounted, opaque bar -- the same occlusion-bug class as PR #78/#84, this time on the failure path the previous round added without a layout fix. 3. shared/src/umassDining.ts: getAttr previously returned "" for both an absent -dv attribute and a present-but-blank one, so dv() collapsed both to null -- meaning a mutated/misspelled attribute name (e.g. the cholesterol_dv underscore -> hyphen) was indistinguishable from a legitimately blank one. getAttrRaw now distinguishes absent (undefined) from present-but-blank (""), implementing the undefined/null contract NutritionFacts's *Dv fields already documented. Existing lookupBarcode output is untouched (dv() is only used by the new *Dv fields). 4. shared/src/openFoodFacts.ts's searchProducts docstring claimed a "marks servingSize as an estimate" mitigation that no UI actually read. Added mobile/src/lib/plate.ts's isEstimatedServing (reads the "per 100g" marker) and wired it into PlateSheet's search-result row and plate-item row, so a per-100g fallback shows "est. per 100g" instead of silently presenting 100g numbers as one serving. Docstring updated to point at the actual consumer. Verification: shared node --test 117/117 (was 116 -- new absent/blank %DV test), mobile tsc --noEmit clean, mobile jest 20 suites / 111 tests (was 15/91 -- new hallMenu.test.tsx, isEstimatedServing cases, and the estimate-marker PlateSheet cases). All four findings' new/changed tests individually mutation-verified (implementation reverted or a targeted mutation applied, confirmed red, restored, confirmed green). * mobile: auto-dismiss the logged banner and make its occlusion clearance banner-aware Device-pass finding on mobile/plate-logging @ e4ee0d5: after LOG succeeds the plate empties, so listBottomPadding(barHeight, false) = 0 for both the list padding and the banner anchor, and `logged` was never cleared -- the success banner permanently covered the last menu row until the plate was repopulated, and its text was clipped by the gesture-nav edge (no safe-area inset). - Auto-dismiss: a useEffect keyed on `logged` clears it via setTimeout after a few seconds (4s), cleaning up on unmount/re-trigger. Retires the pre-existing "banner never dismisses" nit from the #106 review too. - Occlusion: the SectionList's bottom padding now adds the banner's own measured height (via onLayout, same pattern as the bar) on top of the existing bar-aware padding, only while `logged` is set. The banner itself now also pads for the bottom safe-area inset (useSafeAreaInsets, same as PlateBar) so it isn't clipped by gesture nav when there's no bar underneath it to already clear that space. Both fixes are red-first in hallMenu.test.tsx: a fake-timers test proves the banner is gone after the timeout (fails without the effect), and two padding tests prove the list's paddingBottom includes the banner's measured height alone and stacked on top of the bar's clearance (fails if the padding sum is reverted to bar-only). All three individually mutation-verified (mutated the fix away, confirmed red, restored). Also promoted jest.useFakeTimers()/useRealTimers() to a file-wide beforeEach/afterEach: logPlate's success and failure paths both now schedule a real setTimeout, and no test in this file ever unmounts its renderer, so a real timer landing ~4s after a test finished (well past teardown) was crashing the whole suite ("window.dispatchEvent is not a function") instead of just failing one test -- caught by running the full mobile jest suite, not just this file in isolation. mobile jest: 20 suites / 114 tests (was 111). mobile tsc --noEmit clean. shared untouched.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #80, Closes #81, Closes #82
Three related findings from PR #78's review, all on
halls/[slug]andrank-surfaces.spec.ts, fixed on one branch since they share a verification loop.#81 -- pointer-events wrapper untested (elementFromPoint gap)
The
{ trial: true }actionability checks already inrank-surfaces.spec.tsscroll their target into view first, so they can't tell "reachable after scrolling" from "blocked by an overlay right where it already was" -- PR #78's review found that gap: removing the compare-prompt stack wrapper'spointer-events-noneclass breaks click-through in reality, but every existing spec in the file stays green regardless.BUTTON, with a precondition that the probe point actually falls inside the stack wrapper's band (so a future layout change can't silently turn this into a no-op).pointer-events-nonefrom the wrapper -> this new spec failed (topmost element was the wrapperDIV, not the button) -> restored -> green. Confirmed under the mutation that the other 10 specs in the file stay green (matching the review's finding), and re-confirmed the mutation still goes red after web: compare prompt occludes the last dish row's Log button at narrow viewports #82's fix landed (see below), so web: compare prompt occludes the last dish row's Log button at narrow viewports #82 didn't silently neutralize this probe.onboarding.spec.ts:51's retiredpage.route("**/api/**")residency-guard form with thepage.on("request")cross-origin+/api/pattern fromhome-dashboard.spec.ts. Mutation-verified: temporarily injected ano-corscross-originfetchviapage.addInitScript, confirmed the new guard catches it, reverted.#82 -- narrow-viewport occlusion
At 375x667, the bottom-anchored prompt (
min(92vw, 28rem)wide) covers the last dish row's Log button until dismissed.aria-hidden,sm:hiddenspacer just before the fixed stack, sized to the stack's own rendered height (bind:clientHeight, reactive, no manual effect) plus the wrapper'sbottom-5offset. Only applies below Tailwind'ssm(640px) breakpoint, so desktop geometry -- and web e2e: pointer-events wrapper on the compare prompt is untested (elementFromPoint gap) #81's desktop probe -- is unaffected. No new component.ponytail:comment: removed the now-fixed narrow-viewport ceiling, added the residual one (a user already scrolled to the exact bottom when the prompt appears doesn't get the button moved to them, due to scroll anchoring -- the sanctioned "bottom padding equal to stack height" tradeoff, not a regression versus pre-fix where nothing helped).#80 -- rank sync drift
chooseCompare()onhalls/[slug]updated both Elo tracks but never calledsyncDiningHallRanks, per #67's original zero-server-calls constraint for that surface. #67 is now closed, and per the orchestrator's decision this is superseded:chooseCompare()now fire-and-forget syncs favorite dining halls exactly like/rank'schoose()does, gated on a session existing -- sanctioned by CLAUDE.md's data residency table (favorite dining halls are the one ranking-derived, hall-level thing the server may see, signed-in only).friends-notifications.spec.ts.rest/v1/favorite_dining_halls, logs two same-hall dishes, chooses a winner, asserts both the DELETE and the POST (insert) fire -- both fixture dishes are Hampshire, so a single comparison meetsMIN_RATED_DISHES_PER_HALL, proving a rank actually got pushed, not just a no-op call.page.on("request")style (not route-glob counting) -- asserts zero Supabase/cross-origin requests during the same signed-out flow.chooseCompare(): signed-in spec failed (no sync call at all) as expected; the signed-out guard stayed green identically before and after, so it was separately mutation-verified by temporarily dropping thesession &&guard inchooseCompare()-- confirmed it turns red (a real cross-origin request lands) before reverting.Verification (all local, per CLAUDE.md's "CI is local-only")
CI=1 PORT=4317 npx playwright test(full suite) -- 70 passedpnpm --filter web check-- 0 errors, 0 warningspnpm --filter web build-- succeededshared/untouched, sopnpm --filter @udine/shared testwasn't required and wasn't run.One pre-existing flake observed and disclosed, not introduced by this branch: under a full-suite parallel (4-worker) run with an unrelated mutation applied,
rank-surfaces.spec.ts's "home's Your Top Dishes module..." spec failed once on an element-not-found timeout. It passed 3/3 in isolation and passed in every other full-suite run in this branch's history (including the final one above). Retries are 0 by this repo's own config (playwright.config.ts), so nothing laundered a real failure -- this reads as parallel-worker contention, not a regression from this diff, but flagging it since a re-run under load could surface it again.Per-issue evidence with the same red/green detail posted as comments on #80, #81, #82.
Not merging -- for review.