Files
claude-desktop-debian/docs/testing/cases/code-tab-foundations.md
Aaddrick 3506c14918 test(harness): add Linux compatibility test harness (#579)
Build out a Playwright-based regression-detection harness covering
the compat-matrix surfaces (KDE-W, KDE-X, GNOME, Sway, i3, Niri,
packaging formats). Adds:

- Planning + decision docs under docs/testing/ — README, matrix,
  runbook, automation, cases/ (11 case files), quick-entry-closeout
- Playwright scaffolding (config, tsconfig)
- 78 spec runners under tools/test-harness/src/runners/ — T## case-
  doc runners and S## distribution/smoke runners
- Substrate primitives in tools/test-harness/src/lib/: AX-tree
  loader (snapshotAx + waitForAxNode + axTreeToSnapshot), focus-
  shifter, eipc-registry, niri-native bridge, drag-drop bridge,
  electron-mocks, claudeai page-objects, inspector client

S03 (DEB Depends declared) and S04 (RPM Requires declared) ship
marked test.fail() — they're regression detectors for the case-doc
gap (deb.sh emits no Depends:, rpm.sh sets AutoReqProv: no), and
the expected-failure shape lets them report green on every host
until upstream packaging starts declaring runtime deps.

127 files, no runtime changes; harness is opt-in via
'cd tools/test-harness && npx playwright test'.

Co-authored-by: Claude <claude@anthropic.com>
2026-05-04 23:17:37 -04:00

9.9 KiB

Code Tab — Foundations

Tests covering Code-tab availability on Linux (officially unsupported per upstream docs), sign-in flow, folder picker, drag-and-drop, and the basic editing surfaces (terminal, file pane). See ../matrix.md for status.

T15 — Sign-in completes in the embedded webview

Drift in build 1.5354.0 — Sign-in is an in-app mainView.webContents.loadURL flow, not an xdg-open browser handoff. Claude.ai/login renders inside the embedded BrowserView; the resulting sessionKey cookie is then exchanged at ${apiHost}/v1/oauth/${org}/authorize with redirect URI https://claude.ai/desktop/callback. No system browser is involved.

Severity: Smoke Surface: Auth / embedded webview Applies to: All rows Issues:

Steps:

  1. Launch a fresh app instance (signed-out state).
  2. Click Sign in. Observe claude.ai/login rendering inside the app.
  3. Authenticate. Observe the in-app navigation completing back to the workspace.

Expected: Sign-in stays inside the embedded webview (will-navigate handler Ihr keeps /login/ paths in-app). After auth the sessionKey cookie is captured and silently exchanged for an OAuth token via the desktop/callback redirect. Account dropdown populates; no auth banner remains.

Diagnostics on failure: DevTools console for the mainView BrowserView, network captures of the /v1/oauth/{org}/authorize and /v1/oauth/token calls, launcher log, cookie jar inspection (sessionKey on .claude.ai).

References: Code tab auth troubleshooting

Code anchors:

  • build-reference/app-extracted/.vite/build/index.js:141996 — desktop OAuth redirect URI https://claude.ai/desktop/callback
  • build-reference/app-extracted/.vite/build/index.js:142431 — POST to ${apiHost}/v1/oauth/${org}/authorize with Bearer ${sessionKey}
  • build-reference/app-extracted/.vite/build/index.js:216565Ihr treats /login/ paths as in-app (not external)
  • build-reference/app-extracted/.vite/build/index.js:141316mainView.webContents.loadURL(...) drives the embedded sign-in

T16 — Code tab loads

Severity: Smoke Surface: Code tab — top-level UI Applies to: All rows Issues:

Steps:

  1. After sign-in, click the Code tab at the top center.
  2. Wait a few seconds.

Expected: Code tab renders the session UI (sidebar, prompt area, environment dropdown). Per upstream docs the Code tab is "not supported" on Linux — the patched build under this project should render the UI normally or surface a clear, actionable message. Not a blank screen, infinite spinner, or Error 403: Forbidden.

Diagnostics on failure: Screenshot, DevTools console, network captures (auth/feature-flag responses), launcher log, the active patch set in scripts/patches/.

References: Use Claude Code Desktop, Get started with the desktop app

Code anchors:

  • build-reference/app-extracted/.vite/build/index.js:525066sidebarMode === "code" rewrites the BrowserView path to /epitaxy
  • build-reference/app-extracted/.vite/build/index.js:496066 — Code deeplinks (claude://code?...) navigate to /epitaxy?...
  • build-reference/app-extracted/.vite/build/index.js:105273IHi recognises /epitaxy and /epitaxy/... as the Code-tab path
  • build-reference/app-extracted/.vite/build/index.js:105346sidebarMode enum contains "code"

Inventory anchor: …tablist.tab-by-name.code (role tab, label Code) — confirms the Code tab is reachable from the new-chat tablist in the captured idle state.

T17 — Folder picker opens

Severity: Smoke Surface: Code tab → Environment selection Applies to: All rows Issues:Runner: tools/test-harness/src/runners/T17_folder_picker.spec.ts — runtime-attach via SIGUSR1 + main-process dialog.showOpenDialog mock + webContents.executeJavaScript to drive the renderer. Click chain to reach the folder-picker button awaits selector tuning

Steps:

  1. In the Code tab, click the environment pill → LocalSelect folder.
  2. Choose a project directory.

Expected: Native file chooser opens. On Wayland sessions the chooser is xdg-desktop-portal-backed (verify with busctl --user tree org.freedesktop.portal.Desktop). On X11 sessions the GTK/Qt native picker fires. Selected path appears in the env pill.

Diagnostics on failure: systemctl --user status xdg-desktop-portal, XDG_SESSION_TYPE, the portal backend in use (xdg-desktop-portal-kde, xdg-desktop-portal-gnome, xdg-desktop-portal-wlr), launcher log.

References: Local sessions

Code anchors:

  • build-reference/app-extracted/.vite/build/index.js:66403 — IPC channel claude.web_FileSystem_browseFolder (renderer → main)
  • build-reference/app-extracted/.vite/build/index.js:509188browseFolder impl calls dialog.showOpenDialog with properties: ["openDirectory", "createDirectory"]
  • build-reference/app-extracted/.vite/build/index.js:450534grantViaPicker (Operon host-access folder grant) uses the same ["openDirectory"] shape
  • tools/test-harness/src/lib/claudeai.ts:122installOpenDialogMock intercepts both (opts) and (window, opts) arities, matching the call sites at index.js:509196 and :450534

Inventory anchor: root.main.region.button-by-name.select-folder (role button, label Select folder…) — the persistent button the T17 runner clicks before the dialog mock fires.

T18 — Drag-and-drop files into prompt

Severity: Critical Surface: Code tab → Prompt area Applies to: All rows Issues:

Steps:

  1. Open a Code-tab session.
  2. From the system file manager, drag one or more files into the prompt area.
  3. Repeat with multiple files at once.

Expected: Files attach to the prompt. The renderer resolves dropped File objects to absolute paths via the preload-bridged claudeAppSettings.filePickers.getPathForFile (Electron's webUtils.getPathForFile). Multi-file drops attach each file. Works on both Wayland and X11.

Diagnostics on failure: Screen recording, wl-paste --list-types (Wayland) or xclip -selection clipboard -t TARGETS -o (X11) during drag, DevTools console, launcher log.

References: Add files and context

Code anchors:

  • build-reference/app-extracted/.vite/build/mainView.js:9267filePickers.getPathForFile wraps webUtils.getPathForFile
  • build-reference/app-extracted/.vite/build/mainView.js:9552 — exposed to the renderer as window.claudeAppSettings

T19 — Integrated terminal

Severity: Critical Surface: Code tab → Terminal pane Applies to: All rows Issues:

Steps:

  1. In a Code-tab session, press Ctrl+` (or open via the Views menu).
  2. Confirm the terminal opens in the session's working directory.
  3. Run git status, npm --version, gh auth status.

Expected: Terminal pane opens in the session's working directory, inherits the same PATH Claude sees. Standard commands run cleanly. Terminal pane is local-session-only per docs.

Diagnostics on failure: Terminal pane content, echo $PATH from inside the pane, pwd, the shell binary in use, launcher log.

References: Run commands in the terminal

Code anchors:

  • build-reference/app-extracted/.vite/build/index.js:69135 — IPC channel claude.web_LocalSessions_startShellPty (also resizeShellPty, writeShellPty at :69184, :69210)
  • build-reference/app-extracted/.vite/build/index.js:486438startShellPty body: spawns node-pty in n.worktreePath ?? n.cwd with TERM=xterm-256color
  • build-reference/app-extracted/.vite/build/index.js:486463node-pty dynamic import (optional dep, package.json line 100)
  • build-reference/app-extracted/.vite/build/index.js:259306shell-path-worker/shellPathWorker.js resolves the user's interactive PATH; FX() (line 259311) returns it for the spawned PTY env

T20 — File pane opens and saves

Severity: Critical Surface: Code tab → File pane Applies to: All rows Issues:

Steps:

  1. In a Code-tab session, click a file path in chat or diff to open it in the file pane.
  2. Make a small edit. Click Save.
  3. Modify the file externally (e.g. echo >> file). Re-edit in the pane. Observe the on-disk-changed warning.

Expected: File opens in the editor pane. Edits write back to disk on Save. If the file changed on disk since opening, the pane shows the on-disk-changed warning and offers override or discard. (The conflict check is sha256-based, not mtime-based — writeSessionFile reads the current bytes, hashes them, and rejects with Conflict if the renderer-supplied expectedHash doesn't match.)

Diagnostics on failure: sha256sum <file> output (and stat mtime for cross-checking), launcher log, DevTools console, screen recording of the warning state.

References: Open and edit files

Code anchors:

  • build-reference/app-extracted/.vite/build/index.js:68922 — IPC channel claude.web_LocalSessions_readSessionFile
  • build-reference/app-extracted/.vite/build/index.js:69003 — IPC channel claude.web_LocalSessions_writeSessionFile with expectedHash argument at position 3
  • build-reference/app-extracted/.vite/build/index.js:492874readSessionFile impl
  • build-reference/app-extracted/.vite/build/index.js:492954writeSessionFile impl: sha256-hashes current on-disk bytes, returns { status: nW.Conflict, currentHash } when expectedHash mismatches