Skip to content

feat: add AppleOS discriminant to the device model (additive)#896

Merged
thymikee merged 2 commits into
mainfrom
feat/apple-os-discriminant
Jun 27, 2026
Merged

feat: add AppleOS discriminant to the device model (additive)#896
thymikee merged 2 commits into
mainfrom
feat/apple-os-discriminant

Conversation

@thymikee

Copy link
Copy Markdown
Member

What

Adds an explicit, stored Apple operating-system discriminant to the device model:

  • device.ts: new export type AppleOS = 'ios' | 'ipados' | 'tvos' | 'watchos' | 'visionos' | 'macos' (all six literals reserved) and an optional appleOs?: AppleOS field on DeviceInfo.
  • resolveApplePlatformName now accepts an optional appleOs and prefers it, falling back to the existing DeviceTarget-based inference when it is absent. Mapping: ios/ipados -> the single iOS runner profile (iOS), tvos -> tvOS, macos -> macOS.
  • resolveRunnerPlatformName threads device.appleOs through.
  • Discovery populates appleOs (no widened filters; watchOS/visionOS stay dropped as today):
    • iPhone/iPod simulator+device -> ios
    • iPad (productType/name /ipad/) -> ipados
    • tvOS runtime / AppleTV productType -> tvos
    • host Mac (buildHostMacDevice) -> macos

Why

Today an Apple device's OS is inferred late and lossily from DeviceTarget. Storing it makes iOS/iPadOS/tvOS/macOS first-class and unambiguous. This is non-breaking groundwork for the platforms/apple consolidation.

Non-breaking

  • The field is optional; legacy DeviceInfo records without appleOs resolve byte-identically through the existing target inference.
  • No runner SDK/destination selection changes. iPadOS uses the iOS runner profile/SDK exactly as iPad does today (no new profile). A test asserts the iPadOS runner profile and destination are identical to the legacy iPad record.

Validation

All commands run from the worktree and passed:

  • Typecheck: node_modules/.bin/tsc -p tsconfig.json --noEmit (exit 0)
  • Format: node ./node_modules/oxfmt/bin/oxfmt --write <changed .ts files>
  • Lint: node_modules/.bin/oxlint <changed .ts files> --deny-warnings (exit 0)
  • Tests: node_modules/.bin/vitest run devices apple-runner-platform capabilities dispatch-resolve runner-xctestrun (66 passed)
  • Extra regression check: vitest run platforms/ios/__tests__/index platforms/macos session-device-utils session-open-target (91 passed)

@github-actions

github-actions Bot commented Jun 27, 2026

Copy link
Copy Markdown

Size Report

Metric Base Current Diff
JS raw 1.4 MB 1.4 MB +357 B
JS gzip 445.2 kB 445.4 kB +145 B
npm tarball 583.9 kB 584.1 kB +205 B
npm unpacked 2.0 MB 2.0 MB +469 B

Startup median (7 runs, lower is better):

Scenario Base Current Diff
CLI --version 26.9 ms 28.7 ms +1.8 ms
CLI --help 47.5 ms 48.7 ms +1.2 ms

Top changed chunks:

Chunk Raw diff Gzip diff
dist/src/9722.js +10 B +8 B
dist/src/session.js +10 B +6 B

@thymikee

Copy link
Copy Markdown
Member Author

Sequencing note: this PR is clean against current main, but it conflicts with sibling #895 in src/utils/device.ts and src/utils/__tests__/device.test.ts. I would merge #895 first because it establishes the canonical/exported platform tuple, then rebase this branch and keep both changes: exported PLATFORMS/isPlatform from #895 plus the new AppleOS/appleOs additions here. CI is green and I do not see a code blocker in this diff; this is just to avoid a surprise conflict after the first sibling lands.

@thymikee thymikee left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed end-to-end. The discriminant is genuinely additive: appleOs is optional, and for every record discovery actually produces, resolveRunnerPlatformName returns the same value as the old target-only inference (iPad → iPadOS → iOS profile, iPhone/iPod → iOS, tvOS → tvOS, host Mac short-circuits on platform === 'macos'), so runner SDK/destination selection is unchanged — confirmed by trace and by the byte-identical iPad test. Locally tsc -p tsconfig.json --noEmit is clean and the targeted suites pass (62). Only non-test caller of resolveApplePlatformName is resolveRunnerPlatformName, which now threads appleOs, so no caller is left inconsistent.

One actionable notesrc/daemon/handlers/session-inventory.ts:91-93: the publicDevices projection strips only simulatorSetPath, so appleOs now rides into the public devices inventory returned to clients/agents. That's additive and ADR-0006-safe (a "new optional response field older clients can ignore" — no rpcProtocolVersion bump), and it's arguably useful (an agent can now tell iPad from iPhone). But the PR frames appleOs as internal-only groundwork, and nothing pins the devices output shape, so this surfaces silently. Worth a deliberate call: keep it and treat the richer output as intended, or strip it here like simulatorSetPath if it should stay internal for now.

Two minor, non-blocking nits:

  • The simctl simulator path derives appleOs from the device name only (resolveAppleOs(target, [device.name])), so a user-renamed iPad sim (name without "iPad") tags as ios. No functional impact today since iOS/iPadOS share the iOS runner profile; deviceTypeIdentifier would be more robust if the discriminant ever drives behavior.
  • resolveRunnerPlatformNameForAppleOs's default: silently routes the reserved watchos/visionos to the iOS profile. Fine as documented groundwork, but that's the spot to make exhaustive when those OSes actually get discovered.

Sequencing: agree with your own note — land #895 first, then rebase keeping both. The device.ts / device.test.ts overlap is purely textual/co-located; isPlatform (leaf-platform guard) and AppleOS (Apple-OS discriminant) are orthogonal, so it's a mechanical keep-both with no logic to reconcile. Merge-ready after that rebase.

@thymikee

Copy link
Copy Markdown
Member Author

Thanks @thymikee. Addressed:

(a) Done — kept appleOs internal as framed. The public devices projection in session-inventory.ts now strips it alongside simulatorSetPath: ({ simulatorSetPath: _simulatorSetPath, appleOs: _appleOs, ...device }) => device. Added a handler test asserting appleOs (and simulatorSetPath) do not appear on the returned devices. Surfacing appleOs to agents stays a deliberate later change.

(b) Fixed via deviceTypeIdentifier. simctl's per-device JSON readily exposes it (e.g. com.apple.CoreSimulator.SimDeviceType.iPad-Pro-11-inch-M4), so parseSimctlAppleDevices now classifies from [device.deviceTypeIdentifier ?? '', device.name] — the stable type id wins and a user-renamed iPad sim no longer mis-tags as ios; the display name remains a fallback. Added a test with a renamed iPad sim ("Work Tablet") that still resolves to ipados.

(c) Acknowledged — resolveRunnerPlatformNameForAppleOs's default routing reserved watchos/visionos to the iOS profile stays documented groundwork; that's the spot to make exhaustive once those OSes are actually discovered.

Sequencing: agree — merge #895 first, then rebase this keeping both changes.

Verified: tsc --noEmit clean, oxfmt/oxlint clean, vitest run session-inventory devices apple-runner-platform + the new session.test.ts projection test all green.

thymikee added 2 commits June 27, 2026 13:51
Add an explicit, stored AppleOS ('ios' | 'ipados' | 'tvos' | 'watchos' |
'visionos' | 'macos') and an optional appleOs field on DeviceInfo so Apple
operating systems are first-class instead of inferred late from DeviceTarget.

- device.ts: add AppleOS type, optional DeviceInfo.appleOs, and make
  resolveApplePlatformName prefer device.appleOs while falling back to the
  existing target-based inference for legacy records. iOS and iPadOS both map
  to the single iOS runner profile; tvOS -> tvOS; macOS -> macOS.
- Populate appleOs at discovery only (no widened filters): iPhone/iPod -> ios,
  iPad -> ipados, tvOS -> tvos, host Mac -> macos.
- resolveRunnerPlatformName threads device.appleOs through.

Non-breaking groundwork for the platforms/apple consolidation: records without
appleOs resolve byte-identically, and no runner SDK/destination selection
changes (iPad still resolves to the iOS runner profile).
@thymikee thymikee force-pushed the feat/apple-os-discriminant branch from fe1450f to a74be4b Compare June 27, 2026 11:53
@thymikee thymikee merged commit 22a9b2f into main Jun 27, 2026
20 checks passed
@thymikee thymikee deleted the feat/apple-os-discriminant branch June 27, 2026 12:00
@github-actions

Copy link
Copy Markdown
PR Preview Action v1.8.1
Preview removed because the pull request was closed.
2026-06-27 12:00 UTC

thymikee added a commit that referenced this pull request Jun 27, 2026
…on) (#905)

Locks the two axis decisions and starts retiring plans/ into ADRs. ADR 0008
(Proposed) records the command-descriptor registry composing domain-owned facets
and deriving the ~10 tables, bound by ADR 0003's four invariants; ADR 0009
(Accepted, groundwork shipped in #896) records the AppleOS leaf axis under one
'apple' Platform. perfect-shape.md links both and marks Phase 0 + Tier-A dedup as
merged.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant