Skip to content

fix: disable unavailable Graphic Lab color counts - #26

Open
2dubu wants to merge 1 commit into
mainfrom
fix/graphic-color-count-options
Open

2dubu wants to merge 1 commit into
mainfrom
fix/graphic-color-count-options

Conversation

@2dubu

@2dubu 2dubu commented Sep 19, 2026

Copy link
Copy Markdown
Owner

Summary

Graphic Lab now enables only color counts that the current palette and strategy can actually produce. Valid selections are retained; unavailable selections fall back to the largest available count. A single-color result disables all four options and displays an explanation.

Background

The lab always enabled 2–5, but the renderer can exhaust its eligible middle colors before reaching the requested count. It also retains two identical endpoint stops for a single-color result. Counting palette entries or returned stops alone therefore overstates the available choices.

A representative local reproduction produces one distinct color for Vibrant, two for Contrast, and up to four for Muted. This change derives those limits from the resolver rather than special-casing the image, strategies, or colors.

Solution

  • Add the read-only PaletteGraphic.resolvedStopColors property, backed by the same internal resolution entry point as rendering. Anchor fallbacks, candidate filtering, bisection, ordering, and rendering are unchanged.
  • Keep demo selection policy in GraphicColorOptions: resolve each requested count, count distinct RGB values, and enable a count only when the result fulfills it. No private selection algorithm is duplicated in the demo.
  • Reconcile selection when available choices change, including palette, swatch, and strategy updates. Use the reconciled value for rendering and export immediately. For single-color output, retain .two as the rendering fallback while showing no selected segment.
  • Wrap UISegmentedControl for per-segment disabling, native disabled accessibility state, a Colors label, and explanatory text/hint. On both iOS 17.5 and 26.4, a separate SwiftUI segmented Picker probe confirmed that child .disabled(true) still allowed the supposedly disabled segment to be selected. UIKit's setEnabled(_:forSegmentAt:) blocked the same tap.

Related Issue

N/A.

Verification

  • Added synthetic resolver tests for empty/single-color inputs, repeated RGB with different populations, zero/one/two/three middle candidates, filtered candidates, and every strategy/count combination.
  • Added demo policy tests for count availability, preserving valid selections, reducing unavailable selections, and changed palettes/swatches/strategies.
  • Added hosted Graphic Lab tests that verify native segment flags and binding updates across image and strategy changes. The XcodeGen demo scheme now includes these tests; its README documents running them.
  • Commands run:
    • swift package resolve
    • swift build -c debug
    • swift test --filter PaletteKitTests --filter PaletteKitInsightsTests — 59 tests reported, with the availability-gated live model test skipped.
    • xcodebuild test -scheme PaletteKit-Package -only-testing:PaletteKitTests -only-testing:PaletteKitInsightsTests -destination 'id=<iOS-26.4-simulator>' — all 150 core tests passed. The unrelated live Insights test failed because the simulator advertised model availability but lacked the Apple Intelligence model assets.
    • xcodebuild test -scheme PaletteKit-Package -only-testing:PaletteKitInsightsTests -skip-testing:PaletteKitInsightsTests/LiveGenerationTests -destination 'id=<iOS-26.4-simulator>' — all 8 offline Insights tests passed.
    • xcodegen generate --spec Examples/PaletteKitDemo/project.yml
    • xcodebuild test -project Examples/PaletteKitDemo/PaletteKitDemo.xcodeproj -scheme PaletteKitDemo -destination 'id=<simulator>' CODE_SIGNING_ALLOWED=NO — demo build and all 6 tests passed on both iOS 17.5 and 26.4.
    • git diff --check
  • An external, temporary XCUITest harness ran 3 UI tests on each of iOS 17.5 and 26.4: the SwiftUI disabled probe, synthetic Graphic Lab transitions, and the local photo reproduction. Verified disabled tap rejection, selected-state preservation/correction, accessibility enabled/disabled flags, and the single-color explanation. Inspected screenshots locally. No physical-device or spoken VoiceOver validation is claimed.
  • Environment: macOS 26.5.1, Xcode 26.4, Swift 6.3. Existing CIContext/Metal existential warnings and generated App Intents metadata warnings remain outside this change.

Compatibility and Impact

  • Public API: Additive, documented PaletteGraphic.resolvedStopColors; changelog updated. No existing signatures change.
  • Behavioral impact: Graphic Lab choices reflect actual output. Library rendering, color selection, MeshGradient, and animated graphics retain their existing behavior.
  • Performance impact: The demo performs four small color-resolution queries to inspect availability; these do not render images or re-extract palettes. No extraction or rendering algorithm changes.
  • Affected platforms: UIKit-backed PaletteGraphic and the iOS demo; the shared macOS package continues to build and pass its unit suites.

Additional Context

Based directly on main. Implementation and test files do not overlap the open performance PRs #23, #24, and #25; #25 also updates the changelog. The user photo and temporary UI harness remain local and are not included in the branch or uploaded. All committed fixtures are synthetic.

Checklist

  • This pull request is focused and contains no unrelated changes.
  • Tests were added or updated where behavior changed, or I explained why they are not needed.
  • Regression tests cover the intended behavior or root cause rather than only a reported sample.
  • Public API changes are documented and reflected in the changelog, or this pull request has none.
  • I considered source compatibility, behavioral changes, and performance impact.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant