import SwiftUI /// T-iOS-15 · Privacy shade (security-critical, plan §7 T-iOS-15). /// /// iOS writes the app-switcher snapshot to DISK the moment the scene reaches /// `.background` — but the switcher is usually ENTERED at `.inactive`. The /// shade therefore covers whenever `scenePhase != .active` (the exact rule; /// `.inactive`-only would let the `.background` snapshot capture terminal /// content — API keys, tokens, source — into the on-disk switcher image). /// Restored on `.active`. /// /// Pure mapping split from the view so the rule is unit-testable for all /// three phases (PrivacyShadeTests). enum PrivacyShadePolicy { static func isShadeVisible(for scenePhase: ScenePhase) -> Bool { scenePhase != .active } } /// Opaque cover rendered ABOVE the whole navigation tree (RootView ZStack) — /// deliberately no animation: the snapshot moment must never race a fade. /// /// Security invariant (do NOT weaken): the FULL-SCREEN base is `DS.Palette.surface` /// (`.systemBackground`, fully OPAQUE) + `.ignoresSafeArea()`, so no terminal /// byte can leak into the on-disk switcher snapshot. The branded lockup is a /// `.regularMaterial` card layered ON TOP of that opaque base (never over the /// terminal), so the material's translucency is purely decorative — it blurs /// the opaque surface below it, not the PTY content. Accent lock + app name + /// hint give the shade a refined-native identity instead of a flat cover. /// /// Scope note (documented): sheets present in a separate presentation layer a /// root overlay cannot cover — but no terminal bytes are ever rendered in a /// sheet (pairing / plan-gate only), so the root-level shade covers every /// surface that shows PTY content. struct PrivacyShadeView: View { var body: some View { ZStack { // OPAQUE full-screen occlusion — the security base. Must stay opaque // and edge-to-edge; the material card below only decorates it. DS.Palette.surface .ignoresSafeArea() VStack(spacing: DS.Space.md12) { Image(systemName: "lock.fill") .font(DS.Typography.largeTitle) .foregroundStyle(DS.Palette.accent) Text(ShadeCopy.appName) .font(DS.Typography.headline) .foregroundStyle(DS.Palette.textPrimary) Text(ShadeCopy.hint) .font(DS.Typography.caption) .foregroundStyle(DS.Palette.textSecondary) } .padding(.horizontal, DS.Space.xxl24) .padding(.vertical, DS.Space.xl20) .background(.regularMaterial, in: RoundedRectangle(cornerRadius: DS.Radius.lg16)) .overlay( RoundedRectangle(cornerRadius: DS.Radius.lg16) .strokeBorder(DS.Palette.hairline, lineWidth: DS.Stroke.hairline) ) } .accessibilityHidden(true) } } /// 遮罩内用户可见文案(Chinese, named constants)。 private enum ShadeCopy { static let appName = "WebTerm" static let hint = "内容已隐藏" }