Files
hyungi_document_server/Sources/AppFeature/Theme/SageTheme.swift
T
Hyungi 560efb9554 feat(s3): SwiftUI sage 3-pane shell + 6 pages + AI seam
- AppFeature: SageTheme tokens, AppModel (@MainActor @Observable store), RootView (DEVONthink NavigationSplitView), Dashboard/Documents(MD-first+pending fallback+?token= download)/Search/Ask/Memos/Digest pages
- AI seam: AIService actor + AIResult, AppAIComposition (MockAIProvider x4 tiers), AICompletionView (numbered citations + always-visible routing badge), backend picker with visible explicit-unavailable error
- MarkdownView: block-aware renderer (GFM table separator-row skip, AttributedString inline-only)
- DSApp: thin @main, injects FixtureDSClient + mock AIRouter (zero backend / zero LLM)

swift build (full app) + swift test (19) green under Swift 6 strict concurrency. Sources/AI untouched (isolation vs freeze 17f8830 = clean).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 17:26:02 +09:00

86 lines
3.0 KiB
Swift

import SwiftUI
/// Single sage token source (web F1 @theme parity). The shell/tokens are fixed; each page's magnetic
/// treatment layers on top. No emoji status/format are color chips + short text. Status helpers are
/// per-vocabulary (md/review) so a value from one vocabulary never mis-colors another.
public enum Sage {
public static let brand = Color(hex: 0x4f8a6b)
public static let brandDark = Color(hex: 0x3d7256)
public static let surface = Color(hex: 0xf1f4ee)
public static let card = Color.white
public static let sidebar = Color(hex: 0xf4f7f1)
public static let ink = Color(hex: 0x23291f)
public static let muted = Color(hex: 0x697061)
public static let line = Color(hex: 0xdde3d6)
public static let amber = Color(hex: 0xb5840a)
public static let danger = Color(hex: 0xc0564a)
public static func domainColor(_ d: String?) -> Color {
switch d {
case "Engineering": return Color(hex: 0x2f7d8f)
case "Industrial_Safety": return Color(hex: 0xb5840a)
case "General": return Color(hex: 0x7a8b3f)
case "Programming": return Color(hex: 0x3d7256)
case "법령": return Color(hex: 0x8a6a3f)
case "Philosophy": return Color(hex: 0x7a6a9b)
default: return muted
}
}
public static func formatColor(_ f: String?) -> Color {
switch f?.lowercased() {
case "md": return Color(hex: 0x5a8f7a)
case "pdf": return Color(hex: 0xc0564a)
case "m4a", "mp3", "wav", "audio": return Color(hex: 0x8a6aa5)
case "html": return Color(hex: 0xc2911f)
case "docx", "xlsx", "txt": return Color(hex: 0x6f7c8a)
default: return muted
}
}
public static func mdStatusColor(_ s: String?) -> Color {
switch s {
case "completed": return brand
case "partial": return Color(hex: 0x7a9f86)
case "processing", "pending": return amber
case "failed": return danger
default: return muted
}
}
public static func reviewStatusColor(_ s: String?) -> Color {
switch s {
case "approved": return brand
case "pending": return amber
case "rejected": return danger
default: return muted
}
}
}
public extension Color {
init(hex: UInt) {
self.init(
.sRGB,
red: Double((hex >> 16) & 0xff) / 255.0,
green: Double((hex >> 8) & 0xff) / 255.0,
blue: Double(hex & 0xff) / 255.0,
opacity: 1.0
)
}
}
/// A small color chip + short label (no emoji icons).
public struct Chip: View {
let text: String
let color: Color
public init(_ text: String, _ color: Color) { self.text = text; self.color = color }
public var body: some View {
Text(text)
.font(.caption2.weight(.semibold))
.foregroundStyle(color)
.padding(.horizontal, 7).padding(.vertical, 2)
.background(color.opacity(0.13), in: Capsule())
}
}