feat(web): configurable browser defaults in Settings → Integrations#7082

merged8
main
browser-viewport-defaults
openedAug 15, 2026byJJulius Marminge
View on GitHubTwo-way sync enabledPR last imported 3d ago

Adds a Settings → Integrations page whose first section is Browser, holding the defaults a preview tab opens at.

The preview browser always opened in fill mode, at 100% zoom, following the system appearance, with no way to say otherwise. That's rarely what you want if you work in responsive mode against a handful of known viewports.

What's configurable

SettingOptions
Default viewportFill panel, Responsive (custom W×H), or any device preset — with an orientation toggle
Default zoomChrome's zoom ladder, 25%–500%
Default appearanceSystem / Light / Dark
Auto-show floating previewWhether an agent-opened preview pops the floating player into view

Notes for review

Defaults apply at creation, not after. The viewport travels with preview.open and zoom/appearance with createTab. Applying them post-registration would paint one frame at 100%/system first — a visible flash on every tab open.

Both entry points collapse onto one setting. This removes an existing inconsistency where hand-opened tabs fell back to 1024×768 and agent-opened tabs to 1280×800. Agents keep a 1280×800 fallback, but only when no default is configured: a fill-mode tab is sized by whatever the panel happens to be, and agents assert against what they open.

Rotation preserves preset identity. resolvePreviewViewport already stores rotated presets as the id plus swapped dimensions, so a rotated iPad Mini stays an iPad Mini rather than decaying into a custom size.

Dimensions commit on blur, via onValueCommitted. Typing 2560 passes through 256, itself a legal dimension, so a change-handler would persist that intermediate size and rewrite settings on every keystroke.

Web clients get the defaults disabled, framed as one dimmed block. They're client-local settings for a Chromium guest only the desktop app hosts — editing them from a browser tab would write preferences belonging to a different client, reading as though the desktop app had been configured when it hadn't. Disabled rather than hidden so the reason is visible.

Testing

Typecheck and lint clean; full suite passing. Exercised in a running dev instance: all controls work, values round-trip through a reload, and settings search indexes every row.

Not covered: a desktop tab actually opening at the configured size. The preview is desktop-only and I verified the settings layer in a web build. The consumption path is traced — registerWebview re-asserts from tab state and navigate preserves both fields, so only closeTab resets — but that's inspection, not a runtime check.

Note

Add configurable browser defaults (viewport, zoom, appearance, floating preview) to Settings → Integrations

  • Adds a new /settings/integrations route and IntegrationsSettingsPanel with a Browser section where users can configure default viewport, zoom, appearance, and auto-show floating preview behavior; controls are disabled outside Electron.
  • Stores the four new browser defaults in ClientSettingsSchema and includes them in the "Restore Defaults" flow and settings search.
  • New preview sessions (opened via automation or manually) now use the user's configured default viewport instead of always defaulting to fill; shouldOpenPreviewMiniPlayer respects the autoShowFloatingPreview preference when the agent doesn't explicitly specify open/show.
  • acquireDesktopTab delays tab creation until client settings are hydrated and passes initial zoom and color scheme to createTab, so tabs no longer start at 100% zoom with the system appearance before defaults are applied.
  • normalizePreviewOpenInput no longer forces open: true by default — both open and show remain unset when neither is provided by the agent.

Macroscope summarized 6489255.


Note

Medium Risk Touches preview open/automation, desktop IPC tab creation, and server MCP normalization across many entry points; behavior changes for agent floating preview and initial viewport sizing, though defaults are conservative and explicit agent flags still win.

Overview Adds Settings → Integrations → Browser so users can set default preview viewport, zoom, appearance, and whether agent opens should auto-show the floating preview. These live in new client settings fields and apply when a tab is opened without its own size or visibility.

Defaults are applied at creation so tabs do not flash at 100% zoom / system theme or reflow from fill mode: viewport goes on preview.open, zoom and color scheme on desktop createTab (with zoom clamped to the preset ladder). Imperative open paths await client settings hydration before reading defaults.

Hand-opened tabs and agent preview_open both use the same configured viewport; the server no longer defaults open to true, so unstated agent visibility follows browserAutoShowFloatingPreview on the desktop client. Agent tabs still get a fixed 1280×800 fallback only when the session is still in fill mode after open.

Device-toolbar toggle from fill mode now uses the configured default viewport when set; restore-dirty detection uses structural viewport equality. Web builds show the controls disabled in a framed block; settings search and restore include the new rows.

Reviewed by Cursor Bugbot for commit 6489255f56d9ac7435a7189f827a687c1650ab93. Bugbot is set up for automated code reviews on this repo. Configure here.

Synced from GitHub

Important

Review skipped

Auto reviews are disabled on this repository. 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 UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 527f8926-ffd2-424c-a26f-9e42bbb43f03

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

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

MmacroscopeappBotreviewed · 1 inline threadAug 15, 2026
Synced from GitHub

One layout finding in the new Integrations settings panel. Everything else (primitive usage, variants, ScreenRotationIcon extraction, notice styling, route/nav wiring) matches the existing settings patterns.

Posted via Macroscope — UI Consistency

apps/web/src/components/settings/IntegrationsSettings.tsxlines 176–182Outdated · 7e59042Open thread
……@@ -0,0 +1,432 @@
179+ onValueChange={selectViewport}
180+ disabled={disabled}
181+ >
182+ <SelectTrigger className="w-full sm:w-44" aria-label="Default browser viewport">
MmacroscopeappBoton lines 176–182Aug 15, 2026

settings/IntegrationsSettings.tsx:182

The viewport control cluster can't shrink, so this row overflows on narrow widths. SelectTrigger's default variant carries min-w-36 (never overridden here), and the sized group next to it is shrink-0 with two w-24 fields plus the rotate button (~240px), giving the control a hard minimum of ~390px — more than a SettingsRow gets at phone widths or in a narrow desktop window, so the row spills horizontally once a non-fill default is configured. The comparable two-control row in SettingsPanels.tsx (font family + size) avoids this by making the flexible control min-w-0 flex-1 next to a small fixed control. Allowing the cluster to wrap and letting the select shrink keeps the geometry intact:

suggestion
1 <div className="flex w-full flex-wrap items-center gap-2 sm:w-auto sm:justify-end">
2 <Select
3 value={viewportSelectValue(viewport)}
4 onValueChange={selectViewport}
5 disabled={disabled}
6 >
7 <SelectTrigger
8 className="w-full min-w-0 sm:w-44"
9 aria-label="Default browser viewport"
10 >

No longer relevant as of bb0492d294a1597184c8f77ceb74fb2dfa097239

apps/web/src/browser/browserDefaults.tsline 47Outdated · ab6e0edOpen thread
……@@ -0,0 +1,98 @@
44+
45+/** Non-hook accessor for imperative open paths (menu actions, automation hosts). */
46+export function getBrowserDefaults(): BrowserDefaults {
47+ return toBrowserDefaults(getClientSettings());

🟡 Medium browser/browserDefaults.ts:47

A preview opened before client-settings hydration completes is created with DEFAULT_CLIENT_SETTINGS for its viewport, zoom, and appearance, and never receives the user's persisted creation defaults. getBrowserDefaults() synchronously reads getClientSettings() while the snapshot still contains built-in values; wait for hydration before this one-shot creation path, or gate preview opening during startup.

🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
text
1In file @apps/web/src/browser/browserDefaults.ts around line 47:
2
3A preview opened before client-settings hydration completes is created with `DEFAULT_CLIENT_SETTINGS` for its viewport, zoom, and appearance, and never receives the user's persisted creation defaults. `getBrowserDefaults()` synchronously reads `getClientSettings()` while the snapshot still contains built-in values; wait for hydration before this one-shot creation path, or gate preview opening during startup.
……@@ -428,7 +432,10 @@ function PreviewAutomationHost(props: { readonly environmentId: EnvironmentId })
429433 }
430434 }
431- const shouldPresentPreview = shouldOpenPreviewMiniPlayer(input);
435+ const shouldPresentPreview = shouldOpenPreviewMiniPlayer(
MmacroscopeappBoton line 435Aug 15, 2026

🟡 Medium preview/PreviewAutomationHosts.tsx:435

browserAutoShowFloatingPreview: false is ignored for normal preview_open requests, so the floating preview still opens when the agent omits visibility. normalizePreviewOpenInput fills omitted open with true before this handler runs, and shouldOpenPreviewMiniPlayer therefore never reaches the preference fallback; preserve whether open was omitted or apply the preference before normalization.

No longer relevant as of bb0492d294a1597184c8f77ceb74fb2dfa097239

Synced from GitHub

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

ProviderMetricMain baselineThis PRImpactPR ceiling
CodexTotal thread wire12.6 KiB12.6 KiB+10 B (+0.1%)15.1 KiB✅
CodexThread snapshot wire6.3 KiB6.3 KiB+2 B (+0.0%)7.3 KiB✅
CodexLive turn WebSocket wire6.3 KiB6.3 KiB+8 B (+0.1%)7.8 KiB✅
CodexLive turn WebSocket decoded51.8 KiB51.8 KiB0 B (0.0%)66.4 KiB✅
CodexLive turn messages16160 (0.0%)21✅
ClaudeTotal thread wire12.6 KiB12.6 KiB+9 B (+0.1%)15.1 KiB✅
ClaudeThread snapshot wire6.3 KiB6.3 KiB+1 B (+0.0%)7.3 KiB✅
ClaudeLive turn WebSocket wire6.3 KiB6.3 KiB+8 B (+0.1%)7.8 KiB✅
ClaudeLive turn WebSocket decoded52.7 KiB52.7 KiB0 B (0.0%)66.4 KiB✅
ClaudeLive turn messages16160 (0.0%)21✅

Baseline: ad11723 · PR result: 6489255 · Source CI: success

Scenario and decoded snapshot size

10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.

  • Codex decoded thread snapshot: 99.9 KiB
  • Claude decoded thread snapshot: 100.6 KiB

Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed.

MmacroscopeappBotreviewed · 2 inline threadsAug 15, 2026
Synced from GitHub

UI consistency review of the new Integrations settings panel and the extracted ScreenRotationIcon. Two findings on apps/web/src/components/settings/IntegrationsSettings.tsx; the viewport-row minimum-width overflow flagged in the previous run is still present and unaddressed (not re-posted here).

Posted via Macroscope — UI Consistency

apps/web/src/components/settings/IntegrationsSettings.tsxline 404Outdated · ab6e0edOpen thread
……@@ -0,0 +1,432 @@
401+ <InfoIcon className="mt-0.5 size-3.5 shrink-0 text-warning" />
402+ <p>Only available in the desktop app.</p>
403+ </div>
404+ <div className="opacity-64">{children}</div>
MmacroscopeappBoton line 404Aug 15, 2026

settings/IntegrationsSettings.tsx:404

The wrapper's opacity-64 compounds with the dimming each primitive already applies for its own disabled state — SelectTrigger has data-disabled:opacity-64, NumberFieldGroup has data-disabled:opacity-64, Button has disabled:opacity-64 — so in the browser build these controls composite to roughly 0.41 alpha, noticeably fainter than a disabled control anywhere else in Settings, while the labels/description only get the single 0.64 step.

The read-only block in ProviderSettingsPanel solves the same "dim the whole group" problem with inert + aria-disabled plus one wrapper opacity, which keeps a single dim level and still blocks focus and pointer interaction. Consider following that here (wrapper gets inert/aria-disabled, controls drop the disabled prop) so the group dims exactly once; note the resetAction guards currently keyed on !disabled would then need the same flag to stay hidden.

No longer relevant as of bb0492d294a1597184c8f77ceb74fb2dfa097239

apps/web/src/components/settings/IntegrationsSettings.tsxline 205Outdated · ab6e0edOpen thread
……@@ -0,0 +1,432 @@
202+ </Select>
203+
204+ {sized ? (
205+ <div className="flex shrink-0 items-center gap-1">
MmacroscopeappBoton line 205Aug 15, 2026

settings/IntegrationsSettings.tsx:205

The controls in this cluster resolve to different heights: the adjacent SelectTrigger uses the default size (min-h-9 sm:min-h-8, 36/32px) while these size="sm" number fields render 32/28px and the icon-sm rotate button 32/28px, so the select sits 4px taller than everything beside it in the same items-center row.

Suggest picking one height for the cluster — either size="sm" on the viewport SelectTrigger (which also lowers the row's minimum width), or default-size NumberFields with size="icon" on the rotate button to match the other rows' selects.

No longer relevant as of bb0492d294a1597184c8f77ceb74fb2dfa097239

Added 6 commits

  • fix(web): disable desktop-only browser defaults on web clients839966a
  • feat(web): proper number fields, orientation, and floating-preview toggle86975b8
  • fix(web): tighten the desktop-only browser notice copyb4777da
  • test(web): update preview tests for the new open and createTab shapescca9dc8
  • feat(web): configurable browser defaults in Settings → Integrationsde4c1ee
  • fix(web): frame the desktop-only browser defaults as one disabled blockf4c628c
CcursorBotreviewed · 1 inline threadAug 15, 2026
Synced from GitHub

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

Bugbot Autofix is ON, but a cloud agent failed to start.

Reviewed by Cursor Bugbot for commit cca9dc83304dbee5c4ceed3fae636b729504521c. Configure here.

packages/contracts/src/settings.tsline 149Outdated · cca9dc8Open thread
……@@ -111,7 +119,34 @@ export const DEFAULT_ENVIRONMENT_IDENTIFICATION_MODE: EnvironmentIdentificationM
146+ */
147+ browserAutoShowFloatingPreview: Schema.Boolean.pipe(
148+ Schema.withDecodingDefault(Effect.succeed(DEFAULT_BROWSER_AUTO_SHOW_FLOATING_PREVIEW)),
149+ ),
CcursorBoton line 149Aug 15, 2026

Restore defaults skips browser prefs

Medium Severity

General’s Restore defaults is meant to reset every non-legacy preference, but the new browserDefaultViewport, browserDefaultZoomFactor, browserDefaultAppearance, and browserAutoShowFloatingPreview fields are missing from both dirty tracking and the reset payload. Changing only Integrations → Browser leaves Restore idle, and a restore that does run leaves those values in place.

Triggered by learned rule: Restore defaults on General settings page is intentionally global — not scoped per-page

Reviewed by Cursor Bugbot for commit cca9dc83304dbee5c4ceed3fae636b729504521c. Configure here.

Synced from GitHub

Approvability

Verdict: Needs human review

2 blocking correctness issues found. This PR introduces a new feature (browser defaults in Settings → Integrations) with new UI components, new routes, new settings schema fields, and runtime behavior changes to how preview tabs are created and configured. New features of this scope warrant human review.

You can customize Macroscope's approvability policy. Learn more.

Added 2 commits

  • fix(web): address UI-consistency findings on the browser defaults rowbb0492d
  • fix(web): honour browser defaults on the paths that were skipping them6489255
……@@ -658,6 +664,10 @@ export function useSettingsRestore(onRestored?: () => void) {
658664 fontSizePrompt: DEFAULT_UNIFIED_SETTINGS.fontSizePrompt,
659665 fontSizeCode: DEFAULT_UNIFIED_SETTINGS.fontSizeCode,
660666 fontSizeTerminal: DEFAULT_UNIFIED_SETTINGS.fontSizeTerminal,
667+ browserDefaultViewport: DEFAULT_UNIFIED_SETTINGS.browserDefaultViewport,
MmacroscopeappBoton line 667Aug 16, 2026

🟡 Medium settings/SettingsPanels.tsx:667

restoreDefaults persists the four browser defaults for web clients even though the Integrations controls disable those settings there, so Restore defaults bypasses that platform restriction and changes web-only stored settings. Guard these assignments with the desktop/platform capability (or omit them for web clients) before calling useUpdatePrimarySettings.

🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
text
1In file @apps/web/src/components/settings/SettingsPanels.tsx around line 667:
2
3`restoreDefaults` persists the four browser defaults for web clients even though the Integrations controls disable those settings there, so Restore defaults bypasses that platform restriction and changes web-only stored settings. Guard these assignments with the desktop/platform capability (or omit them for web clients) before calling `useUpdatePrimarySettings`.