eleven / ocho Public

Clone
git clone https://amadan.net/eleven/ocho

Plain git — no account needed to clone.

Download

Download this file

1import SwiftUI
2
3// Tamper-evidence failure banner (docs/tamper-evidence.md, #573): the native
4// twin of the web's #integrity-banner. Shown ONLY when this space fails an
5// integrity check (a fork/rollback/withheld/bad-signature the E2E rule is meant
6// to make un-hideable) — never for "all clear" (a pulled /integrity self-check
7// covers that). An inline band occupying layout, never a floating overlay;
8// amber with a ⚠️ so it's never colour-alone. `onDetails` runs /integrity so
9// the full read-out shows in place.
10public struct IntegrityBanner: View {
11 @ObservedObject var monitor: IntegrityMonitor
12 let onDetails: () -> Void
13
14 public init(monitor: IntegrityMonitor, onDetails: @escaping () -> Void) {
15 self.monitor = monitor
16 self.onDetails = onDetails
17 }
18
19 private var title: String {
20 let n = monitor.unresolved.count
21 return n > 1 ? "Integrity check failed (\(n.formatted()) alerts)" : "Integrity check failed"
22 }
23
24 private var message: String {
25 guard let latest = monitor.unresolved.last else { return "" }
26 return IntegrityMonitor.alertMessage(latest.kind)
27 + " Keep this device — its records are the evidence — and contact your space's admin."
28 }
29
30 public var body: some View {
31 if !monitor.unresolved.isEmpty {
32 HStack(alignment: .top, spacing: 10) {
33 Image(systemName: "exclamationmark.triangle.fill")
34 .foregroundStyle(.white)
35 .accessibilityHidden(true)
36 VStack(alignment: .leading, spacing: 4) {
37 Text(title).font(.subheadline).bold()
38 Text(message).font(.footnote)
39 HStack(spacing: 8) {
40 Button("What's this?") { onDetails() }
41 .buttonStyle(.borderedProminent)
42 Button("Dismiss") { monitor.acknowledge() }
43 .buttonStyle(.bordered)
44 }
45 .controlSize(.small)
46 .padding(.top, 2)
47 }
48 Spacer(minLength: 0)
49 }
50 .foregroundStyle(.white)
51 .tint(.white)
52 .padding(.horizontal, 14)
53 .padding(.vertical, 10)
54 .frame(maxWidth: .infinity, alignment: .leading)
55 .background(Color(red: 0.72, green: 0.42, blue: 0.05)) // amber, holds in light + dark
56 .accessibilityElement(children: .contain)
57 .accessibilityLabel("Integrity check failed. \(message)")
58 }
59 }
60}
61