diff --git a/.github/workflows/ios.yml b/.github/workflows/ios.yml index 1fe9148..e6c3090 100644 --- a/.github/workflows/ios.yml +++ b/.github/workflows/ios.yml @@ -80,6 +80,25 @@ jobs: -destination 'platform=iOS Simulator,name=iPhone 16' \ test + # iPad adaptation (T-iPad-1): run the same app suite on an iPad simulator so + # the adaptive layout (regular size class / NavigationSplitView, T-iPad-2) is + # exercised in CI, not only the compact iPhone path. + ipad-tests: + runs-on: macos-15 + steps: + - uses: actions/checkout@v4 + - name: Select Xcode 16.3 + run: sudo xcode-select -s /Applications/Xcode_16.3.app/Contents/Developer + - name: Install XcodeGen + run: brew install xcodegen + - name: Generate project + run: cd ios && xcodegen generate + - name: xcodebuild test (WebTermTests, iPad Pro 11-inch simulator) + run: | + xcodebuild -project ios/WebTerm.xcodeproj -scheme WebTerm \ + -destination 'platform=iOS Simulator,name=iPad Pro 11-inch (M4)' \ + test + # Layer 3: contract tests against the real Node server (T-iOS-16 test list). # npm ci compiles node-pty (needs the Xcode toolchain — present on the # runner); the Swift ServerHarness then boots the server itself. diff --git a/docs/PLAN_IOS_IPAD.md b/docs/PLAN_IOS_IPAD.md new file mode 100644 index 0000000..ed4f3cb --- /dev/null +++ b/docs/PLAN_IOS_IPAD.md @@ -0,0 +1,231 @@ +# PLAN_IOS_IPAD.md — iPad 适配(自适应布局,非分叉) + +> 落地方案文档。目标:让已完成的 iPhone 客户端([PLAN_IOS_CLIENT.md](./PLAN_IOS_CLIENT.md),P0+P1 已交付,分支 `feat/ios-client`)**原生适配 iPad**——大屏分栏、双向布局、指针/硬件键盘,而**不分叉出第二套 UI**。 +> 拓扑决策:**单一代码库 + size-class 自适应**——`NavigationSplitView` 在 regular 宽度(iPad 全屏/大分屏)给 sidebar+detail,在 compact 宽度(iPhone、iPad Slide Over/小分屏)**自动退化为现有 stack**。iPhone 行为字节级不变。 +> 状态:**规划中(2026-07-05,未开工)**。 +> 本文是 iPhone 计划之上的**布局适配层**,不改协议/会话模型/纯逻辑包;沿用 [PLAN_IOS_CLIENT.md](./PLAN_IOS_CLIENT.md) 的 §3 契约、§4 工程标准、§5 安全模型、§6 并行规则。冲突以 iPhone 计划为准。 +> **G1 日志铁律**:`PROGRESS_LOG.md` 由 orchestrator 独写;被派 subagent 不写 LOG,在返回消息末尾附可粘贴条目。 + +--- + +## 0. 目标与范围 + +### 做什么 + +在 iPad(iPadOS 17+)上把「口袋驾驶舱」升级为「桌面级驾驶舱」,利用大屏做手机做不到的事: + +- **分栏常驻**:左 sidebar = 会话列表(+ Projects 入口),右 detail = 终端 + gate/digest 叠层——不用来回 push/pop,一眼看全 + 直接介入。 +- **终端更宽**:iPad 全屏能放下接近桌面的列数,直接缓解「宽桌面 + 窄手机同看一个全屏 TUI 折行成竖条」的多设备张力(见 [PROGRESS_LOG](./PROGRESS_LOG.md) 该条)——iPad 自己就是宽屏 writer。 +- **双向布局**:横竖屏、Split View、Slide Over、Stage Manager 尺寸变化全程 size-class 自适应,绝不写死方向。 +- **硬件键盘为一等公民**:iPad 常接键盘——现有 `UIKeyCommand` 全键位复用;软键盘 KeyBar(`inputAccessoryView`)在有硬件键盘时可隐、无则保留。 +- **指针/悬停**:iPadOS 指针 hover 高亮、右键(次要点击)上下文菜单(kill/新建/在 cwd 开)。 + +### 不做(本期范围外) + +- **多窗口 / 多场景(`UISceneConfiguration` 多实例、拖拽会话到新窗口)**:iPad 能开两个终端并排是诱人的,但涉及场景生命周期重构 + 每场景独立 SessionEngine,单列一期(见 §7 后续)。本期 = 单场景自适应分栏。 +- **Stage Manager 外接显示器专属布局**:本期只保证 Stage Manager 下尺寸变化不崩、布局自适应;不做外接屏专属多窗排布。 +- **Apple Pencil、拖放文件进终端、Mac Catalyst**:非目标(同 iPhone 计划 §0)。 +- **纯逻辑包改动**:`WireProtocol/SessionCore/HostRegistry/APIClient` 与设备无关,**一行不改**(若发现某常量隐含 iPhone 假设 → 回对应包 owner,不在本计划直改)。 +- **服务器 / `public/` / `src/`**:零触点(iPad 只是又一个说同一协议的客户端)。 + +### 关键约束:iPhone 零回归(硬性) + +**任何自适应改动必须先满足 compact 宽度 == 现有 iPhone 行为**。每个任务的验收都含一条「iPhone 模拟器全套件仍绿 + 目视无变化」。self-check:compact size class 下走的代码路径应与改动前逐帧一致(分栏只在 regular 宽度激活)。 + +--- + +## 1. 整体架构 / 适配策略 + +``` + size class 驱动的单一根视图(AdaptiveRootView) + ┌─────────────────────────────┴─────────────────────────────┐ + horizontalSizeClass == .compact horizontalSizeClass == .regular + (iPhone / iPad Slide Over / 小分屏) (iPad 全屏 / 大分屏 / Stage Manager 大窗) + │ │ + 现有 NavigationStack NavigationSplitView + 列表 → push 终端(字节级不变) ├ sidebar: 会话列表 + Projects section + └ detail : TerminalContainerView(gate/digest 叠层) + └───────────────── 共享同一 AppCoordinator / 同一 SessionEngine ─────────────────┘ + 隐私遮罩(ZStack 顶层,!= .active)· scenePhase · deep link 全部设备无关,原样复用 +``` + +**为什么自适应而非 iPad 分叉**:`horizontalSizeClass` 是同一 App 内运行时可变量(iPad 拉出 Slide Over 立刻从 regular 变 compact)——分叉两套 UI 无法应对同一次运行内的尺寸切换,且双倍维护。自适应 = 一套代码、一处 size-class 分支、compact 分支就是现有已测代码。 + +**为什么 detail 复用 TerminalContainerView 不动**:终端 + gate + digest 的组装(T-iOS-15/24 的 `TerminalContainerView`)与它挂在 push destination 还是 split detail 无关——只换外层容器,内容零改。SessionEngine 单活 WS、latest-writer-wins、隐私遮罩、生命周期全部设备无关,直接复用。 + +**列宽收益(自动,非新逻辑)**:终端列数由 SwiftTerm 的 `sizeChanged` 从其视图宽度 + 字号算出(T-iOS-11 已实现),detail 面板越宽 → cols 越多 → 越接近桌面宽度。**无新代码**,是分栏的自然结果。 + +**依赖方向**:iPad 适配全部落在 **App 胶水层**(`ios/App/WebTerm/**`);纯包不动;自适应「决策」抽成纯函数(`LayoutMode` 之于 size class,仿 `PrivacyShadePolicy` 先例)以便单测,SwiftUI 布局本身靠模拟器目视 + XCUITest。 + +--- + +## 2. 目录结构(新增/改动) + +``` +ios/App/WebTerm/ +├── Wiring/ +│ ├── RootView.swift # 改:抽 compact 分支为子视图,加 regular 分支入口(T-iPad-2) +│ ├── AdaptiveRootView.swift # ★新:size-class 分支 + LayoutMode 决策消费(T-iPad-2) +│ ├── SplitRootView.swift # ★新:NavigationSplitView(sidebar+detail)(T-iPad-2) +│ ├── LayoutMode.swift # ★新:纯函数 size class → LayoutMode(.stack/.split)(T-iPad-2) +│ └── AppCoordinator.swift # 改:sidebar 选中态 ↔ 现有 terminal/projects 路由桥(最小增量) +├── Screens/ +│ ├── SessionListScreen.swift # 改:sidebar 语境下的选中高亮 + Projects section(自适应,compact 不变) +│ └── ProjectsScreen.swift # 改:regular 宽度下作为 sidebar section / 多列网格(compact 仍是 sheet) +├── Components/ +│ ├── KeyBar.swift # 改:硬件键盘在场时可隐(T-iPad-3) +│ └── TerminalContextMenu.swift # ★新:指针右键/长按上下文菜单(kill/新建/cwd 开)(T-iPad-3) +├── project.yml # 改:TARGETED_DEVICE_FAMILY "1,2" + iPad 方向/plist(T-iPad-1) +└── WebTermTests/ , WebTermUITests/ # 各任务的测试 +``` + +`ios/Packages/**` 零改动。 + +--- + +## 3. 契约(自适应决策的可测核) + +```swift +// LayoutMode.swift — 纯函数,唯一 size-class 决策点(仿 PrivacyShadePolicy) +public enum LayoutMode: Equatable { case stack, split } + +public enum LayoutPolicy { + /// regular 宽度 → split(sidebar+detail);compact → stack(现有 iPhone 路径)。 + /// 唯一判据,禁止在视图里散落 sizeClass 分支。 + public static func mode(horizontalSizeClass: UserInterfaceSizeClass?) -> LayoutMode +} + +// SidebarSelection — split 模式下 sidebar 选中态(与现有 AppCoordinator 路由等价映射) +enum SidebarItem: Hashable { case session(UUID), newSession, projects } +``` + +隐私遮罩、scenePhase、deep link、SessionEngine 契约**沿用 iPhone 计划 §3,不重定义**。 + +--- + +## 4. 工程标准 + +沿用 [PLAN_IOS_CLIENT.md §4](./PLAN_IOS_CLIENT.md) 全部(TDD 强制、不可变、文件 ≤400 行、函数 <50 行、早返回、无魔法数字、系统边界验证、显式错误、conventional commits、覆盖率仅计 4 个纯包)。追加两条 iPad 专属: + +- **size-class 分支只出现在一处**(`LayoutPolicy`);视图内严禁散落 `if sizeClass == …`。 +- **iPhone 零回归是每个任务的验收前置**(compact 路径 == 改动前)。 + +--- + +## 5. 任务清单 + +> 状态图例 `[ ]`/`[~]`/`[x]`/`[!]` 同 iPhone 计划。ID 稳定:`T-iPad-N`。测试框架 Swift Testing;XCUITest 仅 UI happy path。 + +### W0 · 可安装性(串行,先行) + +#### T-iPad-1 · device family + iPad plist/方向 `[ ]` · ~0.5 pd +- **Owns**: `ios/project.yml`(device family、iPad 方向、必要 plist)、`.github/workflows/ios.yml`(加 iPad 模拟器测试腿) +- **Depends**: 无 +- **Steps(测试先行)**: + - [ ] `TARGETED_DEVICE_FAMILY` 由 `"1"` 改 `"1,2"`(project + 各 target level——注意 XcodeGen target 默认覆盖,见 iPhone 计划 W5 finding,逐 target 显式设) + - [ ] iPad 方向键全开(`UISupportedInterfaceOrientations~ipad` 含 Portrait/Landscape/UpsideDown);iPhone 方向键**不动** + - [ ] ATS 五段 CIDR / usage description 原样(设备无关,复核仍在) + - [ ] `xcodegen generate` → 拆构建产物 plist 断言 `UIDeviceFamily [1,2]` + - [ ] ios.yml 加一条 `xcodebuild test` 腿跑 iPad 模拟器(iPad Pro 11" 或 iPad (A16)) +- **Accept**: iPad 模拟器 `xcodebuild build` 通过、空跑现有全套件绿(此刻仍是 iPhone 布局放大,不崩);产物 plist `[1,2]` +- **安全注**: device family 放开后 T-iPad-5 的 ipa 核对须覆盖 iPad 产物(capability↔usage 一一对应不变) + +### W1 · 自适应导航壳(核心) + +#### T-iPad-2 · AdaptiveRootView + NavigationSplitView + LayoutPolicy `[ ]` · ~2 pd +- **Owns**: `Wiring/{AdaptiveRootView,SplitRootView,LayoutMode}.swift`(新)、`Wiring/RootView.swift`(改:现有 stack 抽成 `StackRootView` 子视图供 compact 复用)、`Wiring/AppCoordinator.swift`(改:sidebar 选中 ↔ 路由最小桥)、对应测试 +- **Depends**: T-iPad-1 +- **Steps(测试先行, RED)** — `LayoutPolicyTests` + `SidebarSelectionTests`: + - [ ] `LayoutPolicy.mode(.compact) == .stack`、`.mode(.regular) == .split`、`.mode(nil) == .stack`(未知按最保守 = 现有路径) + - [ ] SidebarItem ↔ AppCoordinator 路由等价:选 `.session(id)` == 现有 open(id);`.newSession` == open(nil);`.projects` == 现有 projects 呈现——**同一 coordinator API,split 只是另一个触发面**(断言不新增会话生命周期路径) + - [ ] compact 分支渲染的视图树 == 改动前 `RootView`(快照/结构断言:隐私遮罩仍 ZStack 顶层、scenePhase/deepLink/sheet 全在) + - [ ] size class 从 regular→compact 切换(iPad 拉 Slide Over)时选中态保持、终端不重建(复用 T-iOS-29 的 `.id(controller.id)` 稳定性,断言 controller 不换) +- **Steps(实现, GREEN)**: + - [ ] `AdaptiveRootView` 读 `@Environment(\.horizontalSizeClass)` → `LayoutPolicy.mode` → 选 `StackRootView`(现有)或 `SplitRootView` + - [ ] `SplitRootView` = `NavigationSplitView { sidebar } detail { TerminalContainerView 或空态 }`;sidebar = SessionListScreen(选中绑定)+ Projects section 入口;detail 空 = 「选择或新建会话」占位 + - [ ] 隐私遮罩/scenePhase/deepLink/task(bootstrap) 上提到 `AdaptiveRootView`(两分支共享,绝不重复布线) +- **Accept**: iPad 模拟器横竖屏 + Split View 拉入拉出全程分栏/退栈自适应无崩;**iPhone 模拟器全套件绿 + 目视逐屏无变化**;`LayoutPolicy` 覆盖率 100% +- **安全注**: 隐私遮罩必须仍是**两分支共同的** ZStack 顶层——split detail 的终端字节同样要被 `!= .active` 遮住(新增一条 iPad 遮罩目视验收:Stage Manager 切走 → detail 终端被遮) + +### W2 · 逐面适配(并行,文件互斥) + +#### T-iPad-3 · 终端面板:KeyBar 自适应 + 指针上下文菜单 `[ ]` · ~1 pd +- **Owns**: `Components/{KeyBar,TerminalContextMenu}.swift`、`Screens/TerminalScreen.swift`(增量)、测试 +- **Depends**: T-iPad-2 · **Parallel-safe**: T-iPad-4 +- **Steps(测试先行)**: + - [ ] KeyBar 可见性纯谓词:`GCKeyboard.coalesced != nil`(硬件键盘在场)→ 默认隐;无 → 显;用户可手动切(谓词单测,硬件态注入) + - [ ] 上下文菜单(右键/长按)项 = {在 cwd 开新会话、kill、复制选区}——动作复用现有 OpenRequest/killSession 通道(断言不新增网络路径;kill 仍带 Origin) + - [ ] 指针 hover 高亮不改字节流(纯 UI) +- **Accept**: iPad 接键盘时 KeyBar 自动隐、去键盘复现;右键菜单在 iPad 生效;iPhone 无硬件键盘时 KeyBar 行为不变 +- **安全注**: 上下文菜单的 kill/开会话是既有 G/RO 通道的又一触发面——测试名标「复用 APIClient,无绕过」 + +#### T-iPad-4 · Projects/Timeline/sheet 大屏化 `[ ]` · ~1 pd +- **Owns**: `Screens/ProjectsScreen.swift`(增量)、Projects/Timeline 呈现方式(regular 下 `.sheet` → 列/`.presentationDetents` 或 sidebar section)、测试 +- **Depends**: T-iPad-2 · **Parallel-safe**: T-iPad-3 +- **Steps(测试先行)**: + - [ ] Projects 网格列数随宽度自适应纯函数(compact=1 列现状、regular=多列)——列数决策单测 + - [ ] regular 宽度下 Projects 作为 sidebar section 或 detail 列(compact 仍走现有 sheet,分组/收藏/prefs 往返逻辑零改——只换容器) + - [ ] Timeline/其它 sheet 在 iPad 用合适 detent/尺寸不占满全屏 +- **Accept**: iPad 上 Projects 多列、Timeline 合理尺寸;iPhone 全部走原有 sheet 路径不变;prefs 未知键往返(T-iOS-38 不变量)回归绿 + +### W3 · 验收(report-only, G4) + +#### T-iPad-5 · iPad 验收 + iPhone 回归 + 安全核对 `[ ]` · ~0.5 pd +- **Depends**: T-iPad-2/3/4 · **Owns**: 无源码(report-only,findings 派回 owner) +- **Steps**: + - [ ] **F-iPad 走查**(§6 清单)逐条:分栏、横竖屏、Split View/Slide Over/Stage Manager 尺寸切换、硬件键盘/指针、终端更宽列数、遮罩覆盖 detail + - [ ] **iPhone 零回归**:iPhone 16 全套件绿 + 逐屏目视对比无变化(compact 路径未动) + - [ ] **安全**:拆 iPad 产物 ipa 核对(UIDeviceFamily [1,2]、五段 CIDR、usage 一一对应);分栏后隐私遮罩仍遮 detail 终端;上下文菜单/键盘触发的 G 调用仍走 APIClient 带 Origin + - [ ] 真机项(真 iPad 手势/键盘/指针目视、Stage Manager 实机)→ DEFERRED,附手工清单 + +--- + +## 6. 测试与验收 + +### 设备矩阵 + +| 层 | 环境 | +|---|---| +| 纯逻辑(LayoutPolicy/谓词) | macOS `swift test` / 宿主 WebTermTests | +| App 自适应 | iPhone 16(compact 回归)+ iPad Pro 11"(regular)+ **iPad Split View(同一次运行内 regular↔compact 切换)** | +| XCUITest | iPad happy path 1 条:sidebar 选会话 → detail attach → 输入 → gate approve(复用 iPhone happy path 骨架,换分栏断言) | +| 真机必测 | 真 iPad 分栏手势、硬件键盘全键位、指针 hover/右键、Stage Manager(1 台,DEFERRED 至有设备) | + +### 验收演示脚本(F-style) + +- **F-iPad-1** iPad 全屏 → 左 sidebar 会话列表 + 右 detail 终端同屏;选另一会话 → detail 即时切换(回放恢复)。 +- **F-iPad-2** 横竖屏旋转 → 布局自适应、终端 resize 重绘、无错位。 +- **F-iPad-3** 拉出 Slide Over(compact)→ 自动退化为 stack(== iPhone 布局);推回全屏 → 恢复分栏;全程会话不断、终端不重建。 +- **F-iPad-4** iPad 接硬件键盘 → KeyBar 自动隐、`UIKeyCommand` 全键位可用;拔键盘 → KeyBar 回来。 +- **F-iPad-5** 指针右键会话行/终端 → 上下文菜单(kill/新建/cwd 开)生效。 +- **F-iPad-6** iPad 全屏终端列数明显多于 iPhone(接近桌面),宽 TUI 折行大幅缓解。 +- **F-iPad-7** Stage Manager/切后台 → detail 终端被隐私遮罩覆盖(快照不泄露)。 +- **F-iPad-8**(回归)iPhone 16 逐屏与适配前目视一致、全套件绿。 + +--- + +## 7. 风险与开放问题 + +| 风险 / 问题 | 影响 | 缓解 / 待定 | +|---|---|---| +| `NavigationSplitView` 在 size-class 频繁切换(Slide Over 反复拉)时状态/选中丢失 | 中 | 选中态提升到 AppCoordinator(设备无关单一真值),T-iPad-2 专项测切换保持;终端 `.id(controller.id)` 稳定性已在 P1 验证 | +| 隐私遮罩在 split detail 漏遮 | 高(安全) | 遮罩上提到 AdaptiveRootView 两分支共享 ZStack 顶层;T-iPad-2/5 双重目视 | +| iPhone 回归(compact 分支被无意改动) | 高 | 每任务验收前置 iPhone 全绿 + 目视;compact 分支复用现有 StackRootView 原样 | +| SwiftTerm 在超宽 detail 的性能/选择手感 | 低 | 复用 iPhone 已测路径;真机目视 | +| 多窗口诱惑导致 scope 膨胀 | 中 | 本期明确单场景(§0 非目标);多窗口另立计划 | + +**待你拍板**: +1. iPad 最低系统版本:iPadOS 17(与 iPhone 一致)还是抬到 18/26? +2. 本期是否要指针右键上下文菜单(T-iPad-3 后半)——纯锦上添花,可砍到后续。 +3. 多窗口(拖会话开新窗并排两终端)确认放到下一期? + +**工作量合计**:W0 0.5 + W1 2 + W2(3∥4)2 + W3 0.5 ≈ **5 人日**(并行后墙钟更短)。 + +--- + +## 8. 与现有文档的关系 + +- 本文只改 `ios/App/WebTerm/**` + `project.yml`/`ios.yml`;`ios/Packages/**`、`src/`、`public/` 零改动。 +- 是 [PLAN_IOS_CLIENT.md](./PLAN_IOS_CLIENT.md) 的**布局适配层**,复用其 §3 契约/§4 标准/§5 安全/§6 并行规则;把该计划 §0 非目标里的「iPad 优化布局」提取为本期。 +- 多设备共享 PTY 的尺寸张力([PROGRESS_LOG](./PROGRESS_LOG.md) 记录的「宽桌面+窄手机折行」):iPad 因自身是宽屏 writer 而**天然缓解**,非本计划新机制。 +- 冲突裁决:iPhone 计划管协议/会话/安全模型;本文只加自适应布局,不改之。 diff --git a/ios/project.yml b/ios/project.yml index feaadb4..e0a5c53 100644 --- a/ios/project.yml +++ b/ios/project.yml @@ -19,7 +19,7 @@ settings: SWIFT_VERSION: "6.0" # Swift 6 language mode SWIFT_STRICT_CONCURRENCY: complete IPHONEOS_DEPLOYMENT_TARGET: "17.0" - TARGETED_DEVICE_FAMILY: "1" # iPhone only (iPad layout is out of v1 scope) + TARGETED_DEVICE_FAMILY: "1,2" # iPhone + iPad (T-iPad-1; adaptive layout) CODE_SIGN_STYLE: Automatic packages: @@ -55,9 +55,10 @@ targets: base: PRODUCT_BUNDLE_IDENTIFIER: com.yaojia.webterm # Target-level on purpose (T-iOS-19 finding): XcodeGen's target platform - # default ("1,2") overrides a project-level value, shipping an - # installable-but-untested iPad layout. Target-level wins. - TARGETED_DEVICE_FAMILY: "1" + # default overrides a project-level value. iPad adaptation (T-iPad-1) + # opens this to iPhone + iPad; the adaptive layout (LayoutPolicy / + # NavigationSplitView, T-iPad-2) makes the iPad layout first-class. + TARGETED_DEVICE_FAMILY: "1,2" info: path: App/WebTerm/Resources/Info.plist properties: @@ -69,10 +70,18 @@ targets: CFBundleURLTypes: - CFBundleURLName: com.yaojia.webterm.deeplink CFBundleURLSchemes: [webterminal] + # iPhone: portrait + both landscapes (no upside-down — matches P0). UISupportedInterfaceOrientations: - UIInterfaceOrientationPortrait - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight + # iPad (T-iPad-1): all four orientations — iPad users hold it any way, + # and Split View / Stage Manager assume full orientation freedom. + UISupportedInterfaceOrientations~ipad: + - UIInterfaceOrientationPortrait + - UIInterfaceOrientationPortraitUpsideDown + - UIInterfaceOrientationLandscapeLeft + - UIInterfaceOrientationLandscapeRight # ── Security-critical keys, transcribed verbatim from PLAN_IOS_CLIENT §5.2 ── # NO NSAllowsArbitraryLoads anywhere (release OR debug): the five CIDR # exceptions below cover LAN + hotspot + Tailscale CGNAT + simulator @@ -114,7 +123,7 @@ targets: settings: base: GENERATE_INFOPLIST_FILE: true - TARGETED_DEVICE_FAMILY: "1" + TARGETED_DEVICE_FAMILY: "1,2" TEST_HOST: "$(BUILT_PRODUCTS_DIR)/WebTerm.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/WebTerm" BUNDLE_LOADER: "$(TEST_HOST)" @@ -131,7 +140,7 @@ targets: settings: base: GENERATE_INFOPLIST_FILE: true - TARGETED_DEVICE_FAMILY: "1" + TARGETED_DEVICE_FAMILY: "1,2" TEST_TARGET_NAME: WebTerm schemes: