Commit graph

5 commits

Author SHA1 Message Date
Ullrich Schäfer
a05c8e87a1
e2e: close the interact-before-hydration race (cold-start flake)
Diagnosis (reproduced by looping cold-server runs): the recurring
"cold start" failures in the auth/follow specs were a hydration race,
not a Vite dep-reload (the server.warmup config already handles that —
no reload events in the logs). FollowButton is an onClick button, and
Playwright considers it actionable (visible + enabled) before React
attaches the handler on a freshly-navigated page. A click in that
window is dropped (or triggers a native form submit), so the expected
state change never happens — e.g. notifications.test.ts:59 clicked
"Request to follow" with no hydration wait and the button never
flipped to "Requested".

The existing waitForHydration guard was applied per-interaction and so
was easy to forget (test 1 had it, test 3 didn't). Fix it at the
navigation instead:

- add gotoHydrated(page, url) = goto + waitForHydration, documented as
  the default for "navigate then interact with a React control"
- use it before every FollowButton interaction in notifications +
  social
- consolidate the setProfileVisibility helper (copy-pasted in three
  specs, two missing the hydration wait) into e2e/helpers/profile.ts,
  built on gotoHydrated

Validation against a fresh Postgres, cold servers: notifications 0/10
failures (was the proven flaker); notifications+social+explore 0/5
(55 tests); planner unaffected (25/25).

Note: two other "cold-start"-attributed flakes are NOT this race and
are out of scope here — planner map-load latency (mitigated by warmup
+ CI retries) and the /explore directory assertion (a local artifact
of the scratch DB accumulating >20 public users; CI's fresh DB stays
under the page size, so it doesn't bite there).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 14:09:00 +02:00
Ullrich Schäfer
7a290cd56f
e2e: register and repair the settings/explore/social specs
settings.test.ts, explore.test.ts, and social.test.ts weren't matched
by any Playwright project, so they had silently never run — which is
how they rotted. Register them (one project each) and repair the
selectors against the current UI:

- settings: the page was split into sibling sections
  (/settings/{profile,security,account}); the spec assumed one page.
  Navigate to the right sub-page per test, use the stable section-nav
  links + #id input locators (the Vite dev server transiently
  double-renders the profile form during hydration, breaking
  getByLabel), and wait for hydration before interacting with
  fetcher-backed forms and the avatar dropdown.
- explore: setProfileVisibility now targets /settings/profile and
  waits for hydration so the visibility save uses the fetcher.
- social: already green once registered.

Also fixes an app bug the specs surfaced: deleting a passkey redirected
to /settings#security, a stale anchor that now resolves to
/settings/profile — so you'd land on the wrong section. It now
redirects to /settings/security.

Verified locally against Postgres/BRouter: green under CI-style
retries (the residual registration flake is the same cold-start class
the rest of the suite has, which is why CI runs retries=2).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 08:32:18 +02:00
Ullrich Schäfer
0e267afac7
e2e: shared auth + journal seed helpers; flag unregistered specs
The virtual-authenticator setup and registerUser were copy-pasted into
six spec files and had drifted: the auth spec's copy lost the final
URL assertion, and the settings spec's variant skipped hydration and
the Terms checkbox entirely. Seed-route boilerplate and hardcoded
localhost URLs were repeated across three more files.

- helpers/auth.ts: setup/removeVirtualAuthenticator,
  submitRegistration (no outcome assertion, for expected-failure
  attempts), registerUser (asserts the signed-in redirect),
  registerFreshUser, logout
- helpers/journal.ts: JOURNAL/PLANNER base URLs, seedRoute,
  routeHasGeom, seedKomootConnection
- nine spec files now import the helpers instead of re-deriving them

Found while consolidating: settings.test.ts, explore.test.ts, and
social.test.ts are not matched by any playwright project and have
never run — which is how the settings spec's broken register helper
survived. Registering them (and fixing whatever has rotted) is a
follow-up; the config now carries a warning so the trap is at least
documented.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 07:31:51 +02:00
Ullrich Schäfer
b7ed54ba72
Stabilize e2e against Vite dev hydration race
Two issues caused the same class of flake locally:

1. Default workers were CPU-count, but the journal/planner are served
   by Vite dev (not the production build CI uses). Cold-compiling
   `/api/auth/register` under N parallel hits produced 30s timeouts
   on a quarter of runs. Set workers to 1 in both environments for
   parity with CI.
2. Even sequentially, a button is clickable per Playwright's
   actionability check before React has hydrated its `onClick`. So
   the first click after a navigation could fire native form submit
   (or do nothing), which manifested as "URL never changed to /" or
   "menuitem Log Out never appeared". Add a `waitForHydration` helper
   that polls for React fibers (`__reactProps$<id>`) attached to a
   DOM node and call it after each cross-page navigation that ends
   in an interactive form or dropdown.

CI is unaffected (production builds hydrate fast and didn't expose
either bug), but the helper is harmless there.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 01:06:27 +02:00
Ullrich Schäfer
37f7a349b9 Implement /explore: local user discovery directory
Stream F implementation. Closes the gap between "I want to follow
someone on this instance" and "I have a username from outside the
app." Anonymous visitors and signed-in users both reach a paginated
directory of local public users; signed-in viewers also see Follow
buttons inline.

- apps/journal/app/lib/explore.server.ts — listDirectory (paginated,
  ordered by MAX(public-activity created_at) DESC NULLS LAST,
  tiebreaker users.id DESC), listActiveRecently (top 5 in last 30
  days), countDirectory, batched countFollowersBatch and
  getFollowStateBatch helpers so the page issues two extra queries
  regardless of page size. Excludes private profiles and the demo
  persona; banned/suspended scaffolding noted for forward-compat.
- apps/journal/app/routes/explore.tsx — loader fans out the four
  queries in parallel; component renders an "Active recently" strip
  (hidden when empty) above the main directory; FollowButton inlines
  per row for signed-in viewers. Bio truncated to 120 chars.
- apps/journal/app/routes.ts — register /explore.
- apps/journal/app/root.tsx — add Explore navbar entry for signed-in
  users.
- apps/journal/app/routes/home.tsx — visitor home gains a secondary
  "Browse who's here →" link to /explore alongside the Planner
  escape hatch.
- packages/i18n/src/locales/{en,de}.ts — explore.*, nav.explore,
  home.exploreLink keys.
- apps/journal/app/lib/explore.integration.test.ts — opt-in
  (EXPLORE_INTEGRATION=1) integration tests covering inclusion,
  exclusion, ordering, NULLS LAST behavior, "Active recently"
  30-day filter, count helpers.
- e2e/explore.test.ts — anonymous loads /explore (no auth needed);
  private profile is excluded from the directory.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 09:24:19 +02:00