Merged
Conversation
…t hash + guard .loading reload
Also fix two library build errors found during integration: - AsyncPaletteGraphic.body: move var mutation outside @ViewBuilder switch (type '()' cannot conform to 'View') - AsyncPaletteGraphicView.deinit: remove loader.cancel() call from nonisolated context (loader's own deinit already cancels; redundant + actor-isolation error) - Promote Error → any Error across async files to silence future-Swift warnings
…ngfisher/Lottie alignment)
BREAKING CHANGE: `CardPalette` public type renamed to `GraphicPalette`. Type semantically describes the resolved-palette-for-graphic, independent of the card use case it was originally extracted for. Local var name `cardPalette` and parameter label `cardPalette:` in PaletteGraphicRenderer.resolveStopColors also renamed for consistency. Migration: `CardPalette(...)` → `GraphicPalette(...)`. No behavioral change. Pre-1.5.0 release timing chosen to absorb breakage while user count is small.
…StopColors signature Adds PaletteGraphicRenderer.resolveAnchors as the single internal source of truth for SwatchStrategy fallback resolution. Returns (center, edge) tuple — the only fields the renderer actually consumes. resolveStopColors now takes the two PaletteColor anchors directly instead of receiving a GraphicPalette wrapper. PaletteGraphic.swift fallback path also routed through resolveAnchors so PaletteGraphicRenderer becomes the single owner of strategy fallback logic — even though PaletteGraphic is gated by canImport(SwiftUI) and the Renderer only by canImport(UIKit), UIKit is the stricter condition so the helper is reachable from both. GraphicPalette type still exists; subsequent commits remove it. This commit is independently green: all 43 tests pass.
…renderer suite GraphicPalette was a 5-field public struct that the library only consumed two fields of (center, edge). Background and accent existed solely for the demo's swatch chip visualization — a demo-driven public API we shouldn't have shipped. This commit: - Deletes Sources/PaletteKit/Graphic/GraphicPalette.swift - Splits SwatchStrategy out to its own file (Sources/PaletteKit/Graphic/SwatchStrategy.swift) since it remains public via Configuration.swatchStrategy - Deletes Tests/PaletteKitTests/GraphicPaletteTests.swift - Ports the 6 strategy fallback tests (vibrant/contrast/muted hex checks, fallback-when-swatches-missing, nil-swatches, allCases count) to PaletteGraphicRendererTests, targeting the internal resolveAnchors helper added in the previous commit. background/accent coverage is intentionally dropped — those fields are gone from the library. Demo still references GraphicPalette in CardLabView; the next commit inlines a private replacement struct. Library + library tests are green: 35/35 (was 43, lost 8 GraphicPalette unit tests, ported 6 to renderer suite, net −2 since equality / Hashable conformance tests no longer apply to a non-existent type). BREAKING CHANGE: `GraphicPalette` public type removed. Callers needing the resolved (center, edge, background, accent) color set should replicate the strategy fallback chain locally — see PaletteGraphicRenderer.resolveAnchors for the canonical center/edge logic. Pre-1.5.0 release window chosen to absorb breakage while user count is small.
CardLabView is the lab tool that surfaces all four resolved colors (center / edge / background / accent) for visual inspection while tweaking strategy + grain configuration. The dropped library GraphicPalette type held this exact shape but only because the demo needed it — see the previous commit for context. Inlines a private nested struct CardLabView.ResolvedColors that replicates the strategy fallback chain locally. ~40 lines of duplicated logic vs the library's internal resolveAnchors helper, but contained to a demo-only file. Future divergence (lab adds custom rendering modes, etc.) is now decoupled from library evolution. iOS Simulator build succeeds with no new warnings from this change (4 pre-existing warnings in BenchSuiteView/TimingsView/ContentView/BenchView are unrelated).
…d (AsyncImage parity) Closes the SwiftUI/UIKit API parity gap flagged in v1.5 final review: SwiftUI consumers couldn't observe success state for telemetry, secondary UI composition, or custom error rendering. Adds: - public enum AsyncPaletteGraphicPhase (.empty/.loading/.success/.failure) — promoted from internal ResolutionPhase, now part of the public surface - AsyncPaletteGraphic.init(image:..., content: (Phase) -> Content) — primary init mirroring AsyncImage's phase content overload. No callback params (observe via phase). Generic over Content: View. - Convenience init unchanged ergonomically EXCEPT swatchStrategy: param dropped (was duplicating configuration.swatchStrategy). Use Configuration directly. Why phase closure not callback: AsyncImage and lottie-ios SwiftUI both use content closure pattern — callbacks fight SwiftUI's declarative model. The dropped onSuccess callback proposal would have been a SwiftUI outlier. BREAKING CHANGE: AsyncPaletteGraphic is now generic over Content not Placeholder. Migration: 'AsyncPaletteGraphic<Color>' → use convenience init unchanged (AnyView constraint internal); explicit 'AsyncPaletteGraphic<MyView>' type annotations need updating. Convenience init's swatchStrategy param removed — set via configuration.swatchStrategy. UIKit AsyncPaletteGraphicView's onSuccess callback unchanged (UIKit convention is callbacks; AsyncImage parity reasoning is SwiftUI-specific).
… load Per code review reflection: not every public API needs a runtime demo. The async wrapper's API is already simple enough (3 lines: image:, optional placeholder/configuration, frame+clipShape) that the canonical documentation in PaletteKit.docc/AsyncLoading.md and the README example demonstrate it more clearly than a single-image lab tab ever could. The CardLab demo remains the lab tool — it shows the full Configuration surface (direction, colorCount, swatchStrategy, grain, axis, shape). Async-loading-from-URL is orthogonal to those configuration knobs; adding it as a parallel tab introduced a thin demo that didn't earn its UI footprint. Removed: - Examples/PaletteKitDemo/PaletteKitDemo/Async/AsyncLoadView.swift (file) - Examples/PaletteKitDemo/PaletteKitDemo/Async/ (now-empty directory) - TabView wrapper in PaletteKitDemoApp.swift (reverted to single ContentView) - Xcode project Async/ group registration (via XcodeBuildMCP) iOS Sim build still succeeds with no new warnings.
6834962 to
91366e7
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
AsyncPaletteGraphic+ UIKitAsyncPaletteGraphicView— async-loading siblings of v1.4'sPaletteGraphicpair. Pass anImageSource(URL / Data / CGImage) and the view extracts the palette internally before rendering.PaletteCache(process-wide.sharedsingleton + DI-friendly instances) memoizes resolved palettes — eliminates redundant extraction across list cells, navigation back-and-forth, and shared brand images.AsyncPaletteGraphicTransition(SwiftUI) + per-instanceTransition(UIKit), coordinated 0.20s / 0.35s / 0.50s presets matchingKarrotUIKit/AsyncImageprecedent. Cache hits skip the transition (sync resolution → no animation).PaletteGraphicwith placeholder slot) and phase content closure mirroring Apple'sAsyncImage(url:content:)for telemetry / secondary UI composition / custom error rendering.What's changed
Library
Sources/PaletteKit/Graphic/Async/:AsyncPaletteGraphic(SwiftUI View) — generic overContent: View, two public initsAsyncPaletteGraphicView(UIKitUIViewsubclass) — pair ofPaletteGraphicViewAsyncPaletteGraphicLoader— internal@MainActor ObservableObject, shared by both viewsPaletteCache— public NSCache wrapper,countLimit = 100, String-keyedAsyncPaletteGraphicTransition+View.asyncPaletteGraphicTransition(_:)modifierAsyncPaletteGraphicPhase— public enum (.empty / .loading / .success / .failure)Sources/PaletteKit/Graphic/SwatchStrategy.swift— extracted from formerCardPalette.swiftCard/→Graphic/,Support/→Logging/Tests
PaletteCacheTests,AsyncPaletteGraphicLoaderTests,AsyncPaletteGraphicTransitionTests,AsyncPaletteGraphicViewTestsPaletteGraphicRendererTestsafter theGraphicPalettetype was droppedDocs
AsyncLoading.mdcovering both init styles, caching, transitions, error handlingTutorials/Card.mdgains async sectionPaletteKit.mdTopics catalog getsAsync loadingsubsection1.5.0, async example added next to### Generate a graphic, Roadmap updated (v1.5 ✅, v1.6 PaletteMeshGraphic listed)Migration
This release is largely additive but contains two intentional breaks made under pre-tag low-user-count conditions:
CardPalettepublic typePaletteGraphic/AsyncPaletteGraphicdirectly; the resolved center/edge logic is internal to the renderer. Demo lab includes a localResolvedColorsstruct if you need the same 4-color resolution pattern as a reference.Sources/PaletteKit/Card/Sources/PaletteKit/Graphic/(file-level rename only — no API change beyond theCardPaletteremoval above)API quick-look
Test plan
swift buildclean on macOS hostswift test35/35 passPaletteKitDemosucceeds (no new warnings introduced)