feat(ios): P1-B — W7 UI wave: deep links, timeline, quick-reply, diff, projects, session switcher, thumbnails, lock-screen push
T-iOS-22: DeepLinkRouter (full-field whitelist, cold-start stash, route(from:) for push-tap reuse), 21 tests T-iOS-24: TimelineSheet mirroring web render() order; disabled→empty-state; reuses AwayDigest onExpand T-iOS-25: QuickReply chips (built-ins mirror quick-reply.ts via KeyByteMap; visible iff live gate && !readOnly) T-iOS-27: read-only DiffScreen + App-layer DiffFetcher (RO no-Origin; APIClient fold-in noted for T-iOS-38 owner) T-iOS-26: Projects list/detail — grouping byte-identical to web group keys (prefs-shared collapse state), prefs-clobber defenses (no blind PUT on empty base; adopt server echo), claude\r bootstrap T-iOS-23: UnreadLedger + TitleSanitizer (SessionCore, +15 tests), lastOutputAt decode, list-boundary re-sanitize T-iOS-29: new-in-cwd (untrusted cwd, no bootstrap re-injection) + exited-session reopen; fixes stale-controller SwiftTerm view bug via .id(controller.id) T-iOS-28: offscreen SwiftTerm thumbnail pipeline (LRU 32, concurrency gate 2, 256KiB cap, grid clamp) T-iOS-21: PushRegistrar (WEBTERM_GATE category: Allow=.authenticationRequired, no .foreground) + NotificationActionHandler (whitelisted payload, token never persisted, bg-task-wrapped POST, 403 fallback) CRITICAL fix (verify-found boot crash): @Sendable literals on UN completion closures — MainActor-inherited closures trapped Swift 6 executor check on UN's background queue; boot re-verified (no new crash reports, permission prompt reached, privacy shade correctly covering during system alert) Verified: 261 pkg + 247 app + 10 integration tests green; 7/7 semantics checks; Owns audit clean
This commit is contained in:
@@ -107,6 +107,28 @@ struct ModelDecodingTests {
|
||||
#expect(session.rows == 24)
|
||||
}
|
||||
|
||||
@Test("LiveSessionInfo.lastOutputAt 可选字段(T-iOS-37/23):在场→解出;缺席(旧服务器)→nil;坏类型→nil 不丢条目")
|
||||
func lastOutputAtDecodesAsOptional() async throws {
|
||||
// Arrange: 在场 / 缺席 / 坏类型(不可信服务器)三个条目。
|
||||
let present = Self.noTelemetrySessionJSON
|
||||
.replacingOccurrences(of: #""createdAt":1720000000000"#,
|
||||
with: #""createdAt":1720000000000,"lastOutputAt":1720000007000"#)
|
||||
let wrongType = Self.fullSessionJSON
|
||||
.replacingOccurrences(of: #""createdAt":1720000000000"#,
|
||||
with: #""createdAt":1720000000000,"lastOutputAt":"soon""#)
|
||||
|
||||
// Act
|
||||
let sessions = try await fetchSessions(
|
||||
body: "[\(present),\(Self.noTelemetrySessionJSON),\(wrongType)]"
|
||||
)
|
||||
|
||||
// Assert
|
||||
#expect(sessions.count == 3)
|
||||
#expect(sessions[0].lastOutputAt == 1_720_000_007_000)
|
||||
#expect(sessions[1].lastOutputAt == nil)
|
||||
#expect(sessions[2].lastOutputAt == nil)
|
||||
}
|
||||
|
||||
@Test("未知 status 字符串 → .unknown(不丢弃整个会话条目)")
|
||||
func unknownStatusStringMapsToUnknownWithoutDroppingEntry() async throws {
|
||||
// Arrange
|
||||
|
||||
Reference in New Issue
Block a user