Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 8 additions & 4 deletions apps/desktop/src/preview/WebviewPreferences.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,21 +43,25 @@ function parseWebPreferences(input: string): Record<string, unknown> {
describe("PREVIEW_WEBVIEW_PREFERENCES", () => {
const parsed = parseWebPreferences(PREVIEW_WEBVIEW_PREFERENCES);

it("contains exactly the three security-critical keys", () => {
it("contains only the required preview overrides", () => {
expect(Object.keys(parsed).toSorted()).toEqual(
["contextIsolation", "nodeIntegration", "sandbox"].toSorted(),
["contextIsolation", "nodeIntegration", "sandbox", "zoomFactor"].toSorted(),
);
});

it("uses canonical JS-boolean string literals (not yes/no, on/off, 1/0)", () => {
// `value="no"` is a TRUTHY string when assigned to webPreferences.X — so
// `contextIsolation="no"` would silently leave isolation ENABLED. Lock
// the values to `"true"` / `"false"` so the parser does the right thing.
for (const value of Object.values(parsed)) {
expect(value).toMatch(/^(true|false)$/);
for (const key of ["contextIsolation", "nodeIntegration", "sandbox"]) {
expect(parsed[key]).toMatch(/^(true|false)$/);
}
});

it("starts previews at 100% independently of the app zoom", () => {
expect(parsed["zoomFactor"]).toBe("1");
});

it("disables context isolation (so react-grab can see the page's React DevTools hook)", () => {
expect(parsed["contextIsolation"]).toBe("false");
});
Expand Down
8 changes: 5 additions & 3 deletions apps/desktop/src/preview/WebviewPreferences.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,15 @@
* the preload's `import { ipcRenderer }` line, but no Node globals leak.
* - `nodeIntegration=false`: pinned for clarity (the page itself never gets
* Node access).
* - `zoomFactor=1`: Electron otherwise initializes each guest with the
* embedder's current zoom, coupling a newly-opened preview to the app UI.
*
* Format notes (locked down by `WebviewPreferences.test.ts`):
* - Whitespace-free. Electron's webpreferences parser splits on `,` and
* does not trim, so a leading space would turn a key into an unknown one
* and silently drop it.
* - Values are JS-boolean strings (`true`/`false`) — `yes`/`no` are not
* special-cased by the parser; `value="no"` becomes the truthy STRING
* - Boolean values use JS-boolean strings (`true`/`false`) — `yes`/`no` are
* not special-cased by the parser; `value="no"` becomes the truthy STRING
* `"no"` when assigned to a boolean webPreferences key. Most critically,
* `contextIsolation="no"` is truthy → contextIsolation stays ENABLED →
* react-grab can't see the React DevTools hook.
Expand All @@ -39,4 +41,4 @@
* security-critical flags can't regress on preview tabs.
*/
export const PREVIEW_WEBVIEW_PREFERENCES =
"contextIsolation=false,sandbox=true,nodeIntegration=false";
"contextIsolation=false,sandbox=true,nodeIntegration=false,zoomFactor=1";
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
Comment thread
cursor[bot] marked this conversation as resolved.
Outdated
3 changes: 3 additions & 0 deletions docs/user/keybindings.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ characters and uses SQLite's ASCII case-insensitive matching.
The full command list and the current defaults are shown in **Settings** → **Keybindings**, which
always matches the build you are running. Use that rather than a copied list.

On desktop, app UI zoom and integrated-browser zoom are independent. New browser previews start at
100%, and the preview's three-dot menu changes only that browser tab's zoom.

Note that `chat.new` and `chat.newLocal` both create a thread through the same path. A new thread
inherits the project you were in, along with model and mode selections. Branch, worktree, and
environment mode always come from your configured defaults, not from the thread you were looking
Expand Down
Loading