Skip to content

Commit 82d3101

Browse files
committed
refactor(setup): persist wizard platform selection in setupPreferences
The setup wizard was writing the user's "which platforms does this project target?" answer into sessionDefaults.platform, conflating UI memory with a runtime tool-param default. It also relied on sessionDefaults.simulatorPlatform (an internal cache) to recover the non-macOS half of multi-platform selections, which silently reverted [macOS, visionOS] to [macOS, iOS] on re-run. Move wizard memory to a dedicated top-level setupPreferences.platforms field. sessionDefaults.platform/simulatorPlatform are no longer touched by setup; they remain agent-controlled session defaults. The mcp-json output still seeds XCODEBUILDMCP_PLATFORM for fresh clients, since that is an explicit env-var bootstrap, not internal state. Follow-up #366 tracks moving simulatorPlatform out of sessionDefaults.
1 parent f0eb257 commit 82d3101

4 files changed

Lines changed: 49 additions & 31 deletions

File tree

src/cli/commands/__tests__/setup.test.ts

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1337,9 +1337,11 @@ sessionDefaults:
13371337

13381338
const parsed = parseYaml(storedConfig) as {
13391339
sessionDefaults?: Record<string, unknown>;
1340+
setupPreferences?: { platforms?: string[] };
13401341
};
13411342

1342-
expect(parsed.sessionDefaults?.platform).toBe('macOS');
1343+
expect(parsed.setupPreferences?.platforms).toEqual(['macOS']);
1344+
expect(parsed.sessionDefaults?.platform).toBeUndefined();
13431345
expect(parsed.sessionDefaults?.simulatorId).toBeUndefined();
13441346
expect(parsed.sessionDefaults?.simulatorName).toBeUndefined();
13451347
});
@@ -1577,7 +1579,12 @@ sessionDefaults:
15771579
sessionDefaults?: Record<string, unknown>;
15781580
};
15791581

1580-
expect(parsed.sessionDefaults?.platform).toBe('macOS');
1582+
expect(
1583+
(parsed as { setupPreferences?: { platforms?: string[] } }).setupPreferences?.platforms,
1584+
).toEqual(['macOS']);
1585+
// setup intentionally does not touch sessionDefaults.platform (agent-controlled field);
1586+
// the pre-existing value from the fixture is preserved.
1587+
expect(parsed.sessionDefaults?.platform).toBe('iOS Simulator');
15811588
expect(parsed.sessionDefaults?.deviceId).toBeUndefined();
15821589
expect(parsed.sessionDefaults?.simulatorId).toBeUndefined();
15831590
expect(parsed.sessionDefaults?.simulatorName).toBeUndefined();
@@ -1648,7 +1655,10 @@ sessionDefaults:
16481655
sessionDefaults?: Record<string, unknown>;
16491656
};
16501657

1651-
expect(parsed.sessionDefaults?.platform).toBe('tvOS Simulator');
1658+
expect(
1659+
(parsed as { setupPreferences?: { platforms?: string[] } }).setupPreferences?.platforms,
1660+
).toEqual(['tvOS']);
1661+
expect(parsed.sessionDefaults?.platform).toBeUndefined();
16521662
expect(parsed.sessionDefaults?.simulatorId).toBe('TVOS-1');
16531663
expect(parsed.sessionDefaults?.simulatorName).toBe('Apple TV 4K');
16541664
});
@@ -1723,7 +1733,10 @@ sessionDefaults:
17231733
sessionDefaults?: Record<string, unknown>;
17241734
};
17251735

1726-
expect(parsed.sessionDefaults?.platform).toBe('watchOS Simulator');
1736+
expect(
1737+
(parsed as { setupPreferences?: { platforms?: string[] } }).setupPreferences?.platforms,
1738+
).toEqual(['watchOS']);
1739+
expect(parsed.sessionDefaults?.platform).toBeUndefined();
17271740
expect(parsed.sessionDefaults?.simulatorId).toBe('WATCH-1');
17281741
expect(parsed.sessionDefaults?.simulatorName).toBe('Apple Watch Series 9');
17291742
});
@@ -1793,7 +1806,10 @@ sessionDefaults:
17931806
sessionDefaults?: Record<string, unknown>;
17941807
};
17951808

1796-
expect(parsed.sessionDefaults?.platform).toBe('visionOS Simulator');
1809+
expect(
1810+
(parsed as { setupPreferences?: { platforms?: string[] } }).setupPreferences?.platforms,
1811+
).toEqual(['visionOS']);
1812+
expect(parsed.sessionDefaults?.platform).toBeUndefined();
17971813
expect(parsed.sessionDefaults?.simulatorId).toBe('XROS-1');
17981814
expect(parsed.sessionDefaults?.simulatorName).toBe('Apple Vision Pro');
17991815
});

src/cli/commands/setup.ts

Lines changed: 12 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -182,26 +182,16 @@ function normalizeExistingDefaults(config?: ProjectConfig): {
182182
function inferPlatformsFromExisting(config?: ProjectConfig): SetupPlatform[] {
183183
if (!config) return [];
184184

185-
const platform = config.sessionDefaults?.platform;
186-
if (platform != null && SESSION_DEFAULT_TO_SETUP_PLATFORM[platform] != null) {
187-
return [SESSION_DEFAULT_TO_SETUP_PLATFORM[platform]];
185+
const stored = config.setupPreferences?.platforms;
186+
if (stored && stored.length > 0) {
187+
return [...stored];
188188
}
189189

190-
// Multi-platform or legacy config: macOS is recovered from the workflow set,
191-
// the non-macOS component from the cached simulatorPlatform.
192-
const results: SetupPlatform[] = [];
190+
// No stored preference: only macOS is unambiguously recoverable from enabledWorkflows.
191+
// Simulator-platform identity (iOS vs tvOS vs watchOS vs visionOS) cannot be inferred
192+
// from workflow ids alone, so leave it blank and let the wizard re-prompt.
193193
const workflows = new Set(config.enabledWorkflows ?? []);
194-
if (workflows.has('macos')) results.push('macOS');
195-
196-
const simPlatform = config.sessionDefaults?.simulatorPlatform;
197-
const fromSim = simPlatform != null ? SESSION_DEFAULT_TO_SETUP_PLATFORM[simPlatform] : undefined;
198-
if (fromSim != null && fromSim !== 'macOS') {
199-
results.push(fromSim);
200-
} else if (workflows.has('simulator')) {
201-
results.push('iOS');
202-
}
203-
204-
return results;
194+
return workflows.has('macos') ? ['macOS'] : [];
205195
}
206196

207197
function derivePlatformSessionDefault(platforms: SetupPlatform[]): string | undefined {
@@ -347,9 +337,9 @@ function getChangedFields(
347337
afterValue: afterDefaults.simulatorName,
348338
},
349339
{
350-
label: 'sessionDefaults.platform',
351-
beforeValue: beforeDefaults.platform,
352-
afterValue: afterDefaults.platform,
340+
label: 'setupPreferences.platforms',
341+
beforeValue: beforeConfig?.setupPreferences?.platforms,
342+
afterValue: afterConfig.setupPreferences?.platforms,
353343
},
354344
];
355345

@@ -1113,11 +1103,6 @@ export async function runSetupWizard(deps?: Partial<SetupDependencies>): Promise
11131103
deleteSessionDefaultKeys.push('simulatorId', 'simulatorName');
11141104
}
11151105

1116-
const derivedPlatform = derivePlatformSessionDefault(selection.platforms);
1117-
if (!derivedPlatform) {
1118-
deleteSessionDefaultKeys.push('platform');
1119-
}
1120-
11211106
const persistedProjectPath =
11221107
selection.projectPath != null
11231108
? relativePathOrAbsolute(selection.projectPath, resolvedDeps.cwd)
@@ -1141,8 +1126,9 @@ export async function runSetupWizard(deps?: Partial<SetupDependencies>): Promise
11411126
deviceId: selection.deviceId,
11421127
simulatorId: selection.simulatorId,
11431128
simulatorName: selection.simulatorName,
1144-
platform: derivedPlatform,
11451129
},
1130+
setupPreferences:
1131+
selection.platforms.length > 0 ? { platforms: [...selection.platforms] } : null,
11461132
},
11471133
deleteSessionDefaultKeys,
11481134
});

src/utils/project-config.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,10 @@ export type PersistActiveSessionDefaultsProfileOptions = {
4646
profile?: string | null;
4747
};
4848

49+
export type SetupPreferences = {
50+
platforms?: ('macOS' | 'iOS' | 'tvOS' | 'watchOS' | 'visionOS')[];
51+
};
52+
4953
export type PersistProjectConfigPatchOptions = {
5054
fs: FileSystemExecutor;
5155
cwd: string;
@@ -56,6 +60,7 @@ export type PersistProjectConfigPatchOptions = {
5660
experimentalWorkflowDiscovery?: boolean;
5761
disableSessionDefaults?: boolean;
5862
sessionDefaults?: Partial<SessionDefaults>;
63+
setupPreferences?: SetupPreferences | null;
5964
};
6065
deleteSessionDefaultKeys?: (keyof SessionDefaults)[];
6166
};
@@ -424,6 +429,12 @@ export async function persistProjectConfigPatch(
424429
nextConfig[key] = value;
425430
}
426431

432+
if (options.patch.setupPreferences === null) {
433+
delete nextConfig.setupPreferences;
434+
} else if (options.patch.setupPreferences !== undefined) {
435+
nextConfig.setupPreferences = options.patch.setupPreferences;
436+
}
437+
427438
if (options.patch.sessionDefaults) {
428439
const patch = removeUndefined(options.patch.sessionDefaults as Record<string, unknown>);
429440
const nextSessionDefaults: Partial<SessionDefaults> = {

src/utils/runtime-config-schema.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@ export const runtimeConfigFileSchema = z
2525
sessionDefaults: sessionDefaultsSchema.optional(),
2626
sessionDefaultsProfiles: z.record(z.string(), sessionDefaultsSchema).optional(),
2727
activeSessionDefaultsProfile: z.string().optional(),
28+
setupPreferences: z
29+
.object({
30+
platforms: z.array(z.enum(['macOS', 'iOS', 'tvOS', 'watchOS', 'visionOS'])).optional(),
31+
})
32+
.optional(),
2833
})
2934
.passthrough();
3035

0 commit comments

Comments
 (0)