refactor: Migration auf ManaWebShell + ManaTheme.paper aus mana-swift-* v1.6.0/v0.6.0
ManaWebShell aus mana-swift-ui v0.6.0 ersetzt den lokalen `Sources/Features/WebShell/`-Ordner. WebShellCoordinator, WebShell- View, WebShellScripts geloescht (~430 LOC). CookieBridge bleibt lokal (App-spezifischer Cookie-SSO-Pfad fuer .mana.how), wandert nach `Sources/Core/WebShell/CookieBridge.swift`. `RootView.makeWebShellConfig()` baut Config mit Host-Whitelist `zitare.com` + `www.zitare.com` + `*.mana.how`, ZitareTheme-Hints, `syncDarkMode(localStorageKey: "zitare-mode")` und `hideElements` fuer den zitare-web-Header. ZitareTheme forwarded auf ManaTheme.paper aus mana-swift-core v1.6.0 (~90 LOC weg, paper-Werte jetzt single-source in `mana/packages/themes/src/variants/paper.css`). AppConfig.userAgent als plattform-spezifischer Helper hinzu. 20/20 Unit-Tests gruen. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
e139a382d8
commit
4b00c4ecdf
8 changed files with 72 additions and 534 deletions
|
|
@ -43,4 +43,12 @@ enum AppConfig {
|
|||
/// zusätzlich rauskopieren. Bis dahin schlägt der Pull mit 404
|
||||
/// fehl und `SnapshotSync.tryRefresh()` macht fail-soft no-op.
|
||||
static let snapshotURL = webBaseURL.appendingPathComponent("index-min.json")
|
||||
|
||||
/// User-Agent-Suffix für WKWebView (ManaWebShell). WKWebView hängt
|
||||
/// das an seinen Standard-UA an, ersetzt ihn nicht.
|
||||
#if os(macOS)
|
||||
static let userAgent = "ZitareNative/0.1 (macOS)"
|
||||
#else
|
||||
static let userAgent = "ZitareNative/0.1 (iOS)"
|
||||
#endif
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,105 +1,28 @@
|
|||
import ManaTokens
|
||||
import SwiftUI
|
||||
|
||||
#if canImport(UIKit)
|
||||
import UIKit
|
||||
|
||||
private typealias PlatformColorType = UIColor
|
||||
#elseif canImport(AppKit)
|
||||
import AppKit
|
||||
|
||||
private typealias PlatformColorType = NSColor
|
||||
#endif
|
||||
|
||||
/// Paper-Variant aus `mana/packages/themes/src/variants/paper.css`.
|
||||
/// Lokal in zitare-native nachgebaut, weil ManaTokens noch keine
|
||||
/// Variants kennt.
|
||||
/// Zitare-Theme — forwarded auf ``ManaTheme/paper`` aus
|
||||
/// `mana-swift-core` v1.6.0 (dieselbe Variant wie zitare-web).
|
||||
///
|
||||
/// Sepia, warm, lese-fokussiert — skeumorph an Druckpapier angelehnt,
|
||||
/// passt zum (read)-Surface der Web-App.
|
||||
/// Bis v1.5.x lebte hier ein ~90-LOC-HSL-Apparat als lokaler Nachbau
|
||||
/// der `paper.css`-Variant. Mit v1.6.0 liefert ManaTokens alle acht
|
||||
/// Web-Theme-Variants nativ — `paper` ist eine davon.
|
||||
///
|
||||
/// `ZitareTheme` bleibt als dünner Alias bestehen, damit bestehende
|
||||
/// Call-Sites nicht in einem Sprint umziehen müssen. Neue Call-Sites
|
||||
/// bevorzugen direkt `ManaTheme.paper.<token>` (oder
|
||||
/// `@Environment(\.manaTheme)`).
|
||||
enum ZitareTheme {
|
||||
/// Page-Hintergrund (warmes Off-White / dunkles Sepia)
|
||||
static let background = dynamic(light: HSL(38, 28, 95), dark: HSL(24, 14, 9))
|
||||
|
||||
/// Standard-Text
|
||||
static let foreground = dynamic(light: HSL(20, 14, 16), dark: HSL(38, 24, 88))
|
||||
|
||||
/// Card, Panel, Modal
|
||||
static let surface = dynamic(light: HSL(0, 0, 100), dark: HSL(24, 12, 13))
|
||||
|
||||
/// Hover-State auf Surface
|
||||
static let surfaceHover = dynamic(light: HSL(38, 24, 92), dark: HSL(24, 14, 17))
|
||||
|
||||
/// Disabled-Felder, Skeleton
|
||||
static let muted = dynamic(light: HSL(38, 20, 90), dark: HSL(24, 12, 18))
|
||||
|
||||
/// Sekundär-Text, Placeholder
|
||||
static let mutedForeground = dynamic(light: HSL(20, 14, 50), dark: HSL(38, 12, 60))
|
||||
|
||||
/// Rahmen, Trennlinien
|
||||
static let border = dynamic(light: HSL(38, 18, 80), dark: HSL(24, 10, 25))
|
||||
|
||||
/// Zitare-Primary — warmes Terra/Sienna im Light, weicheres Sienna im Dark
|
||||
static let primary = dynamic(light: HSL(18, 50, 38), dark: HSL(24, 60, 65))
|
||||
|
||||
/// Text auf Primary
|
||||
static let primaryForeground = dynamic(light: HSL(0, 0, 100), dark: HSL(24, 14, 9))
|
||||
|
||||
static let error = dynamic(light: HSL(0, 65, 45), dark: HSL(0, 60, 55))
|
||||
static let success = dynamic(light: HSL(135, 35, 35), dark: HSL(135, 35, 55))
|
||||
static let warning = dynamic(light: HSL(38, 80, 40), dark: HSL(38, 70, 55))
|
||||
|
||||
// MARK: - HSL Helper
|
||||
|
||||
struct HSL {
|
||||
let hue: Double
|
||||
let saturation: Double
|
||||
let lightness: Double
|
||||
|
||||
init(_ hue: Double, _ saturation: Double, _ lightness: Double) {
|
||||
self.hue = hue
|
||||
self.saturation = saturation
|
||||
self.lightness = lightness
|
||||
}
|
||||
|
||||
var color: Color {
|
||||
Color(
|
||||
hue: hue / 360.0,
|
||||
saturation: saturation / 100.0,
|
||||
brightness: brightnessFromLightness(),
|
||||
opacity: 1.0
|
||||
)
|
||||
}
|
||||
|
||||
/// HSL → HSB Konversion (SwiftUI Color nutzt HSB).
|
||||
private func brightnessFromLightness() -> Double {
|
||||
let l = lightness / 100.0
|
||||
let s = saturation / 100.0
|
||||
return l + s * min(l, 1 - l)
|
||||
}
|
||||
}
|
||||
|
||||
private static func dynamic(light: HSL, dark: HSL) -> Color {
|
||||
#if canImport(UIKit)
|
||||
return Color(
|
||||
PlatformColorType { trait in
|
||||
trait.userInterfaceStyle == .dark
|
||||
? PlatformColorType(dark.color)
|
||||
: PlatformColorType(light.color)
|
||||
}
|
||||
)
|
||||
#elseif canImport(AppKit)
|
||||
return Color(
|
||||
PlatformColorType(name: nil) { appearance in
|
||||
let isDark = appearance.bestMatch(
|
||||
from: [.darkAqua, .aqua]
|
||||
) == .darkAqua
|
||||
return isDark
|
||||
? PlatformColorType(dark.color)
|
||||
: PlatformColorType(light.color)
|
||||
}
|
||||
)
|
||||
#else
|
||||
return light.color
|
||||
#endif
|
||||
}
|
||||
static let background = ManaTheme.paper.background
|
||||
static let foreground = ManaTheme.paper.foreground
|
||||
static let surface = ManaTheme.paper.surface
|
||||
static let surfaceHover = ManaTheme.paper.surfaceHover
|
||||
static let muted = ManaTheme.paper.muted
|
||||
static let mutedForeground = ManaTheme.paper.mutedForeground
|
||||
static let border = ManaTheme.paper.border
|
||||
static let primary = ManaTheme.paper.primary
|
||||
static let primaryForeground = ManaTheme.paper.primaryForeground
|
||||
static let error = ManaTheme.paper.error
|
||||
static let success = ManaTheme.paper.success
|
||||
static let warning = ManaTheme.paper.warning
|
||||
}
|
||||
|
|
|
|||
75
Sources/Core/WebShell/CookieBridge.swift
Normal file
75
Sources/Core/WebShell/CookieBridge.swift
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
import Foundation
|
||||
import ManaCore
|
||||
import WebKit
|
||||
|
||||
/// Reicht den mana-auth-JWT als Cookie an den `WKWebView` weiter, sodass
|
||||
/// eingeloggte `(app)`-Routen auf `zitare.mana.how` ohne zweiten Login
|
||||
/// erreichbar sind.
|
||||
///
|
||||
/// **Phase ζ-1: Skeleton.** Methoden existieren, werden aber heute
|
||||
/// nicht aufgerufen — bevor sie scharfgeschaltet werden, muss der
|
||||
/// Cookie-SSO-Pfad auf der Web-Seite (`zitare/apps/api/src/auth/`
|
||||
/// und `apps/zitare/src/lib/auth/token-helper.ts`) gegen einen
|
||||
/// *echten* mana-auth-Token End-to-End getestet sein (Verifikations-
|
||||
/// Lücke in `zitare/STATUS.md`).
|
||||
///
|
||||
/// **Phase ζ-3:** wird in `SubmitQuoteView` benutzt — vor dem POST
|
||||
/// gegen `zitare-api.mana.how` und vor dem Öffnen von
|
||||
/// `zitare.mana.how/me` im WebView.
|
||||
///
|
||||
/// **Cookie-Schema** (gespiegelt zu mana-auth, siehe
|
||||
/// `mana/services/mana-auth/src/auth/cookies.ts`):
|
||||
/// - Name: `mana.access` (JWT) und optional `mana.refresh` (Opaque)
|
||||
/// - Domain: `.mana.how` (App-Surface; **nicht** `.com`)
|
||||
/// - Path: `/`
|
||||
/// - Secure: true, HTTPOnly: false (WebView muss lesen können),
|
||||
/// SameSite: Lax
|
||||
enum CookieBridge {
|
||||
/// Setzt den `mana.access`-Cookie im geteilten `WKHTTPCookieStore`,
|
||||
/// wenn der `AuthClient` einen gültigen JWT hält. No-op sonst.
|
||||
@MainActor
|
||||
static func installManaAccess(from auth: AuthClient) async {
|
||||
guard case .signedIn = auth.status, let token = currentAccessToken(from: auth) else {
|
||||
Log.web.debug("CookieBridge: kein signedIn-Token, no-op")
|
||||
return
|
||||
}
|
||||
guard let cookie = makeAccessCookie(token: token) else {
|
||||
Log.web.warning("CookieBridge: konnte Cookie-Properties nicht bauen")
|
||||
return
|
||||
}
|
||||
let store = WKWebsiteDataStore.default().httpCookieStore
|
||||
await store.setCookie(cookie)
|
||||
Log.web.info("CookieBridge: mana.access für .mana.how gesetzt")
|
||||
}
|
||||
|
||||
/// Entfernt den `mana.access`-Cookie wieder — etwa nach Logout.
|
||||
@MainActor
|
||||
static func removeManaAccess() async {
|
||||
let store = WKWebsiteDataStore.default().httpCookieStore
|
||||
let cookies = await store.allCookies()
|
||||
for cookie in cookies where cookie.name == "mana.access" {
|
||||
await store.deleteCookie(cookie)
|
||||
}
|
||||
Log.web.info("CookieBridge: mana.access entfernt")
|
||||
}
|
||||
|
||||
private static func currentAccessToken(from auth: AuthClient) -> String? {
|
||||
// ManaCore hält den JWT im Keychain. In ζ-3 ersetzt durch die
|
||||
// tatsächliche `auth.currentAccessToken()`-API; heute nur
|
||||
// Skelett-Hook, damit Cookie-Setup und API in Reichweite sind.
|
||||
// Linter beruhigen ohne unused warning:
|
||||
_ = auth
|
||||
return nil
|
||||
}
|
||||
|
||||
private static func makeAccessCookie(token: String) -> HTTPCookie? {
|
||||
HTTPCookie(properties: [
|
||||
.name: "mana.access",
|
||||
.value: token,
|
||||
.domain: ".mana.how",
|
||||
.path: "/",
|
||||
.secure: true,
|
||||
.sameSitePolicy: HTTPCookieStringPolicy.sameSiteLax
|
||||
])
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue