Uh oh!
There was an error while loading. Please reload this page.
feat(desktop): browser profiles for the preview browser - #7254
feat(desktop): browser profiles for the preview browser#7254juliusmarminge wants to merge 31 commits into
Conversation
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Comment |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Thread transfer impact✅ Thread transfer remains within every enforced ceiling.
Baseline: Scenario and decoded snapshot size10 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.
Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed. |
There was a problem hiding this comment.
Reviewed the web UI changes (RightPanelTabs, PreviewChromeRow/PreviewView, PreviewMoreMenu, IntegrationsSettings, menu.tsx) against the shared primitive contracts. Two consistency findings, both inline.
Posted via Macroscope — UI Consistency
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
ApprovabilityVerdict: Not approved Macroscope's review found this PR not approvable — This PR adds a cross-layer browser-profile capability that changes session partitioning, cookie/login isolation, storage deletion, and the default profile used for implicit preview opens. Its new configurable product default and sensitive browser-storage behavior warrant human review. No code changes detected at You can add or adjust custom eligibility rules. Learn more. |
b7395b6 to
fe1bc48CompareUh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
fe1bc48 to
61d5a11CompareUh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Two findings in the changed web UI. The MenuSubTrigger icon treatment added in this revision fixes the alignment of the new Browser sub-trigger, but the selector it uses also matches the trailing chevron on the two existing icon-less sub-triggers and overrides their ms-auto.
Posted via Macroscope — UI Consistency
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
One finding on the new profile badge in the preview chrome row: the truncate cap doesn't actually ellipsize because Badge is a flex container. Everything else in scope (the MenuSubTrigger icon-column fix, the MenuGroup/MenuGroupLabel wrapping in PreviewMoreMenu, the profile list rows reusing SettingsRow/ITEM_ROW_INNER_CLASSNAME/DraftInput/Button/AlertDialog, and the Select for the default profile) follows the existing primitives and settings conventions.
Posted via Macroscope — UI Consistency
Uh oh!
There was an error while loading. Please reload this page.
0009cb1 to
1adce5cCompareUh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
1adce5c to
36b86edCompareThere was a problem hiding this comment.
One convention finding: the new browserProfile module (and its test) uses the consolidated import { Schema } from "effect" form instead of the subpath namespace import used by the rest of packages/contracts. Everything else in the diff (service interfaces on BrowserSession/PreviewManager, Schema.TaggedErrorClass errors with structural attributes, make/layer shape, dependency acquisition) matches the conventions.
Posted via Macroscope — Effect Service Conventions
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
…nu row Widening `onAddBrowser` to `(profileId?: string)` broke the empty-state Browser card: it is passed straight to a DOM click handler, so React handed the MouseEvent in as the profile id and the open silently failed schema validation. Fixed structurally rather than at the call site — `onAddBrowser` goes back to taking no arguments, and choosing a profile is a separate `onAddBrowserInProfile(profileId)`. The type now rejects wiring the profile variant to a DOM handler, so this cannot recur; adding it surfaced all four call sites immediately. The "+" menu is one row again. The submenu trigger is itself clickable and opens the default profile, with hover or arrow revealing the rest, so the common case stays a single click. The menu is controlled so that action can dismiss it, which a submenu trigger does not do on its own. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`MenuSubTrigger` was the only interactive menu primitive without a cursor: `MenuItem`, `MenuCheckboxItem`, and `MenuRadioItem` all set `cursor-pointer` plus `data-disabled:cursor-not-allowed`, so it kept the default arrow and read as inert. It always was clickable — clicking opens the submenu — and it is now also an action in the add-surface menu, where the arrow cursor was actively misleading. Fixed in the primitive rather than at that one call site, since the gap applies to the other two submenu triggers (Diff scope, preview Appearance) as well. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A tab's profile was only discoverable by opening the three-dot menu, which is a poor place for something that changes what you are logged into. The chrome row gains a leading slot before the URL bar, and the preview names the tab's profile there. Only when it differs from the default: labelling every tab "Default" would be noise on the common case, while a tab running in another profile is exactly what needs calling out. Also gives the three-dot menu's profile heading a `MenuGroup` ancestor — `MenuGroupLabel` reads Base UI's group context and throws without one, which took the app to its error boundary as soon as the menu opened. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The More menu passed the tab's raw `profileId` to the clear actions. A tab created before profiles existed has none, and the IPC layer reads an absent profile as "every profile" — so a menu labelled "Clear cookies (Default)" wiped every partition. The view now resolves the default the same way it resolves the name it displays, and the prop is required so the gap cannot come back. Clearing also only touched sessions already in the in-memory map. Deriving the partition string does not create the session, so clearing a profile with no tab open this run reported success and deleted nothing; the handler now loads the session first. `resolveBrowserProfiles` additionally drops repeated ids, which map to one partition and would otherwise show as two isolated identities sharing every cookie, and reports a custom `incognito` profile as persistent, since persistence is keyed off the built-in id alone. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The leading-icon rules added for the Browser sub-trigger were scoped with `:first-of-type`, which on a sub-trigger with no leading icon matches the trailing chevron instead — the compound selector outranks its `ms-auto` and took away the right alignment on the existing Appearance and Turn triggers. Scoping away from the last child leaves the chevron alone. The profile badge in the chrome row was unbounded while profile names run to 48 characters, so it took width from the URL input, the only flexible element there. It is capped and truncated. Removing a profile now confirms first, like every other destructive action in Settings, and Incognito is no longer offered as — or resolved to — the default profile: as a default it would open every new tab into storage discarded on close, and the settings list and the resolved default now agree on that. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`Badge` is an `inline-flex` with `whitespace-nowrap`, so `truncate` on the badge never reached the name inside it: a long profile name was hard-clipped at both ends with no ellipsis. The cap stays on the badge, the truncation moves to an inner span, and the full name is available as a title. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Built-in rows are a plain span and a badge rather than `h3`/`p` or disabled controls, so the block's own dimming never reached them: on web they were the only full-contrast content inside "only available in the desktop app". Also switches `browserProfile` to the subpath namespace import the rest of `packages/contracts` uses. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The wrapper-level dim stacked with each control's own: the rename field and remove button composited to roughly 0.41 alpha while every other disabled control in the desktop-only block sits at 0.64. Only the built-in row's name and badge lack a disabled treatment, so the dim belongs there. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Profile names are user-supplied and run to 48 characters. The Browser sub-menu rendered them bare inside an unbounded popup, so a long one widened it to fit-content and wrapped; it is now capped and truncated like the other name-bearing menus. The clear actions repeated the name their own group heading already shows, which drove the popup far past its width for no added information. The heading keeps the profile and the actions keep fixed-length labels. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Bare rows stack on narrow viewports with a larger gap inside a row than between rows, so the remove button read as belonging to the profile below. Each profile is now a bounded row, and the list carries the bottom spacing `SettingsRow` leaves to its children. `MenuGroupLabel` renders a block box, so `text-overflow` on an inline span inside it never applied and a long profile name pushed the popup past its width. The truncation sits on the label itself. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A tab created before profiles existed carries no profile of its own and runs in the built-in `default` partition — the scope the browser used before profiles. It was labelled with, and cleared against, whatever profile is configured as the default now, so on a machine with a custom default the active tab's data was left untouched while another profile's was wiped. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
fe5480b to
f666480Compare
Adds profiles to the desktop preview browser so tabs can keep separate cookies and site data. This is the bottom of the remaining stack; the browser-access setting is already merged.
Default preserves existing login storage. Incognito uses an in-memory partition. Other profiles use a separate partition namespace with unambiguous environment/profile scoping. A tab keeps its chosen profile for its lifetime.
Settings supports creating, renaming, choosing a default, and removing profiles. Removal waits for cookie/cache cleanup across all known environments and retains the profile if cleanup fails. Existing tabs remain open after removal and are labeled “Removed profile.” The Browser menu supports opening a chosen profile, including touch input.
Validation: focused profile, preview, settings-hydration, and partition-isolation tests; web/desktop typechecks; scoped formatting and lint. Latest CI results are shown below. Earlier desktop behavior was verified by the maintainer; this audit did not run another GUI pass.
Compatibility: Default partitions are unchanged. Data created in custom profiles by an earlier unmerged version of this stack is not migrated from the collision-prone partition mapping.
Original implementation: Claude Code. Review fixes: GPT-5.6 Sol agents, coordinated through Codex.
Note
Add browser profiles to the preview browser
BrowserProfilecontracts with built-indefaultandincognitoprofiles, and adds settings UI to create, rename, delete, and set the default profileBrowserSessionto support aprofilenamespace and ephemeral (in-memory) partitions for incognito mode, keeping legacy partitions disjointPreviewManagerand IPC handlers to passprofileIdthrough snapshots, config requests, and partition-scoped clearing operationsRightPanelTabsto open specific profiles via a submenu, and keyspreviewWebviewConfigAtomby(environmentId, profileId)DesktopPreviewBridge.clearCookies,clearCache, andgetPreviewConfignow require(environmentId, profileId?)instead of taking no arguments or justenvironmentId📊 Macroscope summarized f666480. 27 files reviewed, 4 issues evaluated, 4 issues filtered, 0 comments posted
🗂️ Filtered Issues
apps/web/src/browser/previewWebviewConfigState.ts — 0 comments posted, 1 evaluated, 1 filtered
parseConfigKeysplits at the first NUL even thoughEnvironmentIdis only a trimmed non-empty string and permits control characters. For an environment ID such as"env\u0000x",configKeyproduces a key which is parsed as environment"env"and profile"x\u0000..."; the webview configuration request is then made for the wrong environment/profile (and its profile value violates the profile-ID contract), so that environment cannot open its preview with the intended partition. [ Already posted ]apps/web/src/components/settings/IntegrationsSettings.tsx — 0 comments posted, 2 evaluated, 2 filtered
removeProfileclears the partition before it removes the profile while deliberately leaving its tabs open. An open tab can receive a response or run script that writes cookies/cache afterclearBrowserProfileDataresolves but before/after the settings update, leaving persistent data in the now-unreachable removed profile partition even though removal reports success. [ Already posted ]removeProfileinvokesupdateSettingsand immediately reports success, but client persistence is fire-and-forget and explicitly catches write failures. IfsetClientSettingsfails (for example, storage/IPC failure), the profile reappears after restart with its cookies/cache already erased, while the UI had claimed removal succeeded. [ Already posted ]packages/contracts/src/settings.ts — 0 comments posted, 1 evaluated, 1 filtered
browserProfileshas noSchema.isMaxLength(BROWSER_PROFILE_MAX_COUNT)check. A hand-edited settings file (or a settings update outside the add-profile UI) with 25 or more individually valid profiles decodes successfully, even though the UI and exported limit cap profiles at 24; all excess profiles are then resolved and displayed/used. [ Out of scope (post-validation triage) ]Note
Medium Risk
Changes Electron session partitioning and breaks the preview IPC surface for clear/config calls; incorrect scope mapping could mix or strand login data, though legacy Default partitions are explicitly preserved.
Overview
Adds named browser profiles so preview tabs can keep isolated cookies and storage, with built-in Default (legacy partition unchanged) and Incognito (ephemeral Chromium partition).
Contracts & server: New
BrowserProfiletypes and client settings (browserProfiles,browserDefaultProfileId). Preview open/snapshots carry optionalprofileIdthrough navigate and status updates so tabs don't silently switch partitions.Desktop:
resolvePartitionScopemaps(environmentId, profileId)to partition scope (JSON tuple for non-default profiles), persistence, and aprofile-partition namespace disjoint from legacy defaults.getPreviewConfig,clearCookies, andclearCacheIPC now takeenvironmentIdand optionalprofileId; per-profile clears load the session first so cleanup works after restart.BrowserSessionsupports ephemeral partitions and partition-scoped clears.Web UI: Integrations settings to create/rename/remove profiles (removal clears data across environments), default profile picker, and a Browser add-menu submenu to open a specific profile. Preview chrome shows the active profile and scopes "clear cookies/cache" to that tab's profile. Open paths (
openPreviewSession, file/link opens) apply hydrated defaultprofileId; webview config is cached per environment + profile.Reviewed by Cursor Bugbot for commit f666480. Bugbot is set up for automated code reviews on this repo. Configure here.