Skip to content

test(web): lock in transcript search null/empty behavior (#908) - #1235

Merged
groupthinking merged 2 commits into
mainfrom
test/transcript-search-regression-908
Aug 7, 2026
Merged

groupthinking merged 2 commits into
mainfrom
test/transcript-search-regression-908

Conversation

@groupthinking

Copy link
Copy Markdown
Owner

Summary

Closes the single open gap on #908 by adding the regression coverage its
acceptance criteria claim, without touching the optimization itself.

This is not a competing implementation. #908 explicitly forbids one. The
perf work already landed on main via merged PR #972 (ded0eccf3) and is
correct — I verified both call sites hoist properly. Behavior here is
byte-for-byte preserved; the logic is only relocated so it can be tested.

The finding

#908 lists this as satisfied:

  • Null and empty search behavior is preserved.

It is preserved — but nothing enforced it:

Component Tests before Ships in production
InteractiveTranscript.tsx 0 yes, via dashboard/panels.tsx
TranscriptViewer.tsx 0 yes, via dashboard/panels.tsx

The refactor added the null guards (seg.text ? … : false, part ? … : '')
precisely because nullish text was a live risk — so they were load-bearing from
day one. A future refactor could drop them and every check would stay green.

Approach, and the constraint

apps/web/vitest.config.ts sets environment: 'node' deliberately:

Uses the node environment because the current suite exercises pure logic …
Add a jsdom project later if/when component rendering tests land.

Introducing jsdom/RTL would be an architectural decision that isn't mine to make
unilaterally, and rendering tests aren't needed to lock in this invariant.

So: extract the pure predicate into src/lib/transcript-search.ts and test it in
the node env. Precedent already exists — src/lib/timestamp.ts is shared pure
logic imported by these exact two components.

Rejected alternative: replicating the predicate inside the test. That
validates a copy rather than the shipped code — a vacuous test.

InteractiveTranscript re-exports TranscriptSegment, so panels.tsx needed no
changes.

Covered invariants (16 tests)

  • empty / null / undefined query returns all segments, never zero
  • nullish seg.text is a non-match, never a thrown TypeError
  • case-insensitive matching in both directions
  • speaker + search compose as AND, not OR
  • regex metacharacters escaped — "a.b" must not match "axb"
  • invalid raw patterns ((, [) do not throw
  • highlight regex omits /g so .test() cannot desync via lastIndex
  • the query is lowercased exactly once per pass, not once per segment

That last one asserts the actual performance property #972 shipped. A regression
to per-segment normalization now fails the suite instead of silently costing N
allocations per keystroke.

Non-vacuous by negative control

Each guard reverted individually in the shipped module, not in the test:

# Mutation Result
NC-1 drop seg.text ? guard 1 failed / 15 passed
NC-2 drop empty-query guard 2 failed / 14 passed
NC-3 drop regex escaping 2 failed / 14 passed
NC-4 add /g flag 1 failed / 15 passed
NC-5 normalize inside the loop 1 failed / 15 passed
NC-6 drop null short-circuit 1 failed / 15 passed
restore 16 passed

Every mutation is caught. No test passes for the wrong reason.

Verification

A/B against a stashed baseline (ambient AI_GATEWAY_API_KEY unset for hermeticity):

Files Tests Failed
baseline (stashed) 44 245 0
with changes 45 261 0

Zero new failures. tsc --noEmit clean, eslint clean.

Refs #908, #972

Issue #908 marks "Null and empty search behavior is preserved" as an accepted
criterion, but nothing enforced it. The perf work landed via merged PR #972
(ded0ecc) and is correct; however both components carrying it had zero test
coverage:

- InteractiveTranscript.tsx - 0 tests
- TranscriptViewer.tsx      - 0 tests

Both ship in production via dashboard/panels.tsx. The refactor *added* the
null guards (`seg.text ? ... : false`, `part ? ... : ''`) because nullish text
was a live risk - so the guards were load-bearing from day one, yet a future
refactor could drop them and every check would stay green.

This is not a competing implementation. Behavior is byte-for-byte preserved;
the logic is only relocated so it can be tested.

Approach: extract the pure search logic into src/lib/transcript-search.ts and
test it in vitest's `node` environment. apps/web/vitest.config.ts deliberately
avoids jsdom ("Add a jsdom project later if/when component rendering tests
land"), so introducing jsdom/RTL would be an unasked-for architectural change.
Precedent for the extraction already exists: src/lib/timestamp.ts is shared
pure logic imported by these same two components.

Rejected alternative: replicating the predicate inside the test. That validates
a copy rather than the shipped code - a vacuous test.

Covered invariants (16 tests):
  - empty / null / undefined query returns ALL segments, never zero
  - nullish seg.text is a non-match, never a thrown TypeError
  - case-insensitive matching in both directions
  - speaker + search compose as AND
  - regex metacharacters are escaped ("a.b" must not match "axb")
  - invalid raw patterns ( "(", "[" ) do not throw
  - highlight regex omits /g so .test() cannot desync via lastIndex
  - query is lowercased exactly ONCE per pass, not once per segment

That last one asserts the actual performance property PR #972 shipped, so a
regression to per-segment normalization now fails the suite instead of silently
costing N allocations per keystroke.

Non-vacuous by negative control - each guard reverted individually in the
shipped module:

  NC-1  drop `seg.text ?` guard        -> 1 failed / 15 passed
  NC-2  drop empty-query guard         -> 2 failed / 14 passed
  NC-3  drop regex escaping            -> 2 failed / 14 passed
  NC-4  add /g flag                    -> 1 failed / 15 passed
  NC-5  normalize inside the loop      -> 1 failed / 15 passed
  NC-6  drop null short-circuit        -> 1 failed / 15 passed
  restore                              -> 16 passed

A/B against a stashed baseline (apps/web, ambient AI_GATEWAY_API_KEY unset):

  baseline       44 files / 245 passed / 0 failed
  with changes   45 files / 261 passed / 0 failed

Zero new failures. tsc --noEmit and eslint both clean.

Refs #908, #972

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@vercel

vercel Bot commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
v0-uvai Ready Ready Preview, v0 Aug 2, 2026 2:56pm

@github-actions github-actions Bot added javascript Pull requests that update javascript code tests labels Aug 2, 2026
@coderabbitai

coderabbitai Bot commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are limited based on label configuration.

🏷️ Required labels (at least one) (1)
  • [‘architecture-gap’, ‘bug’, ‘ci-cd’, ‘ci/cd’, ‘copilot-rabbit’, ‘documentation’, ‘duplicate’, ‘enhancement’, ‘frontend’, ‘github_actions’, ‘good first issue’, ‘help wanted’, ‘high-priority’, ‘invalid’, ‘javascript’, ‘ml-model’, ‘needs-triage’, ‘pipeline-critical’, ‘placeholder-code’, ‘priority:high’, ‘python’, ‘python:uv’, ‘question’, ‘styling’, ‘tests’, ‘v0’]

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository YAML (base), Repository UI (inherited), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: a9dc76ba-77e4-4dad-b5c9-db673b82ab62

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

Dependency Review

✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.

Snapshot Warnings

⚠️: No snapshots were found for the head SHA 82459d4.
Ensure that dependencies are being submitted on PR branches and consider enabling retry-on-snapshot-warnings. See the documentation for more information and troubleshooting advice.

Scanned Files

None

@github-actions

github-actions Bot commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

Agent Completion Truth Gate: NOT_APPLICABLE

Evidence agrees.

Machine-readable verdict
{
  "details": {},
  "reasons": [],
  "verdict": "not_applicable"
}

Workflow evidence

@groupthinking
groupthinking enabled auto-merge (squash) August 7, 2026 20:49
@groupthinking
groupthinking merged commit b4b596d into main Aug 7, 2026
15 of 16 checks passed
@groupthinking
groupthinking deleted the test/transcript-search-regression-908 branch August 7, 2026 20:49
@linear-code

linear-code Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

GRV-376

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

javascript Pull requests that update javascript code tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant