Skip to content

test(app): add runtime CLS source gate - #820

Merged
Astro-Han merged 6 commits into
devfrom
pawwork/issue-814-runtime-cls-gate
May 21, 2026
Merged

test(app): add runtime CLS source gate#820
Astro-Han merged 6 commits into
devfrom
pawwork/issue-814-runtime-cls-gate

Conversation

@Astro-Han

@Astro-Han Astro-Han commented May 21, 2026

Copy link
Copy Markdown
Owner

Summary

Adds a head-only runtime CLS source gate for session composer and question dock height changes.

Why

#814 needs a controlled regression gate for runtime timeline shifts during composer / prompt dock resizing. This PR observes real LayoutShift entries during bounded interaction windows and fails on large visible timeline primary-source movement, including nested primary-turn-descendant sources called out in design review.

Related Issue

Closes #814.

Follow-up: #818 tracks deterministic question dock open/growth coverage. This PR covers composer growth, composer shrink, and question dock close/shrink; open/growth is deferred because current deterministic question seeding would mix dock opening with tool-message hydration.

Human Review Status

Pending

Review Focus

  • primary-turn-descendant classification: assistant/message-part descendants inside visible primary ancestors must not be swallowed as residual diagnostics.
  • The failure snapshot should be directly readable from CI logs: action, entry value, hadRecentInput, source label/class, primary ancestor, before/after rects, scroll metrics, render mode, and row counts.
  • The workflow change should keep this as a head-only absolute gate, separate from the base/head perf comparator.

Risk Notes

  • Question dock open/growth is intentionally not covered here; [Task] Add deterministic runtime CLS gate for question dock open/growth #818 tracks that deterministic follow-up.
  • (conditional) No visible UI or copy changed; no screenshot/recording required.
  • (conditional) No platform, packaging, updater, signing, shell, or permissions surface changed.
  • (conditional) No docs, release notes, dependencies, credentials, deletion behavior, generated content, or user-local files changed. CI workflow routing is called out in Review Focus.

How To Verify

Runtime CLS unit: bun test --preload ./happydom.ts ./e2e/perf/runtime-cls-probe.unit.ts → 7 pass, 0 fail
Runtime CLS E2E: bun run test:e2e:local:runtime-cls → 3 pass
Typecheck: bun run typecheck → pass
Diff check: git diff --check → clean

Screenshots or Recordings

Not required; this PR adds runtime/perf test coverage and CI wiring only, with no visible UI or copy changes.

Checklist

How to use this checklist:

  • Tick a box by replacing [ ] with [x]. Do not edit, add, or remove items.
  • The bot-applied label items can only be honestly ticked AFTER the PR is opened and the labeler / priority-triage bots have run — return to the PR description and tick them then.
  • Most items are required. The few that are conditional are explicitly marked (conditional); for those, leave unticked if they truly do not apply and explain why in Risk Notes. All other items must be ticked before requesting human review.
  • Type label — this PR carries exactly one of bug, enhancement, task, documentation. Type labels are author-added; the labeler bot does NOT assign them. Add the label in the GitHub UI, then tick this.
  • Routing labels — this PR carries at least one of app, ui, platform, harness, ci. The labeler bot assigns these on PR open based on changed paths. Confirm the bot's choice (or override if wrong), then tick this.
  • Priority label — this PR carries exactly one of P0, P1, P2, P3. The priority-triage bot suggests one on PR open. Confirm or override, then tick this.
  • Human Review Status above is set to Pending, Approved by @<reviewer>, or Not required: <reason> (default is Pending; "not required" is restricted to bot-authored low-risk PRs).
  • I linked the related issue, or stated in Summary why there is no issue.
  • I described the review focus and any meaningful risks.
  • I replaced the example block in How To Verify with the real verification steps and the key result for each.
  • I did not introduce unrelated refactors, dependencies, generated files, or file changes beyond the stated scope.
  • (conditional) I manually checked visible UI or copy changes when needed, with screenshots or recordings. Leave unticked only if no visible UI or copy changed.
  • (conditional) I considered macOS and Windows impact for platform, packaging, updater, signing, paths, shell, or permissions changes. Leave unticked only if no platform/packaging surface was touched.
  • (conditional) I called out docs, release notes, dependencies, permissions, credentials, deletion behavior, generated content, or local file changes when relevant. Leave unticked only if none of those surfaces was touched.
  • I reviewed the final diff for unrelated changes and suspicious dependency changes.
  • I am targeting dev, and my PR title and commit messages use Conventional Commits in English.

Summary by CodeRabbit

  • Tests

    • Added end-to-end runtime layout-shift checks to detect CLS regressions during interactive flows.
    • New runtime CLS gate suite covering composer growth/shrink and dock interactions.
    • Added unit tests for CLS classification and failure-reporting logic.
  • Chores

    • Added CI workflow step and package scripts to run the runtime CLS e2e checks.

Review Change Stack

@Astro-Han Astro-Han added enhancement New feature or request ci Continuous integration / GitHub Actions P2 Medium priority app Application behavior and product flows ui Design system and user interface labels May 21, 2026
@github-actions github-actions Bot added task Narrow execution, audit, spike, migration, tracking, or upstream follow-up work and removed ui Design system and user interface labels May 21, 2026
@coderabbitai

coderabbitai Bot commented May 21, 2026

Copy link
Copy Markdown
Contributor

Warning

Rate limit exceeded

@Astro-Han has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 50 minutes and 10 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 820f2639-2b71-40cf-958f-e39aacf411f3

📥 Commits

Reviewing files that changed from the base of the PR and between 9b16cd2 and 13e94f8.

📒 Files selected for processing (3)
  • packages/app/e2e/perf/runtime-cls-gate.spec.ts
  • packages/app/e2e/perf/runtime-cls-probe.ts
  • packages/app/e2e/perf/runtime-cls-probe.unit.ts
📝 Walkthrough

Walkthrough

Adds a browser-injected Runtime CLS probe, Playwright lifecycle wrappers, unit tests for source classification/formatting, deterministic e2e gate specs for composer growth/shrink and dock close, and CI/npm script hooks to run the gate.

Changes

Runtime CLS probe and e2e gate

Layer / File(s) Summary
CLS data model and threshold constants
packages/app/e2e/perf/runtime-cls-probe.ts
Exports runtime CLS types (rects, scroll metrics, source classification, entries, snapshot, result) and the RUNTIME_CLS_PRIMARY_SHIFT_THRESHOLD with predicate helpers.
CLS source classification and failure filtering
packages/app/e2e/perf/runtime-cls-probe.ts
Rect conversion/visibility helpers, DOM labeling/ancestor path, primary-ancestor snapshot logic, classifyRuntimeClsSource implementation, collectRuntimeClsFailures filtered by primary threshold, and formatRuntimeClsFailure producing diagnostic + JSON snapshot.
Browser-side probe installation and PerformanceObserver
packages/app/e2e/perf/runtime-cls-probe.ts
Injected init script installs window.__pawwork_runtime_cls_probe, captures before-snapshots, observes layout-shift entries (or supports mockObserver), classifies and stores entries while active, truncates entries, and returns combined before/after result on stop().
Playwright probe lifecycle wrappers
packages/app/e2e/perf/runtime-cls-probe.ts
Provides installRuntimeClsProbe(page), startRuntimeClsProbe(page, action, options), and stopRuntimeClsProbe(page) wrappers that call the injected probe via page.evaluate with runtime checks.
Unit tests for CLS utilities
packages/app/e2e/perf/runtime-cls-probe.unit.ts
Bun unit tests that stub getBoundingClientRect to validate classifyRuntimeClsSource cases, threshold-only primary failure reporting, and formatRuntimeClsFailure content.
E2E helpers: seeding, scrolling, viewport prep
packages/app/e2e/perf/runtime-cls-gate.spec.ts (lines 1–251)
Deterministic session seed generator, settleFrames/seedRuntimeClsSession, timeline scroll/metrics and positioning helpers, revealRuntimeClsRows, centerVisibleMessageID, readPromptText/Height, and assertNoPrimaryRuntimeClsFailures.
E2E test suites: probe lifecycle and source gate
packages/app/e2e/perf/runtime-cls-gate.spec.ts (lines 253–461)
Probe lifecycle tests and the runtime CLS source gate with three scenarios (composer growth, composer shrink, question dock close) that start the probe, exercise UI interactions, stop the probe, and assert no primary runtime CLS failures.
CI workflow and npm script hooks
.github/workflows/perf-probe-baseline.yml, packages/app/package.json
Adds "Run runtime CLS gate (head)" workflow step running bun --cwd head/packages/app test:e2e:local:runtime-cls and new npm scripts test:e2e:local:runtime-cls and test:e2e:runtime-cls.

Sequence Diagram

sequenceDiagram
  participant PlaywrightTest
  participant Page as BrowserPage
  participant Probe as window.__pawwork_runtime_cls_probe
  participant PerfObs as PerformanceObserver
  PlaywrightTest->>Page: addInitScript(init probe)
  PlaywrightTest->>Probe: start(action, targetMessageID)
  Probe->>PerfObs: subscribe(layout-shift)
  Note over PerfObs,Probe: LayoutShift entries captured & classified
  PlaywrightTest->>Page: perform UI interactions (composer/dock)
  PlaywrightTest->>Probe: stop()
  Probe-->>PlaywrightTest: RuntimeClsResult (entries, snapshot)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related issues

  • #818 — Adds runtime CLS probe and gate machinery overlapping this PR's scope (seeds and gate scenarios match).
  • #747 — Original runtime CLS composer-height bug report; this PR implements focused runtime checks addressing that symptom class.

Possibly related PRs

  • Astro-Han/pawwork#609 — Related perf-gate/CI harness changes that extended perf probe runs and workflows.

Suggested labels

harness

Poem

🐰 I hop through layout shifts and count each gentle nudge,
Seeding turns and settling frames — I never hold a grudge.
I watch the composer grow and shrink with keen, polite delight,
Ensuring prompts and docks stay steady through the night. ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'test(app): add runtime CLS source gate' is concise, follows Conventional Commits format, and clearly summarizes the main change of adding a runtime CLS test gate.
Description check ✅ Passed The PR description comprehensively covers all required template sections: Summary, Why, Related Issue, Human Review Status, Review Focus, Risk Notes, How To Verify, and most checklist items are completed with detailed verification steps.
Linked Issues check ✅ Passed The code changes successfully meet the primary objectives from #814: a focused runtime CLS gate observing LayoutShift entries during bounded composer height changes, covering composer growth/shrink and question dock close, with deterministic test fixtures and clear CI diagnostics.
Out of Scope Changes check ✅ Passed All changes are directly scoped to #814 objectives: new runtime CLS probe, gate spec file, unit tests, workflow integration, and package.json script additions. No unrelated refactors, dependency changes, or out-of-scope modifications detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch pawwork/issue-814-runtime-cls-gate

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 and usage tips.

@github-actions github-actions 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.

Suggested priority: P2 (includes non-doc, non-test paths outside the low-risk bucket).

P1/P0 are reserved for maintainer confirmation. Please relabel manually if this is a release blocker, security issue, data-loss risk, or updater/runtime failure.

@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 performance monitoring framework for Cumulative Layout Shift (CLS) within E2E tests. It includes a browser-side probe that utilizes the PerformanceObserver API to detect and classify layout shifts, a new suite of Playwright tests targeting UI interactions like composer resizing and dock closure, and unit tests for the classification logic. The reviewer feedback focuses on improving the robustness of the probe by ensuring that initialization errors are not swallowed and that internal state is properly reset after each measurement to prevent data leakage between test iterations.

Comment thread packages/app/e2e/perf/runtime-cls-probe.ts Outdated
Comment thread packages/app/e2e/perf/runtime-cls-probe.ts Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (2)
packages/app/e2e/perf/runtime-cls-gate.spec.ts (2)

24-39: 💤 Low value

Rename test constants to SCREAMING_SNAKE_CASE.

runtimeClsSeedTurns, runtimeClsMinimumRows, runtimeClsMaximumMountedMessages, composerGrowthText, and question don't match the repo naming rule for constants in *.spec.ts files. As per coding guidelines, packages/app/e2e/**/*.spec.ts: Use SCREAMING_SNAKE_CASE for constants in tests.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/app/e2e/perf/runtime-cls-gate.spec.ts` around lines 24 - 39, Rename
the test constants to SCREAMING_SNAKE_CASE: change runtimeClsSeedTurns ->
RUNTIME_CLS_SEED_TURNS, runtimeClsMinimumRows -> RUNTIME_CLS_MINIMUM_ROWS,
runtimeClsMaximumMountedMessages -> RUNTIME_CLS_MAXIMUM_MOUNTED_MESSAGES,
composerGrowthText -> COMPOSER_GROWTH_TEXT, and question -> QUESTION; update all
references in this spec (e.g., where RUNTIME_CLS_SEED_TURNS,
RUNTIME_CLS_MINIMUM_ROWS, RUNTIME_CLS_MAXIMUM_MOUNTED_MESSAGES,
COMPOSER_GROWTH_TEXT, and QUESTION are used) so imports/uses remain consistent
and run the tests to ensure no breakage.

3-3: ⚡ Quick win

Don't mix fixture tracking with manual child-session deletion.

Once Line 318 registers child.id with project.trackSession(...), the finally block adds a second cleanup path for the same resource. That makes teardown order-dependent for no gain; keep the fixture-managed path and drop the explicit cleanupSession(...). As per coding guidelines, packages/app/e2e/**/*.spec.ts: Call project.trackSession(sessionID, directory?) and project.trackDirectory(directory) for any resources created outside the fixture so teardown can clean them up.

Suggested cleanup
-import { cleanupSession, seedSessionQuestion, withSession } from "../actions"
+import { seedSessionQuestion, withSession } from "../actions"
...
-      try {
-        await test.step("seed child question dock outside the measured window", async () => {
-          await llm.toolMatch(inputMatch({ questions: question }), "question", { questions: question })
-          await seedSessionQuestion(project.sdk, { sessionID: child.id, questions: question })
-        })
-        const targetMessageID =
-          await test.step("reveal a long visible parent timeline window with the dock open", async () => {
-            const target = await prepareRuntimeClsWindow(page, project, session.id)
-            await expect(dock).toBeVisible({ timeout: 30_000 })
-            await settleFrames(page, 6)
-            return target
-          })
-
-        const result = await test.step("close the child question dock under the runtime CLS probe", async () => {
-          await startRuntimeClsProbe(page, "question-dock-close", { targetMessageID })
-          await dock.getByRole("radio", { name: /Continue/i }).click()
-          await dock.getByRole("button", { name: /submit/i }).click()
-          await expect(dock).toHaveCount(0)
-          await expect(page.locator(promptSelector).first()).toBeVisible()
-          await settleFrames(page, 6)
-          return await stopRuntimeClsProbe(page)
-        })
-
-        await assertNoPrimaryRuntimeClsFailures(result)
-      } finally {
-        await cleanupSession({ sdk: project.sdk, sessionID: child.id })
-      }
+      await test.step("seed child question dock outside the measured window", async () => {
+        await llm.toolMatch(inputMatch({ questions: question }), "question", { questions: question })
+        await seedSessionQuestion(project.sdk, { sessionID: child.id, questions: question })
+      })
+      const targetMessageID =
+        await test.step("reveal a long visible parent timeline window with the dock open", async () => {
+          const target = await prepareRuntimeClsWindow(page, project, session.id)
+          await expect(dock).toBeVisible({ timeout: 30_000 })
+          await settleFrames(page, 6)
+          return target
+        })
+
+      const result = await test.step("close the child question dock under the runtime CLS probe", async () => {
+        await startRuntimeClsProbe(page, "question-dock-close", { targetMessageID })
+        await dock.getByRole("radio", { name: /Continue/i }).click()
+        await dock.getByRole("button", { name: /submit/i }).click()
+        await expect(dock).toHaveCount(0)
+        await expect(page.locator(promptSelector).first()).toBeVisible()
+        await settleFrames(page, 6)
+        return await stopRuntimeClsProbe(page)
+      })
+
+      await assertNoPrimaryRuntimeClsFailures(result)

Also applies to: 318-318, 320-346

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/app/e2e/perf/runtime-cls-gate.spec.ts` at line 3, The test registers
child.id with project.trackSession(...) but then also calls cleanupSession(...)
in the finally block—remove the explicit cleanupSession(...) call and rely on
project.trackSession(sessionID, directory?) to manage teardown; locate the
finally block around the child session creation (where child.id is registered)
and delete the manual cleanupSession(...) invocation so the fixture-managed
teardown is the sole cleanup path.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/app/e2e/perf/runtime-cls-probe.ts`:
- Around line 521-546: The PerformanceObserver setup currently swallows errors;
change the block around PerformanceObserver/observer.observe so that any
exception sets a persistent probe error/unsupported flag (e.g., set a
module-level boolean or an Error stored on this probe) instead of silently
catching, and ensure start() and stop() check that flag and throw a descriptive
error when the observer failed to initialize (include context like "layout-shift
observer failed to start"). Also preserve the existing
entries/startedAt/maxEntries logic but mark the probe as unusable when
observer.observe throws so CI fails closed rather than silently recording zero
shifts.
- Around line 304-305: installRuntimeClsProbe currently only calls
page.addInitScript which affects future navigations/frames, leaving an
already-loaded document unpatched and causing startRuntimeClsProbe to fail;
modify installRuntimeClsProbe to also inject the same probe into the current
document (for example by running the same probe function body via page.evaluate
or by using page.addScriptTag with the probe content) so the probe is present
immediately and in future navigations. Ensure the injected logic matches the
existing addInitScript payload and reference the functions
installRuntimeClsProbe and startRuntimeClsProbe when applying the change.

---

Nitpick comments:
In `@packages/app/e2e/perf/runtime-cls-gate.spec.ts`:
- Around line 24-39: Rename the test constants to SCREAMING_SNAKE_CASE: change
runtimeClsSeedTurns -> RUNTIME_CLS_SEED_TURNS, runtimeClsMinimumRows ->
RUNTIME_CLS_MINIMUM_ROWS, runtimeClsMaximumMountedMessages ->
RUNTIME_CLS_MAXIMUM_MOUNTED_MESSAGES, composerGrowthText ->
COMPOSER_GROWTH_TEXT, and question -> QUESTION; update all references in this
spec (e.g., where RUNTIME_CLS_SEED_TURNS, RUNTIME_CLS_MINIMUM_ROWS,
RUNTIME_CLS_MAXIMUM_MOUNTED_MESSAGES, COMPOSER_GROWTH_TEXT, and QUESTION are
used) so imports/uses remain consistent and run the tests to ensure no breakage.
- Line 3: The test registers child.id with project.trackSession(...) but then
also calls cleanupSession(...) in the finally block—remove the explicit
cleanupSession(...) call and rely on project.trackSession(sessionID, directory?)
to manage teardown; locate the finally block around the child session creation
(where child.id is registered) and delete the manual cleanupSession(...)
invocation so the fixture-managed teardown is the sole cleanup path.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 86e1e673-de88-4788-a4fd-48ec5ee209e5

📥 Commits

Reviewing files that changed from the base of the PR and between fda7f5f and 8896d04.

📒 Files selected for processing (5)
  • .github/workflows/perf-probe-baseline.yml
  • packages/app/e2e/perf/runtime-cls-gate.spec.ts
  • packages/app/e2e/perf/runtime-cls-probe.ts
  • packages/app/e2e/perf/runtime-cls-probe.unit.ts
  • packages/app/package.json

Comment thread packages/app/e2e/perf/runtime-cls-probe.ts Outdated
Comment thread packages/app/e2e/perf/runtime-cls-probe.ts Outdated
@github-actions

github-actions Bot commented May 21, 2026

Copy link
Copy Markdown

Perf delta summary

Comparator: pass

Profile / Scenario interaction median interaction worst long task max tbt frame gap p95 frame gap max jank count cls status
default / homepage-cold 32 -> 24 (-8) 64 -> 48 (-16) 70 -> 83 (+13) 20 -> 33 (+13) 16.8 -> 33.4 (+16.6) 166.6 -> 116.7 (-49.9) 3 -> 3 (0) 0 -> 0 (0) pass
default / long-session-input-lag 48 -> 48 (0) 48 -> 64 (+16) 0 -> 0 (0) 0 -> 0 (0) 16.7 -> 16.8 (+0.1) 16.8 -> 16.8 (0) 0 -> 0 (0) 0 -> 0 (0) pass
default / session-streaming-long 40 -> 48 (+8) 64 -> 64 (0) 0 -> 0 (0) 0 -> 0 (0) 16.8 -> 16.8 (0) 33.3 -> 33.3 (0) 0 -> 0 (0) 0 -> 0 (0) pass
default / tool-call-expand 16 -> 16 (0) 16 -> 24 (+8) 0 -> 0 (0) 0 -> 0 (0) 16.7 -> 16.8 (+0.1) 16.7 -> 16.8 (+0.1) 0 -> 0 (0) 0 -> 0 (0) pass
default / tool-default-open-heavy-bash 24 -> 24 (0) 32 -> 24 (-8) 64 -> 62 (-2) 14 -> 12 (-2) 50 -> 50 (0) 116.7 -> 116.6 (-0.1) 2 -> 2 (0) 0 -> 0 (0) pass
default / terminal-side-panel-open 48 -> 48 (0) 56 -> 56 (0) 0 -> 0 (0) 0 -> 0 (0) 33.3 -> 33.3 (0) 33.4 -> 33.3 (-0.1) 0 -> 0 (0) 0 -> 0 (0) pass
default / session-scroll-reading 24 -> 32 (+8) 48 -> 32 (-16) 0 -> 0 (0) 0 -> 0 (0) 16.8 -> 33.3 (+16.5) 16.8 -> 33.3 (+16.5) 0 -> 0 (0) 0 -> 0 (0) pass
low-end / session-scroll-reading-long 88 -> 72 (-16) 96 -> 112 (+16) 153 -> 148 (-5) 214 -> 221 (+7) 33.4 -> 33.4 (0) 150.1 -> 133.3 (-16.8) 9 -> 9 (0) 0.011 -> 0.011 (0) pass
low-end / session-timeline-recompute 208 -> 192 (-16) 232 -> 248 (+16) 194 -> 178 (-16) 398 -> 382 (-16) 166.7 -> 166.7 (0) 183.3 -> 166.7 (-16.6) 4 -> 4 (0) 0.397 -> 0.474 (+0.077) warn: cls_delta
low-end / concurrent-shimmer-extreme 0 -> 0 (0) 0 -> 0 (0) 0 -> 0 (0) 0 -> 0 (0) 16.7 -> 16.8 (+0.1) 16.8 -> 16.8 (0) 0 -> 0 (0) 0 -> 0 (0) pass

@Astro-Han Astro-Han removed the enhancement New feature or request label May 21, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/app/e2e/perf/runtime-cls-gate.spec.ts`:
- Around line 277-305: The test currently never emits a layout-shift while the
probe is active, so add a synthetic entry emission between
startRuntimeClsProbe(...) and the first stopRuntimeClsProbe(...) to prove
active-window capture: after await startRuntimeClsProbe(page, "first-window", {
targetMessageID: "msg-1" }) call, invoke the page.evaluate block that calls
window.__emitRuntimeClsEntry with a layout-shift entry (targeting the same node)
and then call stopRuntimeClsProbe to assert that the first stop returned an
entries array containing that emitted entry; keep the existing post-stop
evaluation that emits another entry and asserts repeatedStop?.entries is empty
to verify post-stop ignores.

In `@packages/app/e2e/perf/runtime-cls-probe.ts`:
- Around line 518-523: The direct-primary branches that return for
element.matches("[data-message-id]") and
element.matches('[data-component="session-turn"]') need the same visibility
gating used elsewhere: require primaryAncestor?.visibleBefore &&
primaryAncestor?.visibleAfter before classifying as "primary-message-wrapper" or
"primary-turn" so off-screen wrappers/turns aren't treated as visible timeline
content; update the runtime classifier (the ifs using element.matches(...)) and
mirror the identical visibility check in the exported classifier above so
collectRuntimeClsFailures() and the unit-helper path remain aligned.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 240166cb-2cee-4184-985d-6d5f48c23923

📥 Commits

Reviewing files that changed from the base of the PR and between 8896d04 and 9b16cd2.

📒 Files selected for processing (2)
  • packages/app/e2e/perf/runtime-cls-gate.spec.ts
  • packages/app/e2e/perf/runtime-cls-probe.ts

Comment thread packages/app/e2e/perf/runtime-cls-gate.spec.ts
Comment thread packages/app/e2e/perf/runtime-cls-probe.ts Outdated
@Astro-Han

Copy link
Copy Markdown
Owner Author

Addressed CodeRabbit's two nitpick comments in 261b767:

  • Renamed the runtime CLS spec constants to SCREAMING_SNAKE_CASE.
  • Removed the duplicate manual cleanupSession path and rely on project.trackSession(...) for the child session cleanup.

Follow-up CodeRabbit inline comments were addressed in 13e94f8:

  • The lifecycle self-test now proves active-window synthetic layout-shift capture before checking post-stop ignore behavior.
  • Direct primary wrapper/turn sources now require visibleBefore && visibleAfter in both exported and browser-side classifiers.

Verification run locally:

  • bun test --preload ./happydom.ts ./e2e/perf/runtime-cls-probe.unit.ts → 8 pass
  • bun run test:e2e:runtime-cls -- --grep "runtime CLS probe lifecycle" → 4 pass
  • bun run typecheck → pass
  • git diff --check → clean
  • bun run test:e2e:local:runtime-cls → 7 pass

@Astro-Han
Astro-Han merged commit 9d91b93 into dev May 21, 2026
26 checks passed
@Astro-Han
Astro-Han deleted the pawwork/issue-814-runtime-cls-gate branch May 21, 2026 09:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

app Application behavior and product flows ci Continuous integration / GitHub Actions P2 Medium priority task Narrow execution, audit, spike, migration, tracking, or upstream follow-up work

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature] Add controlled runtime CLS gate for composer height changes

1 participant