fix(ios,android): close the acceptance gaps the review found, add Android CI

- T-iOS-34's stated acceptance is "最大字号不破版" and it was failing: the key bar
  froze its height at 52pt while an AX5 keycap needs 108.24pt, so caps clipped —
  recorded as a withKnownIssue rather than fixed. Height now derives from the
  content size category (and tracks live changes via registerForTraitChanges);
  the known-issue marker is gone, replaced by positive assertions including a
  12-category no-clip sweep and a guard that stays red if anyone writes the
  constant back. Honest tradeoff: the keycap font is clamped at .accessibility2,
  the same policy the design system already applies to dense content, because an
  unclamped AX5 bar would eat the terminal. A test pins the clamp so the two
  cannot drift.

- Thumbnails silently 401'd on a token-gated host: the pipeline built its own
  transport with no token source, and by design never throws, so every preview
  degraded to a placeholder with no signal. Assembled from AppEnvironment now.

- Android had zero CI while the token wave shipped 24 files of secret-handling
  code. The instrumented leg is workflow_dispatch-only and says why in the file:
  no one has ever seen it green on a runner, and a required leg nobody trusts
  just produces a false green.

- Android persisted a validated token before the host probe succeeded, stranding
  a secret for a host that never paired.

App bundle 534 -> 550 on both simulators, zero known issues. Android 687 -> 691.
This commit is contained in:
Yaojia Wang
2026-07-30 16:46:20 +02:00
parent 284cfd193a
commit 5cc755b0b6
14 changed files with 1120 additions and 102 deletions

View File

@@ -540,10 +540,10 @@ W5 验收(report-only, 并行)
| T-iOS-13 SessionList | `[x]` | `Screens/SessionListScreen.swift``Components/TelemetryChips.swift``ViewModels/SessionListViewModel.swift` + Tests |
| T-iOS-14 Gate/Digest UI | `[x]` | `Components/{GateBanner,PlanGateSheet,AwayDigestView}.swift``ViewModels/GateViewModel.swift` + `GateViewModelTests` |
| T-iOS-15 App 接线+生命周期 | `[x]` | `Wiring/{RootView,AppCoordinator,PrivacyShade,ColdStartPolicy,TerminalContainerView}.swift` + `PrivacyShadeTests`/`ColdStartPolicyTests` |
| T-iOS-16 集成 CI | `[x]` | `IntegrationTests/**` **10** `@Test` + `scripts/coverage-gate.sh` + `ios.yml` 六个 job含 iPad 单测腿/iPad UI 腿/iOS-17 底线腿)。**未核实**GH Actions 平台侧的运行结果(本环境 `gh` 未登录) |
| T-iOS-16 集成 CI | `[x]` | `IntegrationTests/**` **26** `@Test`10 基线 + 8 令牌端到端 + 8 令牌策略漂移守卫)+ `scripts/coverage-gate.sh`(现门 **5** 个包,含 ClientTLS+ `ios.yml` 六个 job含 iPad 单测腿/iPad UI 腿/iOS-17 底线腿)+ 新增 `android.yml``ServerHarness.locateTsx` 改为逐级向上解析,故 worktree 内无 `node_modules` 也能自举。**未核实**GH Actions 平台侧的运行结果——本仓库唯一 remote 是自建 Gitea两个 workflow **从未在任何 runner 上跑过**;本地已逐条复跑其命令 |
| T-iOS-17 ntfy 桥验证+文档 | `[x]` | `ios/README.md` ntfy 章节(逐条引 `setup-hooks.mjs` 行号,只读验证,未动用户 hook 配置)。**DEFERRED**:手机端到端 |
| T-iOS-18 F 走查(真机) | `[~]` | 机器可执行项已执行并指认测试名P0 收官条目)。**缺口**F-iOS 的真机项QR 扫码/IME/震动/切换器遮罩目检/ntfy 端到端)仍 DEFERRED手工清单在 LOG |
| T-iOS-19 安全核对 | `[~]` | P0 面已逐条核Origin 单点、G/RO 分界、五段 CIDR + 零 ArbitraryLoads、Keychain 属性)。**缺口**P2 新增的 `NSMicrophoneUsageDescription`/`NSSpeechRecognitionUsageDescription` 尚未在**产物层**复核;release ipa 层核对仍未做(免费 team 无分发通道) |
| T-iOS-19 安全核对 | `[~]` | P0 面已逐条核Origin 单点、G/RO 分界、五段 CIDR + 零 ArbitraryLoads、Keychain 属性。P2 新增的 `NSMicrophoneUsageDescription`/`NSSpeechRecognitionUsageDescription` **已在产物层复核**(真机构建产物的 Info.plist 实测有这两键)。**剩余缺口**release ipa 层核对仍未做(免费 team 无分发通道) |
| T-iOS-20 server APNs | `[x]` | `src/push/apns.ts` + `test/push-apns.test.ts`(含本地 h2c 假 APNs 的双 e2e具体测试数以 `npm test` 输出为准LOG 记 65。**DEFERRED**:真机端到端需付费账号 |
| T-iOS-37 server lastOutputAt | `[x]` | `src/types.ts:315` 可选字段 + `src/session/manager.ts:197` 映射 + 测试 |
| T-iOS-38 APIClient P1 契约 | `[x]` | `APIClient/{ApnsToken,Projects,Prefs}.swift` + `ApnsTokenTests`/`ProjectsTests`/`PrefsRoundTripTests` |
@@ -560,9 +560,9 @@ W5 验收(report-only, 并行)
| T-iOS-31 语音 PTT | `[x]` | `Components/{VoicePTT,VoicePTTBanner,SpeechDictation}.swift` + `KeyBar` 🎤 键 + `VoicePTTTests` **40** `@Test`。**DEFERRED**:真机口述→确认→注入 |
| T-iOS-32 Worktree + `--resume` 历史 | `[~]` | `Screens/{WorktreeSheet,ResumeHistorySheet}.swift``ViewModels/{WorktreeViewModel,ResumeHistoryViewModel}.swift` + `WorktreeViewModelTests`(22)/`ResumeHistoryViewModelTests`(12)/`ProjectResumeLaunchTests`(7)。**缺口**Accept 的"端到端一次"(对真服务器真建/真删 worktree无自动化腿、本环境也未手工跑 |
| T-iOS-33 终端内搜索 | `[x]` | `Components/TerminalSearchBar.swift` + `TerminalScreen` 绑定 SwiftTerm 搜索 API + `TerminalSearchTests` **18**(含 2 条真 view 高亮断言) |
| T-iOS-34 主题 + Dynamic Type | `[~]` | `DesignSystem/{AppTheme,TerminalPalette}.swift``Screens/SettingsScreen.swift``Tokens/Typography` 浅色档 + `AppThemeTests`(24)/`DynamicTypeLayoutTests`(8)。**缺口(已实测、未修)**AX5 下键栏需 ~108.24pt 而 `KeyBarMetrics.barHeight` 固定 52pt → 键帽裁切,以 `withKnownIssue` 钉住(`KeyBar.swift` 不在该任务 Owns |
| T-iOS-34 主题 + Dynamic Type | `[x]` | `DesignSystem/{AppTheme,TerminalPalette}.swift``Screens/SettingsScreen.swift``Tokens/Typography` 浅色档 + `AppThemeTests`(24)/`DynamicTypeLayoutTests`(**14**)/`KeyBarTests`(**12**)。**缺口已闭合**(收尾波):`KeyBarMetrics.barHeight` 由常量 52pt 改为按字号档推导,`withKnownIssue` 已删、换成正向断言AX5 不裁切 · 全 12 档不裁切且 ≥44pt · XSXL 逐点仍 52pt 零回归 · 未封顶 AX5 溢出旧固定高的护栏)。**取舍**:键帽字体封顶在 `DS.Typography.numericClamp`(.accessibility2),故 AX3AX5 的字号不再增长(不封顶时 AX5 要 108.24pt,会吃掉终端);该封顶由测试钉死不得漂移 |
| T-iOS-35 web `?join=` 互通 | `[x]` | `DeepLinkRouter.swift``.joinShared` 分支 + `DeepLinkJoinTests` **19**(含改写后的既有拒绝用例) |
| T-iOS-36 P2 验收 | `[~]` | 本波 Wave D 的验收 agent 正在执行;**结果与真实数字以 `PROGRESS_LOG.md` 的该条目为准**,本文不预写结论 |
| T-iOS-36 P2 验收 | `[x]` | 两轮独立复验Wave D + 收尾波)均已跑完,**真实数字以 `PROGRESS_LOG.md` 的该条目为准**452 包测 / 550 App 测iPhone 16 Pro 与 iPad Pro 11" M4 各一遍,**0 known issue**/ 26 集成测 / 覆盖率门 5/5 / 真机构建 `BUILD SUCCEEDED` 且产物无 `aps-environment`。P2 五项逐条走查见本节上方各行 |
**说明**:上表的"测试数"是 `@Test` **声明**静态计数(`grep -rh '@Test'`),与 commit `850531f`/`a5fa843` 里实跑数字一致;
参数化用例实跑会更多。App bundle 侧共 **532**`@Test` 声明。
@@ -588,10 +588,15 @@ W5 验收(report-only, 并行)
另补 iPad UI-test 腿(同 commit
- **签名解锁**`DEVELOPMENT_TEAM` + target 级 `CODE_SIGN_IDENTITY` + `WEBTERM_PUSH_ENTITLEMENTS` env 开关
commit `c4f8b5b`;真机构建在免费 team 上 `BUILD SUCCEEDED`7 天临时 profile
- **已知未闭合缺口(供后续任务领走)**:① AX5 键栏裁切(见 T-iOS-34 行);**无端到端令牌腿**——
`IntegrationTests` 与模拟器内 live-server smoke 都不带 `WEBTERM_TOKEN` 起服务器grep 无 `WEBTERM_TOKEN`/`webterm_auth`
令牌路径目前只有单元级覆盖;③ T-iOS-19 的产物层复核未覆盖 P2 新增的两条 usage description
④ worktree 建/删的**端到端**一次T-iOS-32 Accept既无自动化腿也未手工跑。
- **收尾波2026-07-30闭合了其中三条**:① AX5 键栏裁切 → 已修(见 T-iOS-34 行);
② 端到端令牌腿 → `IntegrationTests/AccessTokenGateTests.swift` **8 例**对真服务器(带 `WEBTERM_TOKEN` 自举)
跑通:对令牌放行 / 错令牌 401 终态且 **connect 计数 == 1**(另有可重试失败的对照组证明"零重试"不是计时假象)/
令牌不替代 Origin合法 cookie + 外域 Origin 仍拒)/ `POST /auth` 的 204+Set-Cookie、401、204-无-Set-Cookie 三态;
另加 `TokenPolicyDriftTests.swift` **8 例**漂移守卫(运行期读 `src/config.ts``src/http/auth.ts` 的真字面量,
与三份 Swift 谓词逐条比对,含变异测试证明守卫自身够响);③ 两条 usage description 已在产物层复核(见 T-iOS-19 行)。
- **剩余未闭合**release ipa 层核对(免费 team 无分发通道);真机人工腿(口述 / 扫码 / 锁屏 Face ID / IME
Android instrumented 腿(`TinkAccessTokenStoreTest` 是令牌静态加密的唯一证明需真机或模拟器CI 上暂设为
`workflow_dispatch` 触发——理由写在 `android.yml` 文件头:没人见它绿过的腿若设为必过,只会逼出一个假绿)。
### P0 — 每日可用("口袋里能开终端、能批准")· 合计 ~13 人天
@@ -920,7 +925,7 @@ W5 验收(report-only, 并行)
- **T-iOS-33** · 终端内搜索 `[x]` ~1 pd。**Wave**: W9 · **Owns**: `App/WebTerm/Components/TerminalSearchBar.swift` + Tests · **Depends**: T-iOS-11 · **Accept**: SwiftTerm search API 命中高亮
- **T-iOS-34** · 主题 + Dynamic Type `[~]` ~1.5 pd。**Wave**: W9 · **Owns**: 主题/字号小增量与同波任务文件不相交开工时列明文件清单)· **Depends**: T-iOS-11/13 · **Accept**: 亮暗主题 + 最大字号不破版
- **落地**: `DesignSystem/{AppTheme,TerminalPalette}.swift` + `Screens/SettingsScreen.swift`齿轮入口在 `ProjectsToolbarItem` stack split 两根视图共享+ Tokens/Typography 浅色档`AppThemeTests`(24)/`DynamicTypeLayoutTests`(8)。默认仍是深色零回归)。
- **缺口已实测未修**: AX5 下键栏两行需 **108.24pt**`KeyBarMetrics.barHeight` 固定 **52pt**44+8)→ 键帽裁切 `withKnownIssue` 钉在 `DynamicTypeLayoutTests`修好后该用例会报 "known issue was not recorded"提醒删标记)。`Components/KeyBar.swift` 不在该任务 Owns需另派
- **缺口已闭合收尾波2026-07-30**: `KeyBarMetrics.barHeight` 从常量 52pt 改为 `max(minHitTarget, keycapHeight(category)) + sm8``intrinsicContentSize`/init frame/`apply(contentSizeCategory:)` 三处同源并经 iOS 17 `registerForTraitChanges` 支持运行期改档`withKnownIssue` 已删。**取舍**键帽字体封顶 `.accessibility2`不封顶时 AX5 108.24pt会把终端吃掉沿用 DS 对密集内容的既有策略实测条高 XSXL 52 / XXL 55 / XXXL 59 / AccM 68 / AccLAX5 77
- **T-iOS-35** · web `?join=` 互通分享 QR 双向`[x]` ~0.5 pd。**Wave**: W9 · **Owns**: `DeepLinkRouter.swift` 增量`?join=` 解析)· **Depends**: T-iOS-22 · **Accept**: 手机扫 web 分享 QR 直达同会话
- **落地**: `DeepLinkRouter` `.joinShared` 分支 + `DeepLinkJoinTests` 19 含把原"scheme 不是 webterminal"的拒绝理由改写为"web 形状只许单一 `join` "主机身份只经 `HostStore`+`HostEndpoint.originHeader` 解析未配对 origin 一律走配对页且 hint 不回显链接内容
- **DEFERRED**: 用真手机相机扫 web 分享 QR 的目视走查模拟器无相机)。