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:
@@ -19,6 +19,11 @@ struct ReconnectBanner: View {
|
||||
}
|
||||
|
||||
let model: Model
|
||||
/// T-iOS-29 · "开新会话" on the EXITED banner only (session over → the
|
||||
/// natural next step; manager.ts:145-153 — the old session is done for
|
||||
/// good). nil = affordance hidden. Not offered on `.failed`: that copy
|
||||
/// asks the user to change a knob first, not to spawn again.
|
||||
var onNewSession: (@MainActor () -> Void)? = nil
|
||||
|
||||
private enum Metrics {
|
||||
static let contentSpacing: CGFloat = 8
|
||||
@@ -27,6 +32,10 @@ struct ReconnectBanner: View {
|
||||
static let backgroundOpacity = 0.9
|
||||
}
|
||||
|
||||
private enum Copy {
|
||||
static let newSession = "开新会话"
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
HStack(spacing: Metrics.contentSpacing) {
|
||||
if isSpinning {
|
||||
@@ -38,6 +47,12 @@ struct ReconnectBanner: View {
|
||||
Text(text)
|
||||
.font(.footnote)
|
||||
.multilineTextAlignment(.leading)
|
||||
if let onNewSession, Self.isNewSessionActionAvailable(for: model) {
|
||||
Button(Copy.newSession) { onNewSession() }
|
||||
.font(.footnote.bold())
|
||||
.buttonStyle(.plain)
|
||||
.accessibilityIdentifier("terminal.exitNewSessionButton")
|
||||
}
|
||||
}
|
||||
.padding(.horizontal, Metrics.horizontalPadding)
|
||||
.padding(.vertical, Metrics.verticalPadding)
|
||||
@@ -46,6 +61,13 @@ struct ReconnectBanner: View {
|
||||
.accessibilityElement(children: .combine)
|
||||
}
|
||||
|
||||
/// The new-session affordance appears on the `.exited` banner only —
|
||||
/// pure predicate so the T-iOS-29 tests pin the rule.
|
||||
static func isNewSessionActionAvailable(for model: Model) -> Bool {
|
||||
if case .exited = model { return true }
|
||||
return false
|
||||
}
|
||||
|
||||
private var isSpinning: Bool {
|
||||
switch model {
|
||||
case .connecting, .reconnecting: return true
|
||||
|
||||
Reference in New Issue
Block a user