feat(webui): add project workspaces and access controls (#4007)

* feat(webui): add project workspaces and access controls

* feat(webui): add project workspaces and access controls

* refactor(tools): centralize workspace access resolution

* refactor(webui): remove unused workspace host state

* fix(webui): hide estimated file edit label

* fix(webui): clarify file edit deletion feedback

* fix(webui): label deleted file activity

* fix(webui): flatten file edit activity rows

* fix(core): remove path-only patch deletion

* fix(core): keep apply patch non-destructive

* refactor(webui): trim workspace host plumbing

* fix(tools): register exec with tools config
This commit is contained in:
Xubin Ren
2026-05-29 03:42:53 +08:00
committed by GitHub
parent 84428136e6
commit 3a420136bb
111 changed files with 9972 additions and 1822 deletions
+16 -5
View File
@@ -5,9 +5,11 @@ import { describe, expect, it, vi } from "vitest";
import { LanguageSwitcher } from "@/components/LanguageSwitcher";
import { ThreadComposer } from "@/components/thread/ThreadComposer";
import { resources } from "@/i18n";
import { LOCALE_STORAGE_KEY, resolveInitialLocale } from "@/i18n/config";
const QUICK_ACTION_KEYS = ["plan", "analyze", "brainstorm", "code", "summarize", "more"];
const IMAGE_QUICK_ACTION_KEYS = ["icon", "sticker", "poster", "product", "portrait", "edit"];
const HERO_GREETING_KEYS = ["workOn", "start", "build", "tackle"];
const SLASH_COMMAND_KEYS = [
"new",
"stop",
@@ -27,12 +29,11 @@ const SETTINGS_NAV_KEYS = [
"appearance",
"models",
"image",
"web",
"browser",
"apps",
"runtime",
"advanced",
];
function isRecord(value: unknown): value is Record<string, unknown> {
return !!value && typeof value === "object" && !Array.isArray(value);
}
@@ -61,6 +62,14 @@ function interpolationKeys(value: unknown): string[] {
}
describe("webui i18n", () => {
it("defaults to English until the user chooses another language", () => {
localStorage.removeItem(LOCALE_STORAGE_KEY);
expect(resolveInitialLocale()).toBe("en");
localStorage.setItem(LOCALE_STORAGE_KEY, "zh-CN");
expect(resolveInitialLocale()).toBe("zh-CN");
});
it("switches UI copy and document locale through the language switcher", async () => {
const user = userEvent.setup();
@@ -97,10 +106,12 @@ describe("webui i18n", () => {
expect(screen.getByLabelText("メッセージ入力欄")).toBeInTheDocument();
});
it("keeps welcome quick actions localized for every registered locale", () => {
it("keeps empty landing resources localized for every registered locale", () => {
for (const resource of Object.values(resources)) {
const empty = resource.common.thread.empty;
expect(empty.greeting).toBeTruthy();
for (const key of HERO_GREETING_KEYS) {
expect(empty.greetings[key as keyof typeof empty.greetings]).toBeTruthy();
}
for (const key of QUICK_ACTION_KEYS) {
const action = empty.quickActions[key as keyof typeof empty.quickActions];
expect(action.title).toBeTruthy();
@@ -182,7 +193,7 @@ describe("webui i18n", () => {
it("keeps Simplified Chinese settings overview copy localized", () => {
const settings = resources["zh-CN"].common.settings;
expect(settings.nav.web).toBe("网页");
expect(settings.nav.browser).toBe("网页");
expect(settings.sections.webSearch).toBe("网页搜索");
expect(settings.byok.tabs.webSearch).toBe("网页搜索");
expect(settings.overview.webSearch).toBe("网页搜索");