Skip to content

web: close the compare-prompt cluster (#80, #81, #82) - #84

Merged
TSVRN9 merged 3 commits into
mainfrom
fix/compare-prompt-cluster
Aug 19, 2026
Merged

TSVRN9 merged 3 commits into
mainfrom
fix/compare-prompt-cluster

Conversation

@TSVRN9

@TSVRN9 TSVRN9 commented Aug 19, 2026

Copy link
Copy Markdown
Owner

Closes #80, Closes #81, Closes #82

Three related findings from PR #78's review, all on halls/[slug] and rank-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 in rank-surfaces.spec.ts scroll 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's pointer-events-none class breaks click-through in reality, but every existing spec in the file stays green regardless.

  • Added a desktop elementFromPoint probe: after logging a dish far down a long-menu fixture and scrolling to the document bottom, asserts the element at the last dish row's Log button center is the 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).
  • Red-first: removed pointer-events-none from the wrapper -> this new spec failed (topmost element was the wrapper DIV, 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.
  • Replaced onboarding.spec.ts:51's retired page.route("**/api/**") residency-guard form with the page.on("request") cross-origin+/api/ pattern from home-dashboard.spec.ts. Mutation-verified: temporarily injected a no-cors cross-origin fetch via page.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.

  • Minimal fix: an aria-hidden, sm:hidden spacer just before the fixed stack, sized to the stack's own rendered height (bind:clientHeight, reactive, no manual effect) plus the wrapper's bottom-5 offset. Only applies below Tailwind's sm (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.
  • Red-first: added a 375x667 elementFromPoint spec (same technique as web e2e: pointer-events wrapper on the compare prompt is untested (elementFromPoint gap) #81's, worst case = scrolled to the document bottom with the prompt up) -- ran it against the code without the spacer and got red (topmost element was the compare-prompt's own choice button, reproducing the review's measurement) -- added the spacer -- green.
  • Updated the file's 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() on halls/[slug] updated both Elo tracks but never called syncDiningHallRanks, 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's choose() 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).

  • Red-first: two new e2e specs using the mocked-session technique from friends-notifications.spec.ts.
    • Signed-in: mocks 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 meets MIN_RATED_DISHES_PER_HALL, proving a rank actually got pushed, not just a no-op call.
    • Signed-out: this spec is the residency guard, page.on("request") style (not route-glob counting) -- asserts zero Supabase/cross-origin requests during the same signed-out flow.
    • Ran both against the pre-fix 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 the session && guard in chooseCompare() -- 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 passed
  • pnpm --filter web check -- 0 errors, 0 warnings
  • pnpm --filter web build -- succeeded
  • shared/ untouched, so pnpm --filter @udine/shared test wasn'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.

- #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.
@TSVRN9
TSVRN9 merged commit 985af4b into main Aug 19, 2026
@TSVRN9
TSVRN9 deleted the fix/compare-prompt-cluster branch August 19, 2026 20:32
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant