560efb9554
- 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>
27 lines
942 B
Swift
27 lines
942 B
Swift
import Foundation
|
|
import AI
|
|
|
|
/// The ONE composition touch-point that names MockAIProvider. When S2 ships real providers,
|
|
/// only this file changes (mockProviders -> realProviders) — AIService, views, and intents stay put.
|
|
public enum AppAIComposition {
|
|
public static func mockProviders(unavailable: Set<AIProviderID> = []) -> [AIProviderID: any AIProvider] {
|
|
var providers: [AIProviderID: any AIProvider] = [:]
|
|
for id in AIProviderID.allCases {
|
|
providers[id] = MockAIProvider(id: id, available: !unavailable.contains(id))
|
|
}
|
|
return providers
|
|
}
|
|
|
|
public static func mockRouter(unavailable: Set<AIProviderID> = []) -> AIRouter {
|
|
AIRouter(
|
|
providers: mockProviders(unavailable: unavailable),
|
|
policy: .default,
|
|
log: { msg in
|
|
#if DEBUG
|
|
print("[route]", msg)
|
|
#endif
|
|
}
|
|
)
|
|
}
|
|
}
|