Complete native rewrite of the web-based SoliCards game as a SwiftUI multiplatform app targeting iOS 17+, iPadOS 17+, and macOS 14+. Three solitaire variants (Klondike, Spider, FreeCell) with full game rules, drag & drop, smart zoom layout, 6 themes, 4 difficulty levels, SwiftData persistence, VoiceOver accessibility, and 57 unit tests. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
6.1 KiB
6.1 KiB
Changelog
All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
[1.2.0] - 2026-04-14
Fixed
- macOS maximized window cutoff — cards no longer overflow the bottom when the window is maximized. Height calculation uses 92% of available space as buffer for platform chrome and spacing.
- macOS card size cap — maximum card width capped at 120pt on macOS to prevent oversized cards on large displays. Extra space distributed as wider inter-card gaps.
- iOS landscape boost scoped to iOS only — the 30% landscape card size boost and 130% scroll content height no longer apply on macOS, where they caused overflow.
[1.1.0] - 2026-04-14
Changed
- Drag and drop rewritten — replaced
DragGestureoffset-based system with absolute position tracking (dragPosition: CGPoint). Cards now follow the finger exactly instead of appearing displaced. - Drop target resolution moved to ViewModel —
endDrag(at:)method checks drop targets directly, eliminating the need for a separate global gesture on the board. - Long press + drag — card dragging now requires a 0.15s long press before drag begins, disambiguating from scroll gestures and tap-to-move.
- Gesture priority fixed — card drag uses
.simultaneousGesture()instead of.gesture()so tap-to-move (onTapGesture) is never blocked. Tapping an ace now reliably moves it to the foundation. - Bottom toolbar redesigned (iOS) — reduced from 9 cramped icons to 4 clear items with labels: New, Undo, Hint, More (menu). Secondary actions (variant, difficulty, auto-complete, sound, rules, stats, settings) consolidated into the "More" menu.
- Toolbar contrast fixed — bottom bar uses
.ultraThinMaterialbackground with.toolbarColorScheme(.dark)so icons are readable against all game themes. - Top row aligned to tableau columns — stock at column 0, waste at column 1, gap at column 2, foundations at columns 3–6. No more
Spacer()pushing elements to opposite edges.
Added
- Smart zoom — card size dynamically adapts to the actual deepest tableau column so the entire board fits on screen without scrolling. Cards shrink as columns grow during play, and expand when columns shorten.
- Landscape mode improvements:
- 30% larger cards than the base height calculation
- Extra horizontal space distributed as wider inter-card gaps to fill the screen
- Vertical scrolling with long-press drag disambiguation (swipe = scroll, hold + drag = move card)
- Scroll content sized to 130% of viewport to guarantee scrollability
- Canvas background responds to scroll gestures (
.contentShape(Rectangle())) - Scroll automatically disabled during card drags to prevent interference
- Stock pile recycling indicator — empty stock shows an
arrow.clockwiseicon so users know to tap to recycle the waste pile.
Fixed
- Cards no longer displaced during drag — root cause was overlay positioned with
.offset(translation)from ZStack origin instead of.position(location)at the finger. - Tap-to-move works on iPad —
.simultaneousGesture()prevents long press from swallowing taps. - ScrollView no longer fights card drags —
.scrollDisabled()activates when a card drag is in progress. - Score bar no longer steals card area — moved outside
GeometryReaderso card layout measures the actual play area, not the area minus an inaccurate chrome guess. - Static analyzer warnings resolved — removed unused
freeCellsEmptyvariable, unuseddestTableaubinding, fixed@MainActorisolation onHapticManager.
[1.0.0] - 2026-04-13
Added
- Three solitaire variants: Klondike, Spider (2-deck), and FreeCell with full rule implementations
- Four difficulty levels: Easy, Medium, Hard, Expert — each with distinct draw counts, undo limits, hint availability, and score multipliers
- Drag and drop card movement using SwiftUI DragGesture with coordinate-space hit-testing
- Tap to move cards to the best available destination (foundation preferred)
- Undo system with up to 20 states of history
- Hint system with 5-priority move suggestions (aces first, then foundations, then tableau reveals)
- Auto-complete detection and animated execution when all remaining cards are face-up
- Win detection with animated victory overlay
- Six color themes: Classic Green, Dark Mode, Ocean Blue, Royal Purple, Forest Green, Sunset Orange
- 12 card back designs and 3 card face styles (classic, modern, extracted)
- Game persistence: auto-save on every move (debounced 2s), resume on app relaunch
- Statistics tracking: wins, losses, best score, best time, current/best streak per variant per difficulty
- User preferences: theme, card style, card back, sound toggle — all persisted via SwiftData
- Sound effects: card flip, place, error, victory (AVFoundation)
- Haptic feedback on iOS (UIImpactFeedbackGenerator)
- Full VoiceOver accessibility: labels, hints, and traits on all interactive elements
- Dynamic Type support on all text
- Reduce Motion support — animations skipped when user preference is enabled
- Localization infrastructure: String Catalog (Localizable.xcstrings) with 40+ strings, English source
- Keyboard shortcuts: Cmd+Z (undo), Cmd+N (new game), H (hint)
- Privacy manifest (PrivacyInfo.xcprivacy): no tracking, no data collection
- App icon with all required sizes for iOS and macOS
- 57 unit tests across 12 test suites covering models, game engine, move validation, auto-complete, and game state
- Multiplatform support: iOS 17+, iPadOS 17+, macOS 14+ from a single target
Architecture
- MVVM with Protocol-Oriented Strategy pattern
GameRulesprotocol withKlondikeRules,SpiderRules,FreeCellRulesimplementations@Observablemacro (Observation framework) for property-level UI updates@MainActorisolation for thread safety- SwiftData for persistence (GameRecord, StatsRecord, PrefsRecord)
- Zero external dependencies