chore(release): bump to v0.6.2 and gate account access

This commit is contained in:
Haitao Pan 2026-03-23 17:49:17 +08:00
parent 144428f054
commit 47273a2d41
6 changed files with 146 additions and 43 deletions

View File

@ -134,6 +134,12 @@ mobile:
build_modes: [debug, profile, release]
description: Mobile settings gateway tab
ui_surface: settings_page
account_access:
enabled: false
release_tier: experimental
build_modes: [debug, profile, release]
description: Mobile account access section
ui_surface: settings_page
vault_server:
enabled: false
release_tier: experimental
@ -319,6 +325,12 @@ desktop:
build_modes: [debug, profile, release]
description: Desktop settings gateway tab
ui_surface: settings_page
account_access:
enabled: false
release_tier: experimental
build_modes: [debug, profile, release]
description: Desktop account access section
ui_surface: settings_page
vault_server:
enabled: false
release_tier: experimental
@ -444,6 +456,12 @@ web:
build_modes: [debug, profile, release]
description: Web settings gateway tab
ui_surface: web_settings_page
account_access:
enabled: false
release_tier: experimental
build_modes: []
description: Web does not expose account access section
ui_surface: web_settings_page
vault_server:
enabled: false
release_tier: experimental

View File

@ -65,6 +65,7 @@ abstract final class UiFeatureKeys {
static const settingsGeneral = 'settings.general';
static const settingsWorkspace = 'settings.workspace';
static const settingsGateway = 'settings.gateway';
static const settingsAccountAccess = 'settings.account_access';
static const settingsVaultServer = 'settings.vault_server';
static const settingsGatewaySetupCode = 'settings.gateway_setup_code';
static const settingsAgents = 'settings.agents';
@ -256,6 +257,12 @@ mobile:
build_modes: [debug, profile, release]
description: Mobile settings gateway tab
ui_surface: settings_page
account_access:
enabled: false
release_tier: experimental
build_modes: [debug, profile, release]
description: Mobile account access section
ui_surface: settings_page
vault_server:
enabled: false
release_tier: experimental
@ -441,6 +448,12 @@ desktop:
build_modes: [debug, profile, release]
description: Desktop settings gateway tab
ui_surface: settings_page
account_access:
enabled: false
release_tier: experimental
build_modes: [debug, profile, release]
description: Desktop account access section
ui_surface: settings_page
vault_server:
enabled: false
release_tier: experimental
@ -566,6 +579,12 @@ web:
build_modes: [debug, profile, release]
description: Web settings gateway tab
ui_surface: web_settings_page
account_access:
enabled: false
release_tier: experimental
build_modes: []
description: Web does not expose account access section
ui_surface: web_settings_page
vault_server:
enabled: false
release_tier: experimental
@ -948,6 +967,9 @@ class UiFeatureAccess {
bool get supportsDiagnostics =>
isEnabledPath(UiFeatureKeys.settingsDiagnostics);
bool get supportsAccountAccess =>
isEnabledPath(UiFeatureKeys.settingsAccountAccess);
bool get supportsGatewaySetupCode =>
isEnabledPath(UiFeatureKeys.settingsGatewaySetupCode);

View File

@ -240,7 +240,12 @@ class _SettingsPageState extends State<SettingsPage> {
}
return switch (_tab) {
SettingsTab.general => _buildGeneral(context, controller, settings),
SettingsTab.general => _buildGeneral(
context,
controller,
settings,
uiFeatures,
),
SettingsTab.workspace => _buildWorkspace(context, controller, settings),
SettingsTab.gateway => _buildGateway(
context,
@ -458,6 +463,7 @@ class _SettingsPageState extends State<SettingsPage> {
BuildContext context,
AppController controller,
SettingsSnapshot settings,
UiFeatureAccess uiFeatures,
) {
return [
SurfaceCard(
@ -492,55 +498,57 @@ class _SettingsPageState extends State<SettingsPage> {
settings.copyWith(showDockIcon: value),
),
),
_SwitchRow(
label: appText('账号本地模式', 'Account local mode'),
value: settings.accountLocalMode,
onChanged: (value) => _saveSettings(
controller,
settings.copyWith(accountLocalMode: value),
if (uiFeatures.supportsAccountAccess)
_SwitchRow(
label: appText('账号本地模式', 'Account local mode'),
value: settings.accountLocalMode,
onChanged: (value) => _saveSettings(
controller,
settings.copyWith(accountLocalMode: value),
),
),
),
],
),
),
if (controller.supportsDesktopIntegration)
_buildLinuxDesktopIntegration(context, controller, settings),
SurfaceCard(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
appText('账号访问', 'Account Access'),
style: Theme.of(context).textTheme.titleLarge,
),
const SizedBox(height: 16),
_EditableField(
label: appText('账号服务地址', 'Account Base URL'),
value: settings.accountBaseUrl,
onSubmitted: (value) => _saveSettings(
controller,
settings.copyWith(accountBaseUrl: value),
if (uiFeatures.supportsAccountAccess)
SurfaceCard(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
appText('账号访问', 'Account Access'),
style: Theme.of(context).textTheme.titleLarge,
),
),
_EditableField(
label: appText('账号用户名', 'Account Username'),
value: settings.accountUsername,
onSubmitted: (value) => _saveSettings(
controller,
settings.copyWith(accountUsername: value),
const SizedBox(height: 16),
_EditableField(
label: appText('账号服务地址', 'Account Base URL'),
value: settings.accountBaseUrl,
onSubmitted: (value) => _saveSettings(
controller,
settings.copyWith(accountBaseUrl: value),
),
),
),
_EditableField(
label: appText('工作区名称', 'Workspace Label'),
value: settings.accountWorkspace,
onSubmitted: (value) => _saveSettings(
controller,
settings.copyWith(accountWorkspace: value),
_EditableField(
label: appText('账号用户名', 'Account Username'),
value: settings.accountUsername,
onSubmitted: (value) => _saveSettings(
controller,
settings.copyWith(accountUsername: value),
),
),
),
],
_EditableField(
label: appText('工作区名称', 'Workspace Label'),
value: settings.accountWorkspace,
onSubmitted: (value) => _saveSettings(
controller,
settings.copyWith(accountWorkspace: value),
),
),
],
),
),
),
];
}

View File

@ -2,7 +2,7 @@ name: xworkmate
description: "XWorkmate desktop-first AI workspace shell."
publish_to: 'none'
version: 0.6.1+1
version: 0.6.2+1
build-date: 2026-03-20
build-id: 4183a40

View File

@ -91,6 +91,48 @@ void main() {
expect(controller.themeMode, ThemeMode.light);
});
testWidgets('SettingsPage hides account access controls by default', (
WidgetTester tester,
) async {
final controller = await createTestController(tester);
await pumpPage(
tester,
child: SettingsPage(controller: controller),
platform: TargetPlatform.macOS,
);
expect(find.text('账号访问'), findsNothing);
expect(find.text('Account Access'), findsNothing);
expect(find.text('账号本地模式'), findsNothing);
expect(find.text('Account local mode'), findsNothing);
});
testWidgets('SettingsPage can expose account access when feature enabled', (
WidgetTester tester,
) async {
final manifest = UiFeatureManifest.fallback().copyWithFeature(
platform: UiFeaturePlatform.desktop,
module: 'settings',
feature: 'account_access',
enabled: true,
releaseTier: UiFeatureReleaseTier.experimental,
);
final controller = await createTestController(
tester,
uiFeatureManifest: manifest,
);
await pumpPage(
tester,
child: SettingsPage(controller: controller),
platform: TargetPlatform.macOS,
);
expect(find.text('账号访问'), findsOneWidget);
expect(find.text('账号本地模式'), findsOneWidget);
});
testWidgets('SettingsPage integration tab exposes unified gateway controls', (
WidgetTester tester,
) async {

View File

@ -236,7 +236,7 @@ void main() {
final controller = AppController(desktopPlatformService: service);
addTearDown(controller.dispose);
await Future<void>.delayed(const Duration(milliseconds: 50));
await _waitFor(() => !controller.initializing);
expect(controller.supportsDesktopIntegration, isTrue);
expect(
@ -276,7 +276,7 @@ void main() {
addTearDown(server.close);
addTearDown(controller.dispose);
await Future<void>.delayed(const Duration(milliseconds: 50));
await _waitFor(() => !controller.initializing);
final result = await controller.testGatewayConnectionDraft(
profile: GatewayConnectionProfile.defaults().copyWith(
@ -296,3 +296,16 @@ void main() {
},
);
}
Future<void> _waitFor(
bool Function() condition, {
Duration timeout = const Duration(seconds: 2),
}) async {
final stopwatch = Stopwatch()..start();
while (!condition()) {
if (stopwatch.elapsed > timeout) {
fail('Condition not met within ${timeout.inMilliseconds}ms');
}
await Future<void>.delayed(const Duration(milliseconds: 10));
}
}