Skip to content

[3316 by Claude Opus 5.5] Make lazy-route browser clock pausing deterministic - #3317

Merged
integry merged 1 commit into
mainfrom
3316/claude-opus-5-5-make-lazy-route-browser-c-20261021-1655-zfu
Oct 21, 2026
Merged

integry merged 1 commit into
mainfrom
3316/claude-opus-5-5-make-lazy-route-browser-c-20261021-1655-zfu

Conversation

@propr-dev

@propr-dev propr-dev Bot commented Oct 21, 2026

Copy link
Copy Markdown
Contributor

AI Implementation Summary

Closes #3316

Branch: 3316/claude-opus-5-5-make-lazy-route-browser-c-20261021-1655-zfu
Commits: ✅ Changes committed (478f46b)


AI Processing Completed

Execution Details:

Summary:
The fix is in propr-ui/e2e/list-search-lazy-navigation.pw.ts and nothing else changed. The full matrix passes in Chromium and WebKit: 24/24 in each (the 21 original cases plus 3 new ones), with zero retries.

Cause. pauseAt moves the clock forward to an absolute time and rejects any target in the past. The fake clock follows real time until the pause actually runs in the page, so "sampled time + 50 ms" is already in the past whenever the command takes longer than 50 ms. In the failing run it took about 106 ms.

Fix. The pause helper now uses three supported clock calls instead of a margin:

  1. Read the page's current time t.
  2. page.clock.setFixedTime(t) freezes the page's Date at t. Timers keep running on real time, which is the same as before the pause lands.
  3. page.clock.pauseAt(t) now has to move the clock forward by exactly 0 ms, however late it arrives. It can't land in the past, and unlike the old +50 ms it can't fire a pending timer early.
  4. page.clock.setSystemTime(t) releases Date while the clock is paused, so it stays stopped at t.

Everything else in the file is unchanged: the 400/300 ms production debounces, the 1,000 ms advance, the held real Settings chunk, the input, URL and focus checks, the Settings and Back assertions, and the timeouts. I made no production, backend or dependency changes. Nothing needed a browser-specific workaround: Chromium and WebKit run the same code.

Latency proof (criterion 4). The helper takes an optional stall hook that runs between each step. One new control case per viewport passes a fixed 250 ms wait at every step, which is more than the old 50 ms margin and the observed 106 ms. That case then:

  • checks the page clock reads exactly t after the pause.
  • types a Tasks search and checks the clock is still at t and the URL has no search=.
  • runs the full Settings navigation and Back checks.

This is a fixed delay, not a timing benchmark, and nothing is retried.

I also ran two throwaway checks that I didn't keep in the repo:

  • Old helper under the same 250 ms stall: it fails every time with Cannot fast-forward to the past.
  • Debounce still queued: for Tasks, Plans and Goals, after the new pause the typed search didn't reach the URL after 1.5 s of real time. Advancing the clock 1,000 ms while staying on the list then did write it, so the debounce was waiting, not dropped.

How it was checked:

  • Chromium: the default config.
  • WebKit: a temporary config, now deleted, using browser libraries I installed locally in this container.
  • Lint and types: eslint with --max-warnings 0 is clean, and the file has no type errors.

These runs used this container, not your validation image or the 2-CPU/2-GiB setup from the issue. There are no previews because the change isn't visible to users.

Detailed Logs:

  • Conversation: 89 messages
  • Session: b6783c8e-6a58-4b5a-a6ab-f43413dd495d

Log files stored at:

  • conversation: /usr/src/app/logs/executions/issue-3316-2026-10-21T17-07-25-255Z-conversation.json
  • output: /usr/src/app/logs/executions/issue-3316-2026-10-21T17-07-25-255Z-output.txt
Latest Conversation Messages
ASSISTANT: All 24 tests in `list-search-lazy-navigation.pw.ts` passed under WebKit on the first run. The summary line was:

24 passed (2.6m)


**Per-test results** (all ✓, at `e2e/list-search-lazy-navig...

ASSISTANT: [content unavailable]

ASSISTANT: The fix is in `propr-ui/e2e/list-search-lazy-navigation.pw.ts` and nothing else changed. The full matrix passes in Chromium and WebKit: 24/24 in each (the 21 original cases plus 3 new ones), with zero...


This PR was created automatically by ProPR after processing issue #3316.


💡 Need changes?

Comment on this PR to request refinements — the AI agent monitors comments and will update the implementation based on your feedback. Keep iterating until you're satisfied!

…thing else changed. The full matrix passes in Chromium and WebKit: 24/24 in each (the 21 original cases plus 3 new ones), with zero retries.

Cause. `pauseAt` moves the clock forward to an absolute time and rejects any target in the past. The fake clock follows real time until the pause actually runs in the page, so "sampled time + 50 ms" is already in the past whenever the command takes longer than 50 ms. In the failing run it took about 106 ms.

Fix. The pause helper now uses three supported clock calls instead of a margin:
1. Read the page's current time `t`.
2. `page.clock.setFixedTime(t)` freezes the page's Date at `t`. Timers keep running on real time, which is the same as before the pause lands.
3. `page.clock.pauseAt(t)` now has to move the clock forward by exactly 0 ms, however late it arrives. It can't land in the past, and unlike the old +50 ms it can't fire a pending timer early.
4. `page.clock.setSystemTime(t)` releases Date while the clock is paused, so it stays stopped at `t`.

Everything else in the file is unchanged: the 400/300 ms production debounces, the 1,000 ms advance, the held real Settings chunk, the input, URL and focus checks, the Settings and Back assertions, and the timeouts. I made no production, backend or dependency changes. Nothing needed a browser-specific workaround: Chromium and WebKit run the same code.

Latency proof (criterion 4). The helper takes an optional `stall` hook that runs between each step. One new control case per viewport passes a fixed 250 ms wait at every step, which is more than the old 50 ms margin and the observed 106 ms. That case then:
- checks the page clock reads exactly `t` after the pause.
- types a Tasks search and checks the clock is still at `t` and the URL has no `search=`.
- runs the full Settings navigation and Back checks.

This is a fixed delay, not a timing benchmark, and nothing is retried.

I also ran two throwaway checks that I didn't keep in the repo:
- Old helper under the same 250 ms stall: it fails every time with `Cannot fast-forward to the past`.
- Debounce still queued: for Tasks, Plans and Goals, after the new pause the typed search didn't reach the URL after 1.5 s of real time. Advancing the clock 1,000 ms while staying on the list then did write it, so the debounce was waiting, not dropped.

How it was checked:
- Chromium: the default config.
- WebKit: a temporary config, now deleted, using browser libraries I installed locally in this container.
- Lint and types: eslint with `--max-warnings 0` is clean, and the file has no type errors.

These runs used this container, not your validation image or the 2-CPU/2-GiB setup from the issue. There are no previews because the change isn't visible to users.
@integry

integry commented Oct 21, 2026

Copy link
Copy Markdown
Owner

/review codex-astra

!gitfix Independent Astra review of exact head 478f46b. Test-only clock reliability change for #3316. Please verify supported Playwright clock semantics, the setFixedTime/pauseAt/setSystemTime sequence under command latency, and whether any pending timer can be fired or suppressed in a way that weakens the original 21 lazy-route navigation cases. Assess the 3 new latency controls and preservation of trusted input, held Settings chunk, 1000-ms advance, URL/focus/Back assertions, viewports and deadlines. Look for false confidence from pinning Date while timers still tick before pause. No production code changes, retries, skipped cases or inflated timeouts should be introduced. Review only; operator validation and frozen-preview merge timing remain separate. Do not merge, publish or deploy.

@propr-dev

propr-dev Bot commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor Author

✅ AI Code Review Complete requested by @integry

Posted 1 review:

View Task Details

@propr-dev

propr-dev Bot commented Oct 21, 2026

Copy link
Copy Markdown
Contributor Author

🔍 AI Code Review — codex-astra

Overall Evaluation

Merge-ready within the supplied scope, conditional on the five pending checks completing successfully. No PR-introduced blocker was identified. This is a static review of the supplied diff and context; no commands, runtime tests, or independent inspection of Playwright 1.62.1 were performed. The linked issue excerpt is truncated, so this assessment covers its visible requirements.

✅ Correct pause sequencing — Pinning Date before targeting that same time with pauseAt removes the sampled-time-plus-50-ms deadline. setSystemTime then restores advancing Date semantics while execution remains paused, without itself advancing timers.

✅ Navigation coverage preserved — All 21 original cases retain their trusted input, held Settings chunk, 1,000-ms advancement, URL and Back assertions, applicable focus assertions, and three viewports. The diff changes no production code, retries, skips, or timeouts.

✅ Relevant latency controls — The three added cases insert 250-ms delays between helper steps, then type while paused and exercise the existing delayed-navigation assertions.

Timers can still fire before pauseAt executes; fixing Date does not freeze them. However, the relevant pending edits in the original typed-search cases and Plans/Goals clear cases are created after the helper returns. Tasks clear cases already wait for the debounced URL clear before invoking it. Consequently, that pre-pause interval does not introduce a demonstrated loss of the original regression coverage.

Merge blockers

No merge blockers.

Suggestions

These are optional follow-ups and are not sent to /fix.

S1: 🟢 Directly verify timer preservation

Optionally add a small clock-specific control that schedules a sentinel timer while paused, confirms it remains pending through real elapsed time, and verifies it fires after the intended clock advancement. The new Date equality assertions alone cannot establish timer behavior, and a missing search callback could leave the navigation assertions passing. This would strengthen positive evidence for timer preservation; the supplied sequence does not demonstrate timer suppression, so this is not a merge requirement.

S2: 🟢 Clarify the helper’s timing guarantee

In propr-ui/e2e/list-search-lazy-navigation.pw.ts:87, clarify that timers stop when pauseAt executes, rather than immediately upon entering the helper. The comment already acknowledges that timers keep ticking while Date is pinned, but “from here” and “fires nothing early” could imply a broader guarantee. Current callers place their relevant edits safely, making this documentation cleanup optional.

Score

The localized change preserves the original assertions and adds useful latency coverage, with no demonstrated regression. Twelve checks passed, five remain pending, and this review did not independently execute the browser matrix or inspect the pinned clock implementation.

Score: 8/10


🤖 Review Details

  • Model: GPT-6 Astra
  • Time: 32s
  • Tokens: 27,999 (27,169 in / 830 out)
  • Cost: $0.20

View Task


💡 Next step: Comment /fix to address every F# merge blocker, /fix all for every pending blocker and suggestion, or name records explicitly, as in /fix F3 S5.
F# and S# IDs increment across review comments and remain permanent, so selectors such as /fix F3 F5 stay unambiguous across cycles. S# suggestions stay optional: they are implemented only when you name them or request /fix all, and they never relax a merge blocker.

Copy and edit to choose records from this review:

/fix S1 S2

🤖 Review by ProPR

@integry
integry merged commit 6ffe39d into main Oct 21, 2026
52 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Make lazy-route browser clock pausing deterministic

1 participant