Skip to content

feat(browser): flash the page viewport size while the pane resizes - #31145

Merged
AmoabaKelvin merged 15 commits into
stablyai:mainfrom
chucoding:feat/browser-viewport-size-indicator
Oct 29, 2026
Merged

AmoabaKelvin merged 15 commits into
stablyai:mainfrom
chucoding:feat/browser-viewport-size-indicator

Conversation

@chucoding

@chucoding chucoding commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor
Files Added Deleted Net
Test 1 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​127 0 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​127
Prod 10 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​190 $\color{#cf222e}{\Huge{\mathbf{−}}}$​81 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​109

ELI5

When you drag the built-in browser narrower or wider, a small 1024 × 768 label now appears in the top-right corner for about a second, like Chrome DevTools does. The number is the page's own innerWidth × innerHeight, so you can land exactly on a CSS breakpoint by dragging.

What Changed

  • Before: resizing the browser pane (split divider, sidebar, or window) gave no hint of the page's size. You had to guess or run innerWidth in DevTools.
  • After: each resize of the active page flashes the page's size in the existing top-right zoom badge, then it fades after the same 1.4 s the zoom badge uses. It does not show when a tab opens or is shown again. It also stays off while a viewport preset, a phone driving the page or a remote viewer sets the page's metrics, because then the pane box is no longer the page's viewport.
  • Mechanism:
    • A ResizeObserver on the page's host box, attached only while the page is active and free-sized, so it costs nothing when idle.
    • The first measurement after each activation is only a baseline, and a parked pane measures 0 × 0, so tab opens and switches never count as resizes.
    • The size is computed the way Chromium sizes the guest: the host box in window DIP (CSS px × UI zoom) rounded up to whole DIPs, times the screen scale (devicePixelRatio / UI zoom), then Blink's AdjustForAbsoluteZoom::AdjustInt with zoom = screen scale × the guest's live zoom level.
    • Until the guest's zoom can be read (before dom-ready), no size is flashed, but the baseline is kept so the first resize after a tab opens is still announced.
  • The zoom badge (BrowserPageZoomIndicator) became BrowserPageFeedbackBadge. It owns its label and fade timer behind a ref handle, so zoom and size share one badge (the latest change wins), and a resize drag re-renders only the badge, not the whole page pane.
  • Out of scope: the client-hosted browser pane (ClientHostedBrowserPagePane) only adopts the renamed badge. An agent's orca browser viewport override is applied in main with no renderer signal, so the readout can show the box size there.

Why

  • One badge, not a second overlay: both would sit in the same corner. Two badges would overlap during a resize-while-zoomed and double-announce to screen readers.
  • The guest's live zoom level, read on each resize: changing Orca's UI zoom also changes the guest webview's zoom level, and the result (for example 1, 120%) is not one of the page zoom presets. getZoomLevel() is a synchronous IPC, made only while the pane is actually resizing.
  • Chromium's sizing and Blink's rounding, not Math.round: plain rounding is off by 1 px at many sizes. Each step above was checked against a live 2× Mac across 100/90/80/50/110/125% page zoom and UI zoom +1. The ceil and screen-scale steps came from mismatches in that grid.
  • Computed from the host box, not by asking the page: executeJavaScript('[innerWidth, innerHeight]') is async. It can answer before the guest has re-laid out, which leaves a stale number at the end of a drag.

Linked Issue

Fixes #31014

Visual Proof

Hidden dev build over CDP, macOS 2× screen, a local page that prints its own innerWidth × innerHeight. Same script in each video: widen the left sidebar, open the right sidebar, hide the left sidebar, close the right sidebar.

Before (main): the page size changes and nothing appears.

before.mp4

After (100% page zoom): the badge flashes 859 × 609, 509 × 609, 929 × 609, 1279 × 609, matching the page's text, and fades.

after.mp4

After (90% page zoom): the badge flashes 954 × 676, 565 × 676, 1032 × 676, 1421 × 676, matching the page's text.

after-zoom.mp4

Testing

  • I manually tested these changes locally

  • Automated tests added/updated, or explained why not below

  • Live grid (hidden dev build, 2× Mac, badge vs the guest's real innerWidth × innerHeight read over its own CDP target, page zoom set with real Cmd+−/=/0 keys):

    Zoom Window and sidebar resizes With a split open (fractional boxes)
    100% 25/25 12/12
    90% 25/25 12/12
    80% 25/25 not run
    50% not run 12/12
    110% 25/25 12/12
    125% 25/25 12/12
    100% at UI zoom +1 23/23 12/12
    90% at UI zoom +1 15/15 12/13, the miss fixed by the round-up-to-DIP step and pinned in the unit test
    • No badge on opening a tab, loading a page, or switching to another tab and back. No badge while a preset is applied, resized under, or cleared. It fades 1.4 s after the last resize. The zoom shortcut still shows 90%/110%/100%.
  • Unit tests (use-browser-page-viewport-size-feedback.test.tsx): the conversion against sizes measured from real guests (1× and 2× screens, UI zoom, fractional boxes), and the hook's baseline rules (first measurement, before dom-ready, parked at 0 × 0, reactivation).

  • pnpm test src/renderer/src/components/browser-pane and changed-file oxlint/oxfmt pass. The e2e spec from the first version was dropped to keep CI time down; the live grid above covers what it checked.

  • Not verified: Linux, Windows, and 1.25×/1.5× screen scales.

AI Disclosure

Implemented with Claude Code (Claude Opus 5.5) under my direction, including the tests and this description. I reviewed the diff and the test results.

Review

To try (worktree /Users/kelvinamoaba/orca/workspaces/orca/browser-viewport-size-readout):

  • Open a browser tab on any site and drag the sidebar edge or a split divider. A W × H badge shows top right, matches innerWidth × innerHeight, and fades about 1.4 s after you stop.
  • Press Cmd+- to 90% and drag again: the numbers still match the page. Switch tabs and back: no badge.
  • Pick a device preset from the toolbar menu and resize: no badge.

Agent skill upstream boundary

  • Not applicable, or this change copies or mechanically translates no upstream skill-installer source, tests, fixtures, registry entries, path tables, comments, or documentation.

Notes

  • Security: no new IPC or input surface. The badge is pointer-events-none and uses existing tokens.
  • Cross-platform: the conversion uses Chromium's own sizing and rounding with the live devicePixelRatio, so it should hold on fractional Windows/Linux scales, but only a 2× macOS screen was measured.
  • SSH/remote: the desktop pane renders locally in all cases. The client-hosted pane only gets the badge rename. Mobile: the phone has no resizable pane, and the readout is off while a phone drives the page.
  • Performance: nothing runs while idle or for inactive pages. While a pane is being resized, it's one ResizeObserver callback and one getZoomLevel() per frame, and only the badge re-renders.

Checklist

  • This PR is small and focused
  • I explained what changed and why (ELI5, the user-facing before/after, the mechanism, and why over the alternatives)
  • Before/after screenshots or videos attached for UI changes, or N/A with reason
  • Self-reviewed for correctness, security, and performance
  • Cross-platform, SSH/remote, and path/shortcut impact considered (or N/A)
  • pnpm lint, pnpm typecheck, pnpm test, and pnpm build pass (or CI will cover; local preferred). I ran typecheck, the browser-pane tests, changed-file lint, and the e2e build; the full pnpm lint and pnpm test are left to CI.

🤖 Generated with Claude Code

@coderabbitai

coderabbitai Bot commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

No actionable comments were generated in the recent review.

ℹ️ Recent review info
⚙️ Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: af5e92c7-f1c0-46f9-9d8b-9e0906d8f423
📥 Commits

Reviewing files that changed from the base of the PR and between 6cb5087327a666c7d19ab4cb08724c25bf560a7c and b521044eb9776cddad7e48e26c68a9d11841b8a1.

📒 Files selected for processing: 3
  • src/renderer/src/components/browser-pane/host-guest/use-browser-page-viewport-size-feedback.test.tsx
  • src/renderer/src/components/browser-pane/host-guest/use-browser-page-viewport-size-feedback.ts
  • tests/e2e/browser-viewport-size-badge.spec.ts
🚧 Files skipped from review as they are similar to previous changes: 3
  • src/renderer/src/components/browser-pane/host-guest/use-browser-page-viewport-size-feedback.test.tsx
  • tests/e2e/browser-viewport-size-badge.spec.ts
  • src/renderer/src/components/browser-pane/host-guest/use-browser-page-viewport-size-feedback.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 8 remain after this review.

Walkthrough

The browser pane now reports guest viewport dimensions after content resizes. A shared feedback badge displays either the page zoom percentage or formatted viewport dimensions. The implementation converts measurements using UI and page zoom, and suppresses feedback for initial, unchanged, and zero-sized measurements. Unit, hook, and end-to-end tests cover conversions, badge timing, pane activity, resizing, and viewport presets.

Priority: Low

Merge Risk: Minimal · up to b5210

The viewport-size badge is ready to merge after normal checks; no remaining issue is established.

Pre-merge checks: Passed 4
Check name Status Explanation
Title check Passed The title is concise, specific, and accurately describes the primary user-facing change: showing viewport-size feedback while the browser pane resizes.
Linked Issues check Passed Issue #31014 is an active direct link. The PR adds a non-interactive top-right feedback badge for the built-in browser pane. A ResizeObserver detects real size changes. The implementation converts hos…
Out of Scope Changes check Passed The changes stay within issue #31014. The badge rename, shared zoom and viewport feedback state, conversion helpers, resize hook, and tests directly implement or verify the requested behavior. The cli…
Description check Passed The description is thorough and follows the template closely. It has an ELI5, What Changed, Why, a linked issue (Fixes #31014), Visual Proof with before/after videos, detailed Testing with a live grid…
Full details: Linked Issues check

Explanation

Issue #31014 is an active direct link. The PR adds a non-interactive top-right feedback badge for the built-in browser pane. A ResizeObserver detects real size changes. The implementation converts host dimensions to guest CSS pixels with UI and live page zoom. It suppresses activation baselines, tab-switch feedback, preset resize feedback, and unreadable zoom values. The shared timer fades feedback after the existing interval. Unit and end-to-end tests cover conversion, zoom, activation, presets, inactive panes, and first-resize behavior.

Full details: Out of Scope Changes check

Explanation

The changes stay within issue #31014. The badge rename, shared zoom and viewport feedback state, conversion helpers, resize hook, and tests directly implement or verify the requested behavior. The client-hosted pane only adopts the renamed badge and does not add viewport flashing, which matches the stated scope. No unrelated product behavior is demonstrated.

Full details: Description check

Explanation

The description is thorough and follows the template closely. It has an ELI5, What Changed, Why, a linked issue (Fixes #31014), Visual Proof with before/after videos, detailed Testing with a live grid and unit tests, an AI Disclosure, the Review section, the upstream boundary checkbox marked not applicable, Notes covering security, cross-platform, SSH/remote, mobile, and performance, and a checklist. The only unchecked item is the full pnpm lint/test/build run, which the author explicitly defers to CI and discloses. Minor gap: the Linked Issue section uses a 'Fixes #31014' line that the template expects, which is present. Overall the description is mostly complete and passes.

  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

@chucoding
chucoding force-pushed the feat/browser-viewport-size-indicator branch from 54cede5 to 6762c6a Compare October 22, 2026 04:22

@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


ℹ️ Review info
⚙️ Run configuration
  • Configuration used: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 32e2b81e-cd93-480a-b8a1-8e3076502dd8
📥 Commits

Reviewing files that changed from the base of the PR and between 54cede5bfcad1cd22c14926e6c975bfaef6b187c and 6762c6ac3a751bd6517f6aedc7a02ba5f5c4116d.

📒 Files selected for processing: 10
  • src/renderer/src/components/browser-pane/ClientHostedBrowserPagePane.tsx
  • src/renderer/src/components/browser-pane/assemble-chrome/browser-page-pane.tsx
  • src/renderer/src/components/browser-pane/assemble-chrome/browser-page-viewport-overlays.tsx
  • src/renderer/src/components/browser-pane/host-guest/browser-page-viewport-size.test.ts
  • src/renderer/src/components/browser-pane/host-guest/browser-page-viewport-size.ts
  • src/renderer/src/components/browser-pane/host-guest/use-browser-page-viewport-size-feedback.test.tsx
  • src/renderer/src/components/browser-pane/host-guest/use-browser-page-viewport-size-feedback.ts
  • src/renderer/src/components/browser-pane/host-guest/use-browser-page-zoom-feedback.test.ts
  • src/renderer/src/components/browser-pane/host-guest/use-browser-page-zoom-feedback.ts
  • src/shared/browser-page-zoom.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 9 remain after this review.

@chucoding

Copy link
Copy Markdown
Contributor Author

Review follow-up (CodeRabbit review 5609286361)

✅ Fixed (2)

Location Change
inline browser-page-pane.tsx:157 A running flash is hidden when the page turns inactive (hook cleanup) or is parked at 0 × 0; the next size after either is a fresh baseline.
inline use-browser-page-viewport-size-feedback.ts:16 When the guest zoom cannot be read (no webview, or before dom-ready), the measurement is skipped instead of computed at the default zoom.

❌ Rebutted (1)

Location Reason
summary Docstring Coverage (39% < 80%) This repo's AGENTS.md asks for comments only where the code does not describe itself, so docstrings were not added just to reach the threshold.

⏭ Deferred (0)

Location Reason Issue

Commit: 6cb5087327a (pushed to the PR branch)

Tests: pnpm test src/renderer/src/components/browser-pane (137 files, 1067 tests), pnpm tc, oxlint, pnpm run check:code-quality:changed, and tests/e2e/browser-viewport-size-badge.spec.ts (headless, 3 runs) all passed. The inactive and parked cases are covered by unit tests only.


Generated by counter-review

@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: 1


ℹ️ Review info
⚙️ Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 0ce2d67b-6556-472b-a292-f1377eb4d321
📥 Commits

Reviewing files that changed from the base of the PR and between 6762c6ac3a751bd6517f6aedc7a02ba5f5c4116d and 6cb5087327a666c7d19ab4cb08724c25bf560a7c.

📒 Files selected for processing: 4
  • src/renderer/src/components/browser-pane/assemble-chrome/browser-page-pane.tsx
  • src/renderer/src/components/browser-pane/host-guest/use-browser-page-viewport-size-feedback.test.tsx
  • src/renderer/src/components/browser-pane/host-guest/use-browser-page-viewport-size-feedback.ts
  • src/renderer/src/components/browser-pane/host-guest/use-browser-page-zoom-feedback.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 9 remain after this review.

@chucoding

Copy link
Copy Markdown
Contributor Author

Review follow-up (CodeRabbit review 5623033503)

✅ Fixed (1)

Location Change
inline use-browser-page-viewport-size-feedback.ts:57 The baseline is kept as the host box size while the guest zoom is unreadable, and both sizes are converted at the zoom readable on the next measurement, so a new tab's first resize is announced.

❌ Rebutted (0)

Location Reason

⏭ Deferred (0)

Location Reason Issue

Commit: b521044eb97 (pushed to the PR branch)

Tests: pnpm test src/renderer/src/components/browser-pane (137 files, 1068 tests), pnpm tc, oxlint, pnpm run check:code-quality:changed, and tests/e2e/browser-viewport-size-badge.spec.ts (headless, 3 runs) all passed. The new e2e assertion for the first resize after opening a tab fails on the previous commit and passes on this one.

Reviews 5623053226 and 5623055638 were CodeRabbit confirmations on the two threads from the previous round; nothing to address there.


Generated by counter-review

chucoding and others added 7 commits October 26, 2026 15:36
Resizing the built-in browser pane gave no sign of the page's size, so
landing on a CSS breakpoint meant guessing or reading innerWidth in
DevTools. The existing zoom badge now also flashes the guest's
innerWidth × innerHeight after each resize, so zoom and size share one
corner instead of stacking. The size comes from the host box, UI zoom
and the guest's live zoom level, because a UI zoom change overwrites
the guest's zoom behind the pane's remembered level.

Refs stablyai#31014

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The issue promises the size badge stays hidden when a tab opens or is
shown again, but the e2e only checked it after the 1.4s fade had
already passed, so a stray flash would go unnoticed. A renderer-side
counter now records every size flash, and the hook test covers a pane
that is hidden and shown again at a new size.

Refs stablyai#31014

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Zoom and size feedback now share one label and one fade timer, so the
latest change wins, but nothing exercised that hook directly. The new
test checks that a size flash replaces the zoom label and restarts the
fade instead of letting the earlier timer hide it early.

Refs stablyai#31014

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
After rebasing onto per-site zoom (stablyai#31621), zoom levels snap to Chrome
presets such as 90%, so the badge drifted a pixel from innerWidth: the
zoom factor snapped levels that UI zoom sets freely, and rounding did
not match Blink. The factor now uses the guest's level as is, and the
size mirrors Blink's AdjustForAbsoluteZoom::AdjustInt on the integer
DIP size, checked against the e2e's measured guest sizes.

Refs stablyai#31014

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Before dom-ready the guest's zoom cannot be read, and a saved site zoom
lands at that moment, so sizing it at the default zoom could flash a
wrong number. A flash left running when the page went inactive or was
parked at 0×0 could also come back stale, since nothing measures it
then. Both cases now skip or hide the flash, because no size is better
than a misleading one.

Refs stablyai#31014

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Skipping measurements until the guest zoom is readable also dropped the
baseline, and a new tab's opening measurement usually arrives before
dom-ready, so the first resize afterwards only set a baseline and never
showed the badge. The baseline is now kept as the host box size and
converted at the zoom readable when the next measurement comes, so no
zoom is guessed and the first real resize is still announced.

Refs stablyai#31014

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The case asserted that the label reads exactly `1024 × 768`, which
restates the format string rather than any behavior, and main now
treats such copied-string tests as low value (stablyai#33736, stablyai#33778).

Refs stablyai#31014

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@chucoding
chucoding force-pushed the feat/browser-viewport-size-indicator branch from b521044 to 19432a1 Compare October 26, 2026 06:46
… that don't prove it

Moves the guest-size conversion into the hook that uses it, inlines the
one-use label formatter and predicate, and drops the e2e spec, the badge
timer test and the predicate tests. The conversion keeps its test against
sizes measured from a real guest.
…of the pane's render

A preset pins the page size, and mobile emulation's layout viewport can
differ from the host box, so the readout only runs on a free-sized pane.
The badge now owns its label and fade state behind a ref handle, so a
resize drag re-renders only the badge instead of the whole page pane.
@AmoabaKelvin

Copy link
Copy Markdown
Collaborator

Thanks @chucoding, I'm taking this over to land it and pushed commits on top. Changes: merged main in, folded the size helpers into the hook, moved the label/fade state into the badge so a drag re-renders only the badge, and skipped the readout while a preset, a phone or a remote viewer sets the page metrics (mobile emulation's layout viewport can differ from the box). I also dropped the e2e spec and the predicate/timer tests to keep CI lean. The conversion test with your measured sizes stays.

Blink rounds the guest viewport in device pixels with the screen scale
times page zoom as its factor, so ignoring the screen scale left the
badge 1px off at most non-100% zoom levels on 2x screens.
@greptile-apps

greptile-apps Bot commented Oct 29, 2026 •

Copy link
Copy Markdown

RetriggerView in GreptileConfidence Score: 5/5

[Medium impact] The latest change appears safe to merge; the earlier missing preset-size flash remains a non-blocking issue.

Findings

  1. P2 Preset size never appears ▶

Summary

This PR adds a shared badge for browser zoom and viewport-size feedback.

  • Resizing the desktop browser pane flashes the page’s viewport size.
  • Zoom and viewport-size updates use one feedback badge.

Reviews (4) · Last reviewed commit: "fix(browser): round the readout's guest ..." · Reviewed by Greptile

Chromium 150's AdjustForAbsoluteZoom::AdjustInt adds 0.5 in float math
before dividing when zoomed in and truncates with a 0.01 slack, so the
+1 rule showed the badge 1px wide at many zoomed-in sizes.
… screen scale

Chromium rounds a guest frame to whole DIPs and only then scales it to
device pixels, so a fractional split width read one device pixel short.
A live sweep at 1.2 UI zoom on a 2x screen showed Chromium sizes the
guest frame as the ceiling of the box in DIPs, not the nearest DIP.
@AmoabaKelvin
AmoabaKelvin merged commit b2b7042 into stablyai:main Oct 29, 2026
57 of 58 checks passed
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.

[Feature]: Show the page viewport size (width × height) while resizing the built-in browser pane

2 participants