Skip to content

chore(stage6): wrap-up — Playwright per-US specs + facets unit test + typo-recall eval#18

Merged
satra merged 2 commits into
mainfrom
chore/stage6-wrap-up
May 18, 2026
Merged

chore(stage6): wrap-up — Playwright per-US specs + facets unit test + typo-recall eval#18
satra merged 2 commits into
mainfrom
chore/stage6-wrap-up

Conversation

@satra

@satra satra commented May 18, 2026

Copy link
Copy Markdown
Collaborator

Summary

Closes out the remaining Stage 6 todos that had been deferred to "Phase 11 polish" pending a live preview / built data package. Now that the data package is local and production is deployed, all of them are genuinely runnable.

Task What landed
T062 `site/src/tests/e2e/search.spec.ts` — FR-007 narrowing, FR-008 typo recovery, PR#17 operator grammar, ✨ semantic-only badge contract
T064 `site/src/tests/unit/facets.test.ts` — FR-013: a facet's own selections don't zero its siblings (4 cases / 4-abstract fixture)
T068 `site/src/tests/e2e/facets.spec.ts` — facet click narrows results, sibling counts recompute, `Clear` restores
T071 `site/src/tests/e2e/cart.spec.ts` — add + reload persistence, `Clear` empties, `cart-email` opens `mailto:` with the poster_id
T081 `site/src/tests/e2e/tour.spec.ts` — first-visit CTA + start, dismissal + header re-entry
T058a / SC-010 `scripts/eval_typo_recall.py` — replays the lexical core against the data shards. recall@10 = 0.9685 over 762 probes (≥ 0.90 target)
T026 / T090 / T091 / T096 / T097 / T099 Bookkeeping pass in `tasks.md` — items already shipped via PRs #9#17 are now marked `[X]` with status notes
T100 Marked OBSOLETE — Stage 6 shipped across 10+ PRs (#9#17), not one final consolidating PR

Test plan

  • `pnpm test:unit --run` — 75/75 pass (4 new for `recomputeFacets`)
  • `pnpm build` — clean (no new svelte-check errors in touched files)
  • `.venv/bin/python scripts/eval_typo_recall.py --shards site/static/data` → `recall@10 = 0.9685`, exit 0
  • Playwright e2e specs run in the local preview workflow on this PR (covered by the existing `pr-preview` job — if data package is materialised, they execute; otherwise they self-skip)

After this lands

Stage 6's 105 numbered tasks are complete. The only Stage-6-area work remaining is the operator-syntax UX shipped in PR #17 (post-spec enhancement).

🤖 Generated with Claude Code

…ypo-recall eval

Closes out the remaining Stage 6 todos that were deferred to "Phase 11
polish" pending a live preview / built data package. Now that the data
package is local and production is up, they're all genuinely runnable.

Tests added:
- `site/src/tests/e2e/search.spec.ts` (T062) — FR-007 narrowing,
  FR-008 typo recovery on ≥ 7-char words, PR#17 operator grammar
  (`"phrase"` ≤ bare-AND set, `-word` subtracts, `OR` unions), and
  the ✨ semantic-only badge tooltip contract.
- `site/src/tests/e2e/facets.spec.ts` (T068) — FR-005/FR-013: facet
  click narrows results, sibling-facet counts recompute against the
  narrowed set, `Clear` restores the corpus-wide count.
- `site/src/tests/e2e/cart.spec.ts` (T071) — FR-006/SC-009: card-icon
  add + reload (localStorage persistence), `Clear` empties, `cart-email`
  produces a `mailto:` URL with the poster_id in the body.
- `site/src/tests/e2e/tour.spec.ts` (T081) — US6 acceptance: CTA banner
  on first visit, "Start tour" opens `.shepherd-element`, dismissal
  hides the CTA while the header `Tour` button remains.
- `site/src/tests/unit/facets.test.ts` (T064) — FR-013 nuance: a facet's
  own selections don't zero its sibling options' counts. 4 cases against
  a 4-abstract fixture. 75/75 unit tests pass.

Eval script:
- `scripts/eval_typo_recall.py` (T058a, SC-010) — ports the lexical-
  search core to Python and replays it against the data-package
  shards. Generates full-title probes (capped at 8 tokens) with one
  recoverable single-typo on a ≥ 4-char content word. Recall against
  the live shards: **0.9685 over 762 probes** (≥ 0.90 target). The
  threshold scheme (<4 → exact, 4–6 → DL ≤ 1, ≥ 7 → DL ≤ 2) confirmed
  sound. Exits 0 on pass / 3 on fail (so the script is usable as a CI
  gate later).

Bookkeeping (T099 + T100 + several already-shipped tasks reconciled in
`specs/008-ui-rewrite/tasks.md`):
- T026 (US8 draft-PR verification) — DONE via PRs #9#17 Deployments-
  box surface.
- T090 (Lighthouse-CI) — DONE in PR #15.
- T091 (a11y axe-core audit) — DONE in PRs #12 + #14.
- T096 (SC sweep) — DONE; SC-002 / SC-003 / SC-004 / SC-005 / SC-011
  measured against production via `sc-sweep.spec.ts`.
- T097 (FR-021 / FR-022 acceptance) — DONE in spirit across PRs #9#17.
- T099 (reconciliation pass) — DONE in this commit.
- T100 — marked OBSOLETE (Stage 6 shipped across 10+ PRs, not as one
  final consolidating PR).

After this PR lands, the only Stage 6 work that remains is the
operator-syntax UX shipped in PR #17 (post-spec enhancement) — the
spec's 105 numbered tasks are complete.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@satra
satra temporarily deployed to pr-preview-18 May 18, 2026 00:37 — with GitHub Actions Inactive

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a comprehensive suite of tests and evaluation scripts for the UI rewrite. Key additions include a Python-based typo-recall evaluation script, Playwright E2E tests for search, facets, cart, and the guided tour, and Vitest unit tests for facet logic. The task list in tasks.md has also been updated to reflect the completion of these items. Reviewers identified several improvement opportunities: ensuring determinism in the typo-recall script by avoiding Python's non-deterministic hash(), adopting 'web-first' Playwright assertions instead of manual counts and brittle timeouts, and utilizing native Vitest Map comparisons for cleaner test code.

Comment thread scripts/eval_typo_recall.py Outdated
"""
if len(word) < 4 or not word.isalpha():
return []
rng = random.Random(hash(word) & 0xFFFFFFFF)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The use of hash() in Python 3 is non-deterministic across different process executions because of hash randomization. This contradicts the docstring's claim that the evaluation is "deterministic across runs". To ensure reproducibility, consider using a stable hash (like zlib.adler32) or passing the rng instance from the main function to seed the local random.Random instance.

Comment thread site/src/tests/e2e/cart.spec.ts Outdated
await page.getByTestId('toggle-cart').click();
await expect(page.getByTestId('cart-drawer')).toBeVisible();
const items = page.getByTestId('cart-item');
expect(await items.count()).toBeGreaterThanOrEqual(1);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using locator.count() in a standard expect assertion is not "web-first" and will not retry if the UI hasn't finished updating. This can lead to flaky tests in environments with varying performance.

Suggested change
expect(await items.count()).toBeGreaterThanOrEqual(1);
await expect(items.first()).toBeVisible();

Comment thread site/src/tests/e2e/search.spec.ts Outdated
// The lexical pipeline is synchronous; semantic is async but only
// changes the ✨ badge / rank, not the result-count. Wait briefly for
// the count to stabilise.
await page.waitForTimeout(250);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Hardcoded timeouts like waitForTimeout are brittle and can lead to flakiness or unnecessarily slow tests. It is recommended to wait for a specific UI state (e.g., the result count updating or a loading indicator disappearing) using Playwright's web-first assertions or waitForFunction.

Comment thread site/src/tests/e2e/search.spec.ts Outdated
)
.catch(() => null);
await typeQueryAndSettle(page, '"critical brain hypothesis"');
await page.waitForTimeout(800); // give semantic neighbour pass time to settle

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This 800ms timeout is quite long and brittle. Since the semantic search status is tracked in a store (as mentioned in the task list), it would be more robust to wait for the search to transition from a 'loading' to a 'ready' state, or for the specific semantic-score elements to appear if they are expected.

Comment thread site/src/tests/unit/facets.test.ts Outdated
// recomputeFacets with the empty map === recomputeFacets with no filters.
const a = recomputeFacets(fixture, empty, null);
const b = recomputeFacets(fixture, NO_FILTERS, null);
expect(JSON.stringify([...a])).toBe(JSON.stringify([...b]));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Vitest's toEqual assertion natively supports comparing Map objects. Stringifying the maps is less efficient and provides less helpful diffs on failure.

Suggested change
expect(JSON.stringify([...a])).toBe(JSON.stringify([...b]));
expect(a).toEqual(b);

…erts

Addresses 5 medium-priority gemini-code-assist comments on PR #18.

scripts/eval_typo_recall.py — `hash(word) & 0xFFFFFFFF` was non-
deterministic across Python processes (`PYTHONHASHSEED` randomisation),
contradicting the docstring's "deterministic across runs" promise.
Switched to `zlib.adler32(word.encode('utf-8'))` — a stable 32-bit hash
that's plenty for seeding a per-word PRNG. Eval still passes:
**recall@10 = 0.9894 over 378 probes** (was 0.9685 with the old
randomised seed, well above the 0.90 target either way).

site/src/tests/unit/facets.test.ts — replaced `JSON.stringify(...)`
Map equality with Vitest's native `expect(...).toEqual(...)`. Walks the
Map structurally and gives a useful diff on failure.

site/src/tests/e2e/cart.spec.ts — converted `expect(await count())`
one-shots to web-first `await expect(locator.first()).toBeVisible()`
and `await expect(locator).toHaveCount(0)`. Dropped every
`waitForTimeout` in this spec; Playwright auto-retries.

site/src/tests/e2e/search.spec.ts — replaced the three `waitForTimeout`
calls (250 ms between fills, 100 ms after clearing, 800 ms after a
semantic query) with `expect.poll` based two-tick stability checks.
The semantic-settle wait now has a 2 s budget and fails fast if the
worker hangs, rather than always burning the hard-coded 800 ms.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@satra
satra temporarily deployed to pr-preview-18 May 18, 2026 00:46 — with GitHub Actions Inactive
@satra

satra commented May 18, 2026

Copy link
Copy Markdown
Collaborator Author

Addressed all 5 gemini-code-assist comments in 6fc3b3f:

eval_typo_recall.pyhash(word) & 0xFFFFFFFF was non-deterministic across Python processes (PYTHONHASHSEED randomisation), which contradicted the docstring's "deterministic across runs" promise. Switched to zlib.adler32(word.encode('utf-8')) — stable 32-bit hash. Eval still passes: recall@10 = 0.9894 over 378 probes (was 0.9685 with the old seed — the new variants happen to be even more recoverable).

facets.test.ts — replaced JSON.stringify(...) Map equality with native expect(...).toEqual(...). Walks the Map structurally and gives a useful diff on failure.

cart.spec.ts — converted expect(await count()) one-shots to web-first await expect(locator.first()).toBeVisible() and await expect(locator).toHaveCount(0). Dropped every waitForTimeout in this spec.

search.spec.ts — replaced the three waitForTimeout calls (250 ms between fills, 100 ms after clearing, 800 ms after a semantic query) with expect.poll two-tick stability checks. The semantic-settle wait now has a 2 s budget and fails fast if the worker hangs, rather than always burning the hard-coded 800 ms.

@satra
satra merged commit 9cf1c13 into main May 18, 2026
2 checks passed
@satra
satra deleted the chore/stage6-wrap-up branch May 18, 2026 00:49
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.

1 participant