Skip to content

v1.15.3.0 feat(browse): viewport auto / reset to unpin a fixed viewport (#1059)#1226

Open
gregario wants to merge 3 commits intogarrytan:mainfrom
gregario:feat/1059-viewport-auto
Open

v1.15.3.0 feat(browse): viewport auto / reset to unpin a fixed viewport (#1059)#1226
gregario wants to merge 3 commits intogarrytan:mainfrom
gregario:feat/1059-viewport-auto

Conversation

@gregario
Copy link
Copy Markdown
Contributor

Summary

Closes #1059. Once browse viewport WxH calls Playwright's setViewportSize, the viewport is pinned. There's no page-level API to undo it — the only way back to window-following is to rebuild the context. Skills that called \$B viewport for responsive testing or breakpoint benchmarks were leaving the browser locked at that size for the rest of the session, with the visible Chrome window noticeably larger than what Claude was rendering into.

Before

$ browse viewport 1280x520
$ browse js \"window.innerWidth + 'x' + window.innerHeight\"
1280x520
# resize Chrome window manually → viewport stays 1280x520
# only escape: browse restart (kills Chrome)

After

$ browse viewport auto
Viewport unpinned — now follows window size (context recreated; refs and load-html content replayed).

# headed mode (real Chrome via /connect-chrome):
$ browse viewport auto
Viewport synced to current window size (1920x1080). Re-run \`viewport auto\` after resizing.

Connection-mode-aware unpin

Mode Path Notes
launched (headless) full recreateContext() with viewport: null cookies/storage/URLs/tab-ownership preserved via existing save/restore. True follow restored.
headed (launchPersistentContext) one-shot resync via page.evaluate(window.innerWidth/Height) + setViewportSize persistent context can't be safely rebuilt (tied to user's Chrome window). Re-run after a resize. Documented in command help.

Implementation notes

  • New viewportPinned: boolean on BrowserManager (default true, matches launch).
  • setViewport(w, h) sets it true so re-pinning after auto works.
  • recreateContext() builds two distinct context-option shapes. Important: Playwright rejects deviceScaleFactor with viewport: null, so the unpinned path omits it. Same constraint the existing headed-launch path already obeys (line 359 — viewport: null with no DSF).
  • --scale plus auto/reset is rejected at the parser — scale needs an explicit WxH to multiply.

Test plan

  • bun test browse/test/commands.test.ts — 227 pass (4 new tests in dedicated end-of-file describe block).
    • viewport auto unpins a fixed size — pins, unpins, re-pins to a different size; the re-pin takes effect.
    • viewport reset is an alias for auto — same path via the alias.
    • viewport auto preserves cookies across context recreation — sets cookie, unpins, cookie still present.
    • viewport auto rejects --scale combination — input validation.
  • Tests live at the end of the file because context recreation renumbers tab IDs, and an existing test in the Tabs describe block hardcodes tab id 1. Running before the Tabs block would break that test.
  • Headed-mode path covered by the implementation but not by automated tests (existing tests don't run a real Chrome window).

Files

  • browse/src/browser-manager.tsunpinViewport() + viewportPinned tracking + recreateContext branching
  • browse/src/write-commands.ts — viewport auto/reset parsing + --scale rejection
  • browse/src/commands.ts — usage docstring
  • browse/test/commands.test.ts — 4 new tests
  • VERSION, CHANGELOG.md — release v1.15.3.0

🤖 Generated with Claude Code

gregario and others added 3 commits April 26, 2026 20:56
Closes garrytan#1059.

Once `browse viewport WxH` calls Playwright's `setViewportSize`, the
viewport is pinned. There's no page-level API to undo it — the only
way back to window-following is to rebuild the context. Skills that
call `$B viewport` for responsive testing or breakpoint benchmarks
were leaving the browser locked at that size for the rest of the
session, with the visible Chrome window visibly larger than the
viewport Claude was rendering into.

Add `browse viewport auto` (alias `reset`) that takes the right path
per connection mode:

- **launched (headless):** full `recreateContext()` with `viewport:
  null`. Cookies, storage, URLs, and tab ownership ride through the
  standard save/restore. The new context truly follows window default.
- **headed (persistent context):** `recreateContext()` is forbidden
  (the persistent context is tied to the user's Chrome window, can't
  be safely rebuilt). Instead, read `window.innerWidth` /
  `innerHeight` via `page.evaluate()` and resync the viewport once
  to match. Snapshot, not true follow — re-call after resize. The
  command help documents this constraint.

Implementation notes:

- New `viewportPinned: boolean` field on BrowserManager (default true,
  matching launch behavior).
- `setViewport(w, h)` flips it back on so a re-pin after auto works.
- `recreateContext()` builds two distinct contextOptions shapes: pinned
  uses `{viewport, deviceScaleFactor}`; unpinned uses `{viewport: null}`
  alone. Playwright rejects `deviceScaleFactor` with `viewport: null`
  (same constraint the existing headed-launch path already obeys).
- `--scale` plus `auto`/`reset` is rejected — scale needs an explicit
  WxH to multiply.

Tests live in a separate describe block at the end of commands.test.ts
because context recreation renumbers tab IDs, and an existing test in
the Tabs block (garrytan#664) hardcodes tab 1.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
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.

browse: add viewport auto / viewport reset to unpin fixed viewport

1 participant