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

未关闭
#35,870 0 条评论 0 个 reaction 已指派 0 人 在 GitHub 查看

维护者通常 1 天内回复

还没有人认领这个 Issue。

评估

难度
3/5
预计耗时
1-2 天
新手友好度
50/100
Issue 类型
缺陷
描述清晰度
基本清楚
活跃度
活跃
技术栈
typescript
领域
frontend

调研方向

Start with SETTINGS_DEVICE_ONLY_PATHS in apps/web/src/components/settings/SettingsScopeSentence.tsx and the deviceOnly prop passed from apps/web/src/routes/_chat.settings.tsx for /settings/browser. Then read BrowserSearchEngineSetting in IntegrationsSettings.tsx and the serverScoped handling in settingsLayout.tsx. Run the focused scope test in apps/web/src/components/settings/ before and after the change. Done when the Browser page's project scope either saves device preferences or shows the rows read-only, with no warning toast.

由索引模型根据 Issue 内容生成。

描述

vouch:denounced
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
主要语言
TypeScript
星标
24.8k
派生
6.4k
平均合并
59 分钟
30 天内合并 PR
418

环境准备

在 Codespaces 中打开

在浏览器里用你自己的 GitHub 账号启动这个项目的开发容器。

从这里开始

  1. 先读完整个 Issue,再读项目的贡献指南。
  2. 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
  3. Fork 仓库,在一个分支上完成修改。
  4. 提交 Pull Request,并在描述里引用这个 Issue 编号。

pingdotgg/t3code 的其他 Issue

查看 pingdotgg/t3code 的全部 Issue

相似的 Issue

更多 TypeScript Issue

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。