diff --git a/web/e2e/rank.spec.ts b/web/e2e/rank.spec.ts index b70f7350..0ae9f21f 100644 --- a/web/e2e/rank.spec.ts +++ b/web/e2e/rank.spec.ts @@ -119,4 +119,11 @@ test("log 2 dishes, make a ranking comparison, and see Your ranking / Favorite F await expect(unrankedHalls).toContainText(hall); } await expect(unrankedHalls.locator("li")).toContainText(["not enough data yet", "not enough data yet", "not enough data yet"]); + + // #38 AC2: ranked vs. unranked halls must be visually distinguishable, "not just text saying + // 'not enough data yet'". Pin that to the actual visual treatment (the shared .card surface), + // not just the wording already asserted above, so a regression that quietly drops the card + // styling back to plain rows can't hide behind a still-passing "contains the right words" check. + await expect(rankedHalls.locator("li").first()).toHaveClass(/card/); + await expect(unrankedHalls.locator("li").first()).not.toHaveClass(/card/); }); diff --git a/web/src/routes/rank/+page.svelte b/web/src/routes/rank/+page.svelte index ea9031e0..db6350fc 100644 --- a/web/src/routes/rank/+page.svelte +++ b/web/src/routes/rank/+page.svelte @@ -27,6 +27,13 @@ let pair: [Dish, Dish] | null = $state(null); let hallRanking = $derived(rankDiningHalls(rankedDishes)); let lastPair: [Dish, Dish] | null = null; + // With only two logged dishes pickPair() legitimately returns the same pair every time, so + // choosing a winner changes nothing on screen above the fold and the tap reads as a dead click. + // This line is the acknowledgement. + let lastChoice = $state(""); + + // Each comparison bumps comparisonCount on both dishes, so the sum double-counts. + const comparisonsMade = $derived(Math.round(rankedDishes.reduce((sum, d) => sum + d.comparisonCount, 0) / 2)); function hallName(hallTid: number): string { return DINING_HALLS.find((h) => h.tid === hallTid)?.name ?? `Hall ${hallTid}`; @@ -108,63 +115,160 @@ void syncDiningHallRanks(page.data.supabase, session.user.id, snapshot); } + lastChoice = `${winner.dishName} over ${loser.dishName}`; pair = pickPair(); } function skip() { + lastChoice = ""; pair = pickPair(); } -
Compare dishes you've actually logged — ranking is built from what you've eaten, not the full menu.
++ Compare dishes you’ve actually logged — ranking is built from what you’ve eaten, not + the full menu. Every comparison stays on this device. +
+Log a couple of meals first, then come back here to rank them.
+Nothing to compare yet
++ Ranking needs at least two different dishes in your log. You have {loggedDishes.length}. + Log a couple of meals first, then come back here to rank them. +
+ +No comparisons yet.
-{:else} -Your favorite dishes by name, regardless of which hall serves them.
-{#if rankedFoods.length === 0} -No comparisons yet.
-{:else} -+ {#if lastChoice}Recorded: {lastChoice}{/if} +
++ No comparisons yet — pick a winner above and your dishes start ordering themselves. +
+ {:else} ++ The number on the right is a rating, not calories — it starts at 1000 and moves as you + compare. Only the order matters. +
+ {/if} ++ Your favorite dishes by name, regardless of which hall serves them. +
+ {#if rankedFoods.length === 0} +No comparisons yet.
+ {:else} ++ A hall needs at least 2 rated dishes before it can be placed. +
+ {/if} +