Skip to content

web UI: style friends, pings, and notifications screens - #61

Merged
TSVRN9 merged 2 commits into
mainfrom
ux/friends-notifications
Aug 19, 2026
Merged

TSVRN9 merged 2 commits into
mainfrom
ux/friends-notifications

Conversation

@TSVRN9

@TSVRN9 TSVRN9 commented Aug 19, 2026

Copy link
Copy Markdown
Owner

Closes #39

Summary

Restyles web/src/routes/friends/+page.svelte and web/src/routes/notifications/+page.svelte using only #35's landed component classes/tokens (.card, .btn*, .badge, .empty-state, .page-title, .section-title, .field-label, .input, .label-rule). No app.css edits, no new hex colors.

  • Friends: .page-title/.label-rule header, .card search panel, friend-request rows badged Pending (outgoing) vs Wants to be friends + .btn-primary Accept (incoming), accepted friends in a .card with Friend badge + hall select/message .inputs + .btn-secondary ping button (per app.css's own comment calling out "Add friend" as the .btn-primary example and "ping" as the .btn-secondary example), pings inbox in .cards with hall .badges, .empty-state for all three "nothing yet" states and for signed-out.
  • Notifications: toggle card shows an Alerts on/Alerts off .badge next to the checkbox (the toggle-clarity criterion), sightings list badges unread rows New and every row's hall name, .empty-state for zero-sightings and signed-out. The old style="opacity: 0.6" inline style on read rows is replaced with the opacity-60 Tailwind utility (not a new color/token).

New web/e2e/friends-notifications.spec.ts (6 tests): real, unmocked signed-out coverage for both screens (no session cookie at all — proves the prompt renders, nothing crashes), plus signed-in coverage that mocks the Supabase session and all network traffic so nothing touches the live project (see spec file header comment for the exact mechanism: a seeded sb-<ref>-auth-token cookie via page.addInitScript, read by @supabase/ssr's browser client only after hydration — the SSR request never sees it, so safeGetSession() stays a no-network no-op — plus page.route for REST and page.routeWebSocket for the friends page's realtime channel).

Note on #39's third acceptance criterion ("no E2E coverage exists for these screens yet ... manual verification via a signed-in session is the check here"): the dispatching task for this PR explicitly asked for e2e coverage with mocked Supabase, so that's what's here instead of manual-only verification. Also verified live against the dev server (screenshots below).

Acceptance criteria (from #39)

  • Friends and notifications screens fully restyled using web UI: install Tailwind CSS + design tokens + styled shared layout #35's tokens — see file diffs; only listed component classes used, no app.css changes.
  • Friend request states (pending/accepted) and the notification toggle are visually clear — badges: Pending, Wants to be friends, Friend, Alerts on/Alerts off, New. See screenshots below.
  • pnpm --filter web check clean, CI passes — see verification section. (Superseded the "no E2E coverage" sub-clause per the note above — added web/e2e/friends-notifications.spec.ts instead of relying on manual-only verification.)

Verification

  • PORT=4302 npx playwright test — all 23 tests pass (17 pre-existing + 6 new), no regressions.
  • pnpm --filter web check — 0 ERRORS 0 WARNINGS.
  • pnpm --filter web build — succeeds (pre-existing adapter-auto "could not detect production environment" notice only, unrelated to this change).
  • Verified live against npx vite dev --port 4302 (web/.env copied from .env.example): screenshots of /friends and /notifications, both signed-out (real, no mocking) and signed-in (same cookie+route mocking technique as the e2e spec, driven via a throwaway Playwright script — not committed).

Red/green log (strict TDD)

The spec was written and run against the original, unstyled +page.svelte markup first (before any implementation edit) and failed for real — every assertion targets a class/badge/copy string that didn't exist in the old plain-HTML markup, so this is genuine new-behavior red, no mutation-testing needed:

1) Notifications — signed out › shows a styled sign-in prompt instead of the notifications UI
   Error: expect(locator).toHaveClass(expected) failed
   Locator: getByRole('heading', { name: 'Notifications', level: 1 })
   Expected pattern: /page-title/

2) Friends — signed out › shows a styled sign-in prompt instead of the friends UI
   Error: expect(locator).toHaveClass(expected) failed
   Locator: getByRole('heading', { name: 'Friends', level: 1 })
   Expected pattern: /page-title/

3) Friends — signed in › accepted friends get ping controls in a card, badged distinctly from requests
   Error: expect(locator).toHaveText(expected) failed
   Locator: locator('li.card').filter({ hasText: 'Casey Friend' }).locator('.badge')
   Expected: "Friend"
   Error: element(s) not found

4) Friends — signed in › pending (incoming/outgoing) ... Accept sends the right update
   Error: expect(locator).toHaveText(expected) failed
   Locator: locator('li').filter({ hasText: 'Casey Friend' }).locator('.badge')
   Expected: "Wants to be friends"
   Error: element(s) not found

5) Notifications — signed in › unread sightings are badged New, hall names are badged
   Error: expect(locator).toBeVisible() failed
   Locator: locator('li').filter({ hasText: 'French Toast' }).locator('.badge').filter({ hasText: 'New' })
   Error: element(s) not found

6) Notifications — signed in › the alert toggle is visually clear and flips state with the right PATCH
   Error: expect(locator).toHaveText(expected) failed
   Locator: locator('.badge')
   Expected: "Alerts off"
   Error: element(s) not found

6 failed

After implementing the styled markup, all 6 pass:

Running 6 tests using 4 workers
  6 passed (4.2s)

Files changed

  • web/src/routes/friends/+page.svelte — styling only, same load/action logic (added two $derived filters for pending/accepted to keep the template readable; no behavior change).
  • web/src/routes/notifications/+page.svelte — styling only, same logic; inline opacity style replaced with a Tailwind utility class + New badge.
  • web/e2e/friends-notifications.spec.ts — new.

Scope notes / things flagged but not changed

  • markRead is still onmouseenter-only (pre-existing behavior) — keyboard users can't mark a sighting read. Not in web UI: style friends, pings, and notifications screens #39's scope, flagging per dispatch instructions rather than silently fixing.
  • Did not add an onAuthStateChange/invalidate("supabase:auth") listener anywhere — out of scope, and the e2e session-mocking technique deliberately doesn't rely on one (it uses the existing hydration re-run of +layout.ts).
  • No +page.ts/server files touched — load output shape is unchanged.

Restyles friends/+page.svelte and notifications/+page.svelte with #35's design
tokens: .page-title/.label-rule headers, .card panels, .badge for friend-request
state (Pending vs Wants to be friends vs Friend) and notification alerts state
(Alerts on/off, New sightings), .btn-primary/.btn-secondary per app.css's own
component-layer guidance, .empty-state for the signed-out prompts, .field-label
+ .input on every form control. Replaces the old inline `style="opacity:0.6"`
read-sighting styling with the opacity-60 utility plus a New badge.

Adds web/e2e/friends-notifications.spec.ts: real (unmocked) signed-out coverage
for both screens, plus signed-in coverage that mocks the Supabase session via a
seeded auth cookie (read by @supabase/ssr's browser client post-hydration,
never seen by the SSR request) and mocks all REST/realtime traffic via
page.route/routeWebSocket -- nothing here touches the live project.
@TSVRN9
TSVRN9 merged commit c076193 into main Aug 19, 2026
7 checks passed
@TSVRN9
TSVRN9 deleted the ux/friends-notifications branch August 19, 2026 14:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

web UI: style friends, pings, and notifications screens

1 participant