Skip to content

Commit d82a63e

Browse files
authored
Merge pull request #3276 from GCWing/gcwing/dev
refactor(settings): reorganize pages and fix interaction and Gemini regressions
2 parents 3f59065 + efa7a90 commit d82a63e

162 files changed

Lines changed: 7073 additions & 6143 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎docs/interactive-capabilities/technical/product-control-open-audit.json‎

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2639,7 +2639,7 @@
26392639
"pageId": "application.shortcuts"
26402640
},
26412641
"evidence": [
2642-
"source:src/web-ui/src/app/scenes/settings/components/KeyboardShortcutsTab.tsx#lists every known shortcut"
2642+
"source:src/web-ui/src/app/scenes/settings/pages/application/KeyboardShortcutsSection.tsx#lists every known shortcut"
26432643
]
26442644
},
26452645
{
@@ -2655,8 +2655,8 @@
26552655
"pageId": "application.shortcuts"
26562656
},
26572657
"evidence": [
2658-
"source:src/web-ui/src/app/scenes/settings/components/KeyboardShortcutsTab.tsx#buildFinalConflictMap",
2659-
"source:src/web-ui/src/app/scenes/settings/components/KeyboardShortcutsTab.tsx#buildStoredKeybindings({})"
2658+
"source:src/web-ui/src/app/scenes/settings/pages/application/KeyboardShortcutsSection.tsx#buildFinalConflictMap",
2659+
"source:src/web-ui/src/app/scenes/settings/pages/application/KeyboardShortcutsSection.tsx#buildStoredKeybindings({})"
26602660
]
26612661
},
26622662
{
@@ -2795,9 +2795,9 @@
27952795
"pageId": "ai.models"
27962796
},
27972797
"evidence": [
2798-
"source:src/web-ui/src/infrastructure/config/components/ModelSettingsPage.tsx#custom_headers",
2799-
"source:src/web-ui/src/infrastructure/config/components/ModelSettingsPage.tsx#custom_request_body",
2800-
"source:src/web-ui/src/infrastructure/config/components/ModelSettingsPage.tsx#skip_ssl_verify"
2798+
"source:src/web-ui/src/app/scenes/settings/pages/ai/ModelSettingsPage.tsx#custom_headers",
2799+
"source:src/web-ui/src/app/scenes/settings/pages/ai/ModelSettingsPage.tsx#custom_request_body",
2800+
"source:src/web-ui/src/app/scenes/settings/pages/ai/ModelSettingsPage.tsx#skip_ssl_verify"
28012801
]
28022802
},
28032803
{
@@ -2882,7 +2882,7 @@
28822882
"pageId": "workspace.session"
28832883
},
28842884
"evidence": [
2885-
"source:src/web-ui/src/infrastructure/config/components/DefaultHarnessConfig.tsx#DefaultHarnessConfig",
2885+
"source:src/web-ui/src/app/scenes/settings/pages/ai/DefaultHarnessSection.tsx#DefaultHarnessSection",
28862886
"source:src/web-ui/src/locales/zh-CN/settings/runtime.json#defaultHarness.title"
28872887
]
28882888
},

‎scripts/generate-interactive-capabilities.mjs‎

Lines changed: 43 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ const APPEARANCE_BOOTSTRAP_PATH = path.join(
1818
);
1919
const SETTINGS_REGISTRY = path.join(REPO_ROOT, 'src/web-ui/src/app/scenes/settings/settingsRegistry.ts');
2020
const SETTINGS_TYPES = path.join(REPO_ROOT, 'src/web-ui/src/app/scenes/settings/settingsTypes.ts');
21+
const SETTINGS_MIGRATIONS = JSON.parse(readFileSync(
22+
path.join(REPO_ROOT, 'src/web-ui/src/app/scenes/settings/settingsDestinationMigrations.json'),
23+
'utf8',
24+
));
2125
const PRODUCT_ACTION_CATALOG = path.join(REPO_ROOT, 'src/web-ui/src/app/global-search/productActionCatalog.ts');
2226
const SCENE_TYPES = path.join(REPO_ROOT, 'src/web-ui/src/app/components/SceneBar/types.ts');
2327
const TOOL_PROVIDER_GROUPS = path.join(
@@ -381,6 +385,23 @@ function controlCoverage(source) {
381385
return coverage;
382386
}
383387

388+
// Keep historical destinations on the wire for older presentation surfaces.
389+
// Validate coverage against the same migration data used by the current Web UI.
390+
export function projectSettingsCatalogDestination(capabilityId, destination, itemId) {
391+
if (destination.kind !== 'settings') return destination;
392+
const capability = SETTINGS_MIGRATIONS.capabilities[capabilityId];
393+
const input = capability?.items?.[itemId] ?? capability?.destination ?? destination;
394+
const migratedView = SETTINGS_MIGRATIONS.views[input.pageId]?.[input.viewId];
395+
const migrated = migratedView ?? SETTINGS_MIGRATIONS.pages[input.pageId];
396+
const sectionId = input.sectionId ?? migrated?.sectionId;
397+
return {
398+
kind: 'settings',
399+
pageId: migrated?.pageId ?? input.pageId,
400+
...(sectionId ? { sectionId } : {}),
401+
...(!migratedView && input.viewId ? { viewId: input.viewId } : {}),
402+
};
403+
}
404+
384405
function validateSource(source) {
385406
if (source.schemaVersion !== 5) throw new Error('Interactive capability source must use schemaVersion 5');
386407
const capabilityIds = new Set();
@@ -389,21 +410,27 @@ function validateSource(source) {
389410
[...settingsSource.matchAll(/definePage\(\{\s*id:\s*'([^']+)'/gu)].map((match) => match[1]),
390411
);
391412
const settingsViewIds = extractTypeUnionIds(readFileSync(SETTINGS_TYPES, 'utf8'), 'SettingsViewId');
413+
const settingsSectionIds = extractTypeUnionIds(readFileSync(SETTINGS_TYPES, 'utf8'), 'SettingsSectionId');
392414
const actionIds = extractSingleQuotedIds(readFileSync(PRODUCT_ACTION_CATALOG, 'utf8'));
393415
const sceneIds = new Set([
394416
...readFileSync(SCENE_TYPES, 'utf8').matchAll(/^\s*\|\s*'([^']+)'/gmu),
395417
].map((match) => match[1]));
396418
const productToolNames = extractProductToolNames(readFileSync(TOOL_PROVIDER_GROUPS, 'utf8'));
397419
const delegatedToolNames = new Set();
398420

399-
const validateDestination = (destination, owner) => {
421+
const validateDestination = (original, capabilityId, itemId) => {
422+
const owner = itemId ? `${capabilityId}.${itemId}` : capabilityId;
423+
const destination = projectSettingsCatalogDestination(capabilityId, original, itemId);
400424
if (destination.kind === 'settings') {
401425
if (!settingsIds.has(destination.pageId)) {
402426
throw new Error(`${owner} targets unknown settings page ${destination.pageId}`);
403427
}
404428
if (destination.viewId && !settingsViewIds.has(destination.viewId)) {
405429
throw new Error(`${owner} targets unknown settings view ${destination.viewId}`);
406430
}
431+
if (destination.sectionId && !settingsSectionIds.has(destination.sectionId)) {
432+
throw new Error(`${owner} targets unknown settings section ${destination.sectionId}`);
433+
}
407434
return;
408435
}
409436
if (destination.kind === 'action' && !actionIds.has(destination.actionId)) {
@@ -470,7 +497,7 @@ function validateSource(source) {
470497
itemIds.add(item.id);
471498
assertBilingual({ ...item, id: `${capability.id}.${item.id}` }, 'title');
472499
if (item.destination) {
473-
validateDestination(item.destination, `${capability.id}.${item.id}`);
500+
validateDestination(item.destination, capability.id, item.id);
474501
if (
475502
item.destination.kind === 'settings'
476503
&& capability.destination.kind === 'settings'
@@ -691,9 +718,16 @@ function validateSource(source) {
691718
throw new Error('User-facing capabilities may not silently ship as unsupported Agent controls');
692719
}
693720

694-
const semanticSettings = new Set(source.capabilities
721+
const projectedSettingsDestinations = source.capabilities
695722
.filter(({ kind }) => kind === 'setting')
696-
.map(({ destination }) => destination.kind === 'settings' ? destination.pageId : null)
723+
.flatMap((capability) => [
724+
projectSettingsCatalogDestination(capability.id, capability.destination),
725+
...capability.items.map((item) => projectSettingsCatalogDestination(
726+
capability.id, item.destination ?? capability.destination, item.id,
727+
)),
728+
]);
729+
const semanticSettings = new Set(projectedSettingsDestinations
730+
.map((destination) => destination.kind === 'settings' ? destination.pageId : null)
697731
.filter(Boolean));
698732
const missingSettings = [...settingsIds].filter((id) => !semanticSettings.has(id));
699733
const staleSettings = [...semanticSettings].filter((id) => !settingsIds.has(id));
@@ -704,10 +738,9 @@ function validateSource(source) {
704738
].filter(Boolean).join('\n'));
705739
}
706740

707-
const semanticSettingsViews = new Set(source.capabilities.flatMap((capability) =>
708-
capability.items
709-
.map(({ destination }) => destination?.kind === 'settings' ? destination.viewId : null)
710-
.filter(Boolean)));
741+
const semanticSettingsViews = new Set(projectedSettingsDestinations
742+
.map((destination) => destination.kind === 'settings' ? destination.viewId : null)
743+
.filter(Boolean));
711744
const missingSettingsViews = [...settingsViewIds].filter((id) => !semanticSettingsViews.has(id));
712745
const staleSettingsViews = [...semanticSettingsViews].filter((id) => !settingsViewIds.has(id));
713746
if (missingSettingsViews.length || staleSettingsViews.length) {
@@ -1034,7 +1067,7 @@ function loadOwnerDefinitions() {
10341067
const result = spawnSync(
10351068
'cargo',
10361069
['run', '--quiet', '-p', 'openbitfun-product-domains', '--bin', PRODUCT_CONTROL_OWNER_EXPORT],
1037-
{ cwd: REPO_ROOT, encoding: 'utf8', maxBuffer: 16 * 1024 * 1024 },
1070+
{ cwd: REPO_ROOT, encoding: 'utf8', maxBuffer: 16 * 1024 * 1024, windowsHide: true },
10381071
);
10391072
if (result.status !== 0) {
10401073
throw new Error([
@@ -1067,7 +1100,7 @@ export function loadRemoteSurfaceRegistry() {
10671100
const result = spawnSync(
10681101
'cargo',
10691102
['run', '--quiet', '-p', 'openbitfun-product-domains', '--bin', REMOTE_SURFACE_EXPORT],
1070-
{ cwd: REPO_ROOT, encoding: 'utf8', maxBuffer: 16 * 1024 * 1024 },
1103+
{ cwd: REPO_ROOT, encoding: 'utf8', maxBuffer: 16 * 1024 * 1024, windowsHide: true },
10711104
);
10721105
if (result.status !== 0) {
10731106
throw new Error([

‎scripts/i18n-dynamic-key-allowlist.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@
8383
"id": "web-editor-option-metadata",
8484
"surface": "web-ui",
8585
"namespace": "settings/editor",
86-
"owner": "src/web-ui/src/infrastructure/config/components/EditorConfig.tsx",
86+
"owner": "src/web-ui/src/app/scenes/settings/pages/development/EditorSettingsPage.tsx",
8787
"description": "Editor option descriptors store labelKey metadata resolved with the settings/editor namespace.",
8888
"sourceReferences": [
8989
"labelKey: 'appearance.cursorStyles.line'",

‎scripts/interactive-capabilities.test.mjs‎

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,33 @@ import {
88
buildCapabilityCatalog,
99
loadRemoteSurfaceRegistry,
1010
parseRegisteredCommands,
11+
projectSettingsCatalogDestination,
1112
renderRemoteSurfaceTsBindings,
1213
} from './generate-interactive-capabilities.mjs';
1314

1415
const repositoryRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
1516
const read = (relativePath) => readFile(path.join(repositoryRoot, relativePath), 'utf8');
1617

18+
test('historical settings wire targets resolve through the current presentation migration', () => {
19+
const { runtimeCatalog } = buildCapabilityCatalog();
20+
const execution = runtimeCatalog.capabilities.find(({ id }) => id === 'setting.tools.execution');
21+
const permissions = execution.items.find(({ id }) => id === 'permission-mode');
22+
assert.deepEqual(permissions.destination, { kind: 'settings', pageId: 'tools.execution', viewId: 'common' });
23+
assert.deepEqual(projectSettingsCatalogDestination(execution.id, permissions.destination, permissions.id), {
24+
kind: 'settings', pageId: 'ai.permissions',
25+
});
26+
const automation = runtimeCatalog.capabilities.find(({ id }) => id === 'setting.tools.automation');
27+
const hooks = automation.items.find(({ id }) => id === 'hooks-enabled');
28+
assert.deepEqual(hooks.destination, { kind: 'settings', pageId: 'tools.automation', viewId: 'hooks' });
29+
assert.deepEqual(projectSettingsCatalogDestination(automation.id, hooks.destination, hooks.id), {
30+
kind: 'settings', pageId: 'tools.automation', sectionId: 'hooks',
31+
});
32+
const computerUse = runtimeCatalog.capabilities.find(({ id }) => id === 'feature.computer-use');
33+
assert.deepEqual(projectSettingsCatalogDestination(computerUse.id, computerUse.destination), {
34+
kind: 'settings', pageId: 'tools.desktop-control',
35+
});
36+
});
37+
1738
test('the public contract is a compact feature-and-settings manual', () => {
1839
const { publicCatalog, runtimeCatalog } = buildCapabilityCatalog();
1940
const featureCount = publicCatalog.capabilities.filter(({ kind }) => kind === 'feature').length;
@@ -352,7 +373,9 @@ test('docs, runtime, and technical views are generated projections of one semant
352373
assert.ok(interactionAudit.files.some(({ sourceFile }) =>
353374
sourceFile.endsWith('/AssistantDefaultsPage.tsx')));
354375
assert.ok(interactionAudit.files.some(({ sourceFile }) =>
355-
sourceFile.endsWith('/AppearanceSettingsPage.tsx')));
376+
sourceFile.endsWith('/LanguageSettingsSection.tsx')));
377+
assert.ok(interactionAudit.files.some(({ sourceFile }) =>
378+
sourceFile.endsWith('/AppearancePackageConfigSection.tsx')));
356379
assert.equal(publicCatalog.source, 'src/shared/interactive-capabilities/catalog.json');
357380

358381
const appearance = runtimeCatalog.capabilities

‎src/crates/adapters/ai-adapters/src/client/format.rs‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,21 @@ impl ApiFormat {
2727
}
2828
}
2929
}
30+
31+
#[cfg(test)]
32+
mod tests {
33+
use super::ApiFormat;
34+
35+
#[test]
36+
fn gemini_api_key_format_is_distinct_from_code_assist() {
37+
for format in ["gemini", "google", " GEMINI "] {
38+
assert_eq!(ApiFormat::parse(format).unwrap(), ApiFormat::Gemini);
39+
}
40+
for format in ["gemini-code-assist", "gemini_code_assist", "code-assist"] {
41+
assert_eq!(
42+
ApiFormat::parse(format).unwrap(),
43+
ApiFormat::GeminiCodeAssist
44+
);
45+
}
46+
}
47+
}

‎src/crates/adapters/ai-adapters/src/providers/gemini/discovery.rs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ pub(crate) async fn list_models(client: &AIClient) -> Result<Vec<RemoteModelInfo
4040
let url = resolve_models_url(client);
4141
debug!("Gemini models list URL: {}", url);
4242

43-
let response = apply_headers(client, client.client.get(&url))
43+
let response = apply_headers(client, client.client.get(&url), &url)
4444
.send()
4545
.await?
4646
.error_for_status()?;

0 commit comments

Comments
 (0)