ζ-0 Setup: Repo-Skelett, iOS-Build grün, Healthz live
- project.yml mit Bundle ev.mana.zitare + Widget + ShareExt-Targets - ManaSwiftCore (ManaCore + ManaTokens) + ManaSwiftUI (ManaAuthUI) als Package-Dependencies via path: - Pure SwiftUI für Native-Surfaces, WKWebView nur für Lese-Tabs (Hybrid-Sonderfall vs cards/memoro/manaspur, dokumentiert im Playbook ZITARE_NATIVE_GREENFIELD.md) - Theme: paper-Variant aus @mana/themes - ZitareAPI.healthCheck via direct URLSession (öffentlicher Endpoint, kein AuthenticatedTransport-Gate) - 6/6 AppConfigTests + 1/1 UI-Smoke grün auf iPhone 16e Simulator - Live: zitare-api.mana.how/healthz → HTTP/2 200 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
commit
0bd59ed148
25 changed files with 1468 additions and 0 deletions
105
Sources/App/RootView.swift
Normal file
105
Sources/App/RootView.swift
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
import ManaCore
|
||||
import SwiftUI
|
||||
|
||||
/// Top-Level-View: TabView mit drei Tabs.
|
||||
///
|
||||
/// **Phase ζ-0 — Setup.** Tabs zeigen aktuell nur Placeholder-Views.
|
||||
/// Ab Phase ζ-1 wird der Lesen-Tab durch `WebShellView` ersetzt,
|
||||
/// der `zitare.com` im `WKWebView` rendert.
|
||||
struct RootView: View {
|
||||
@Environment(AuthClient.self) private var auth
|
||||
@State private var selectedTab: AppTab = .read
|
||||
@State private var healthStatus: HealthStatus = .unknown
|
||||
|
||||
var body: some View {
|
||||
TabView(selection: $selectedTab) {
|
||||
placeholderView(
|
||||
title: "Lesen",
|
||||
subtitle: "ζ-1: WebShellView gegen zitare.com",
|
||||
systemImage: "book"
|
||||
)
|
||||
.tabItem { Label("Lesen", systemImage: "book") }
|
||||
.tag(AppTab.read)
|
||||
|
||||
placeholderView(
|
||||
title: "Erkunden",
|
||||
subtitle: "ζ-1: WebShell auf zitare.com/explore",
|
||||
systemImage: "sparkle.magnifyingglass"
|
||||
)
|
||||
.tabItem { Label("Erkunden", systemImage: "sparkle.magnifyingglass") }
|
||||
.tag(AppTab.explore)
|
||||
|
||||
AccountView(healthStatus: healthStatus)
|
||||
.tabItem { Label("Konto", systemImage: "person.circle") }
|
||||
.tag(AppTab.account)
|
||||
}
|
||||
.task {
|
||||
await probeHealth()
|
||||
}
|
||||
.onOpenURL { url in
|
||||
handle(url: url)
|
||||
}
|
||||
.onContinueUserActivity(NSUserActivityTypeBrowsingWeb) { activity in
|
||||
if let url = activity.webpageURL { handle(url: url) }
|
||||
}
|
||||
}
|
||||
|
||||
/// Phase ζ-0 Probe: ein Aufruf gegen `/healthz` bei App-Start.
|
||||
/// Ab Phase ζ-1 wandert das in einen Service, der den Status global
|
||||
/// trackt und im AccountView anzeigt.
|
||||
private func probeHealth() async {
|
||||
let api = ZitareAPI(auth: auth)
|
||||
do {
|
||||
let ok = try await api.healthCheck()
|
||||
healthStatus = ok ? .ok : .down
|
||||
Log.api.info("Healthz: \(ok ? "OK" : "DOWN")")
|
||||
} catch {
|
||||
healthStatus = .down
|
||||
Log.api.warning(
|
||||
"Healthz fehlgeschlagen: \(String(describing: error), privacy: .public)"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Deep-Link-Routing. Phase ζ-0: nur loggen. Phase ζ-1: in den
|
||||
/// passenden Tab + Pfad routen.
|
||||
private func handle(url: URL) {
|
||||
Log.app.info("Deep-Link empfangen: \(url.absoluteString, privacy: .public)")
|
||||
// ζ-1 TODO: parse zitare.com/q/<slug>, /a/<slug>, /c/<slug>
|
||||
// und in WebShellView mit entsprechender URL laden.
|
||||
}
|
||||
|
||||
private func placeholderView(
|
||||
title: String,
|
||||
subtitle: String,
|
||||
systemImage: String
|
||||
) -> some View {
|
||||
VStack(spacing: 16) {
|
||||
Image(systemName: systemImage)
|
||||
.font(.system(size: 48))
|
||||
.foregroundStyle(ZitareTheme.primary)
|
||||
Text(title)
|
||||
.font(.title2)
|
||||
.fontWeight(.semibold)
|
||||
Text(subtitle)
|
||||
.font(.callout)
|
||||
.foregroundStyle(ZitareTheme.mutedForeground)
|
||||
.multilineTextAlignment(.center)
|
||||
.padding(.horizontal, 32)
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
.background(ZitareTheme.background)
|
||||
}
|
||||
}
|
||||
|
||||
enum AppTab: Hashable {
|
||||
case read
|
||||
case explore
|
||||
case account
|
||||
}
|
||||
|
||||
enum HealthStatus {
|
||||
case unknown
|
||||
case ok
|
||||
case down
|
||||
}
|
||||
24
Sources/App/ZitareNativeApp.swift
Normal file
24
Sources/App/ZitareNativeApp.swift
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
import ManaCore
|
||||
import SwiftUI
|
||||
|
||||
@main
|
||||
struct ZitareNativeApp: App {
|
||||
@State private var auth: AuthClient
|
||||
|
||||
init() {
|
||||
let auth = AuthClient(config: AppConfig.manaAppConfig)
|
||||
auth.bootstrap()
|
||||
_auth = State(initialValue: auth)
|
||||
Log.app.info(
|
||||
"Zitare starting — auth status: \(String(describing: auth.status), privacy: .public)"
|
||||
)
|
||||
}
|
||||
|
||||
var body: some Scene {
|
||||
WindowGroup {
|
||||
RootView()
|
||||
.environment(auth)
|
||||
.tint(ZitareTheme.primary)
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue