Skip to content
Source
Sign inSign up

[Bug]: Browser preferences reject changes while a project is selected #35870

Send to my agent
Opencoygeek(GitHub profile, opens in a new tab) opened this issue 0 comments

Summary

On Settings → Browser, selecting a project leaves device preference controls interactive, but changing one produces a “Setting not saved” warning and leaves the preference unchanged. Device preferences are documented to ignore the project selection, and settings that cannot be overridden should be shown read-only. An isolated check reproduced the writer's rejection for Search engine and confirmed the active row and scope wiring in source; the full UI interaction was not run.

Before submitting

  • I searched existing issues and did not find a duplicate.
  • I included enough detail to reproduce or investigate the problem.

Area

apps/web

Steps to reproduce

  1. Run the script below against a clean checkout of the specified commit with installed dependencies; it creates and removes one uniquely named focused test file in that checkout.
  2. The focused test compares a valid browser search-engine patch in project and all-projects scopes. The script then checks that the Browser route remains scope-aware and that the Search engine row still offers the scoped writer through an active select.

One run reproduced the failure path. The direct pointer interaction in a running web or desktop client was not exercised; the row state and hook connection were confirmed from the pinned source. The script prints tool versions and returns 1 with RESULT: defect reproduced when both the rejection and active UI wiring are present, 0 with RESULT: expected behavior when that wiring changes, and 2 with RESULT: inconclusive if the focused test or setup fails. Save it as repro.sh, then run sh repro.sh "$PWD" from the checkout; Node, Vite+, and installed workspace dependencies are required.

Reproduction script: repro.sh
#!/bin/sh
set -eu
checkout=${1:?pass the T3 Code checkout path}
case "$checkout" in /*) ;; *) checkout=$(cd "$checkout" && pwd) ;; esac
scratch=$(mktemp -d)
test_file="$checkout/apps/web/src/components/settings/browser-scope.y02-01.test.ts"
if [ -e "$test_file" ]; then
  echo 'RESULT: inconclusive'
  exit 2
fi
cleanup() { rm -f "$test_file"; rm -rf "$scratch"; }
trap cleanup EXIT HUP INT TERM
export HOME="$scratch/home" TMPDIR="$scratch/tmp" T3CODE_HOME="$scratch/t3" 
mkdir -p "$HOME" "$TMPDIR" "$T3CODE_HOME"
printf 'Node: %s\n' "$(node --version)"
vp --version | sed -n '1,3p'
cat > "$test_file" <<'TS'
import { DEFAULT_SERVER_SETTINGS, EnvironmentId } from "@t3tools/contracts";
import { expect, it } from "vite-plus/test";
import { planScopedSettingsPatch } from "./scopedSettings";
import type { ResolvedSettingsScope } from "./settingsScope";

it("compares Browser client-setting writes in project and all scopes", () => {
  const id = EnvironmentId.make("example");
  const environments = [{
    environmentId: id,
    label: "Example",
    connection: { phase: "connected" as const },
    serverConfig: { settings: DEFAULT_SERVER_SETTINGS },
  }];
  const project = {
    kind: "project", environmentIds: [id], members: [],
  } as unknown as ResolvedSettingsScope;
  const all = {
    kind: "all", environmentIds: [id], members: [],
  } as unknown as ResolvedSettingsScope;
  const patch = { browserSearchEngine: "google" as const };
  const rejected = planScopedSettingsPatch(project, environments, patch);
  const accepted = planScopedSettingsPatch(all, environments, patch);
  expect(rejected).toMatchObject({
    clientPatch: {}, hasClientWrite: false, serverWrites: [],
    unavailableReason: "This setting is environment-wide and cannot be overridden by a project.",
  });
  expect(accepted).toMatchObject({
    clientPatch: patch, hasClientWrite: true, unavailableReason: null,
  });
});
TS
if ! (cd "$checkout" && vp test run apps/web/src/components/settings/browser-scope.y02-01.test.ts); then
  echo 'RESULT: inconclusive'
  exit 2
fi
node - "$checkout" <<'NODE'
const fs = require('node:fs');
const path = require('node:path');
const root = process.argv[2];
const read = (name) => fs.readFileSync(path.join(root, name), 'utf8');
const paths = read('apps/web/src/components/settings/SettingsScopeSentence.tsx');
const pathList = paths.match(/SETTINGS_DEVICE_ONLY_PATHS:[\s\S]*?new Set\(\[([\s\S]*?)\]\)/)?.[1];
const panel = read('apps/web/src/components/settings/IntegrationsSettings.tsx');
const search = panel.match(/function BrowserSearchEngineSetting\(\) \{([\s\S]*?)\nexport function PullRequestLinksOpenInAppSetting/)?.[1];
const route = read('apps/web/src/routes/_chat.settings.tsx');
const scopeAware = pathList !== undefined && !pathList.includes('"/settings/browser"') && route.includes('deviceOnly={SETTINGS_DEVICE_ONLY_PATHS.has(pathname)}');
const activeWrite = search !== undefined && search.includes('useUpdateScopedSettings()') && search.includes('updateSettings({ browserSearchEngine: next })') && !search.includes('serverScoped') && !search.includes('settingKeys=') && !search.includes('disabled=');
console.log(`Browser page keeps project scope: ${scopeAware}`);
console.log(`Search-engine control remains active and uses scoped writer: ${activeWrite}`);
if (!scopeAware || !activeWrite) {
  console.log('RESULT: expected behavior');
  process.exit(0);
}
console.log('RESULT: defect reproduced');
process.exit(1);
NODE

Expected behavior

Device preferences should ignore the selected project, as the user guide says, or the UI should present a read-only control for a value it cannot change at project scope: docs/user/project-settings.md#L3-L17. The Browser page's profile create, rename, and remove actions use the device writer in every scope, which demonstrates the existing device-preference pattern on this page: apps/web/src/components/settings/IntegrationsSettings.tsx#L1110-L1116.

Actual behavior

The Browser page exposes the project scope picker because /settings/browser is absent from SETTINGS_DEVICE_ONLY_PATHS, and the route passes deviceOnly={false} for that path: apps/web/src/components/settings/SettingsScopeSentence.tsx#L50-L60, apps/web/src/routes/_chat.settings.tsx#L146-L155. The Search engine row uses useUpdateScopedSettings, offers an enabled select, and submits browserSearchEngine when changed: apps/web/src/components/settings/IntegrationsSettings.tsx#L706-L751. The same panel renders this row outside the desktop-only group: apps/web/src/components/settings/IntegrationsSettings.tsx#L1581-L1608.

The isolated run found that a project-scoped browserSearchEngine patch has no client write or server write and returns “This setting is environment-wide and cannot be overridden by a project”; the same patch in all-projects scope has a client write. This follows the planner's client-only key rule: apps/web/src/components/settings/scopedSettings.ts#L220-L245, apps/web/src/components/settings/scopedSettings.ts#L331-L343. The update hook passes the page's deviceOnly value to that planner and shows a warning toast instead of persisting an unavailable patch: apps/web/src/components/settings/useScopedSettings.ts#L50-L62, apps/web/src/components/settings/useScopedSettings.ts#L100-L105.

Impact and scope

Impact: P2 - a project or checkout selection blocks changes to otherwise interactive browser preferences, but selecting All projects or an environment lets the same preference save.

The verified search-engine path is available in both web and desktop clients. Source inspection finds the same scoped writer on desktop browser viewport, zoom, appearance, recording, link-target, and floating-preview rows, and on the “Set as default” profile action: apps/web/src/components/settings/IntegrationsSettings.tsx#L323-L325, apps/web/src/components/settings/IntegrationsSettings.tsx#L473-L475, apps/web/src/components/settings/IntegrationsSettings.tsx#L562-L565, apps/web/src/components/settings/IntegrationsSettings.tsx#L654-L656, apps/web/src/components/settings/IntegrationsSettings.tsx#L1076-L1078, apps/web/src/components/settings/IntegrationsSettings.tsx#L1423-L1432. Those controls were not exercised at runtime. The failure does not corrupt the saved value; it prevents the requested change.

Earliest failure stage

Core processing

Analysis or suspected cause

SettingsRow only makes an environment-wide row inert at project scope when serverScoped is true; the Search engine row supplies neither that flag nor another disabled condition: apps/web/src/components/settings/settingsLayout.tsx#L305-L328, apps/web/src/components/settings/settingsLayout.tsx#L368-L379, apps/web/src/components/settings/settingsLayout.tsx#L427-L449.

Additional context

The six governing files checked have identical blobs on current main and the tested commit, so the checked path remains present.

Impact

Minor bug or occasional failure

Version or commit

main @ c00a373

Environment

Node.js v26.12.1, Vite+ v1.5.1 with local vite-plus v1.4.0 and Vitest v5.0.5, macOS arm64. The focused test ran on this platform.

Logs or stack traces

The script's output below is from the run against the stated commit. Vite+'s checkout path, run timestamp, duration, and advisory line were omitted from this excerpt; the result and diagnostic lines are unchanged.

Node: v26.12.1
vp v1.5.1
Local vite-plus:
  vite-plus  v1.4.0
 Test Files  1 passed (1)
      Tests  1 passed (1)
Browser page keeps project scope: true
Search-engine control remains active and uses scoped writer: true
RESULT: defect reproduced

Screenshots, recordings, or supporting files

No response

Workaround

Select All projects or an environment to save the same preference.

References

  • https://github.com/pingdotgg/t3code/issues/35093 — closed report of the earlier project-scope overwrite; this report concerns the current rejection with an enabled control.
  • https://github.com/pingdotgg/t3code/pull/35143 — merged fix for that overwrite; its description explicitly leaves the interactive project-scoped Browser rows unchanged.
  • https://github.com/pingdotgg/t3code/issues/35094 — closed request for true per-project browser defaults; this report concerns editing existing device preferences.
  1. tanstack-source added labelsbug

  2. github-actions[bot](GitHub profile, opens in a new tab) added labelsvouch:denounced

Sign in to join this conversation.