The CI bot was committing image-tag bumps back to master after every
Docker build, which forced a `git pull --rebase` before every developer
push. Moving the kustomization commit to a dedicated `deploy` branch
keeps master clean — the build bot no longer diverges it.
Argo CD / Flux should now watch the `deploy` branch (targetRevision:
deploy) instead of master.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Engine: replace broken has_legal_moves loop (which checked buried
mid-column cards without sequence validation) with a delegation to
possible_instructions(), mirroring the hint system's logic exactly.
WASM: add has_moves: bool to GameSnapshot, computed in snap() using the
same stock/waste/possible_instructions check so the web client gets the
flag in every state update at no extra round-trip cost.
Web: show a non-blocking no-moves banner (slide-up toast) with Undo and
New Game actions when has_moves is false and the game is not won. Banner
hides automatically once a move restores legal play (e.g. after undo).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The flag was modelled as an opt-in non-standard rule but moving a card
off a foundation is in fact standard Klondike — disabling it is the
non-standard variant.
Changing the core default to true means every client (desktop, Android,
web) gets correct behaviour without each having to independently patch
the value after construction. Clients that expose a settings toggle
(desktop/Android) can still disable it through SettingsResource.
- game_state.rs: flip default from false → true in new_with_mode
- game_state.rs: rename/update take_from_foundation_disabled_by_default
test to reflect the new intended default
- solitaire_wasm/lib.rs: remove now-redundant override in new()
(from_saved keeps its override to fix old saves that serialised false)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
GameState::new_with_mode defaults take_from_foundation=false (non-
standard; the flag exists so the desktop can offer it as a setting).
The WASM web client has no settings layer, so this flag was never
flipped on — every drag or double-click from a foundation pile was
silently rejected by the rules engine.
Set take_from_foundation=true in both SolitaireGame::new (fresh games)
and SolitaireGame::from_saved (restored games, which may have the old
default serialised).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- solitaire_wasm: add SolitaireGame::serialize() and from_saved() so JS
can round-trip the full GameState through localStorage as JSON
- game.js: save {gameState, elapsedSecs, drawThree} to localStorage
(key: fs_game_save) on every render(); clear the save on win
- game.js: on bootstrap, check for a saved game and show a resume
dialog if one exists; Resume restores state + timer, New Game discards
the save and starts fresh with a random seed
- game.html: add #resume-overlay markup (same pattern as win-overlay)
- game.css: add styles for the resume dialog and its secondary button
localStorage failures (private-browsing quota) are silently ignored so
they never block gameplay.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The sync-setup modal was silently blocked by its own guard:
other_modal_scrims checks for any ModalScrim without SyncSetupScreen,
but the Settings panel IS a ModalScrim, so clicking Connect from within
Settings always hit the guard and returned early.
Two fixes:
- handle_sync_buttons: set SettingsScreen.0 = false when ConnectSync
is pressed so settings closes as the event is fired
- open_sync_setup_modal: exclude SettingsPanel from other_modal_scrims
to handle the deferred-despawn timing window (settings scrim entity
still exists in the world until command buffers flush at frame end)
- Make SettingsPanel pub so sync_setup_plugin can reference it
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
GameState serializes take_from_foundation=false (the core default),
so saved games on disk and direct-loaded states never had the setting
applied from SettingsResource — only freshly dealt games did.
Two fixes:
- sync_settings_to_game: new system that reads SettingsChangedEvent
and patches game.0.take_from_foundation on every settings change
(covers initial settings load at startup and in-session toggles)
- handle_restore_prompt: apply settings immediately after game.0 =
restored so the Continue path also respects the current setting
- Register SettingsChangedEvent in GamePlugin::build (idempotent with
SettingsPlugin) so the message is available in headless test apps
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- radial_menu: replace active_id.unwrap() with let Some guard — no
runtime panic possible even if DragState races (§2.3)
- card_plugin: add bottom-right AndroidCornerBg overlay to mask the
rotated baked-in text on classic PNG cards (mirrors top-left treatment)
- hud_plugin: bump Android action button min_width 44→52 px to give
~22px glyphs adequate padding after dynamic font-size increase
- layout: fix doc-lazy-continuation clippy lint in BOTTOM_BAR_HEIGHT comment
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
compute_layout only subtracted safe_area_bottom (OS gesture/nav bar) from
the vertical budget, but the app's own action bar (≡ ← || ? ! M +) sits
*above* that zone — invisible to safe_area_bottom. On Android the bar is
60 px tall (44 px min-height buttons + 8 px top + 8 px bottom bar padding),
so deep tableau columns scrolled 60 px behind the button row.
Fix: add BOTTOM_BAR_HEIGHT (60 px Android, 0 desktop) to safe_area_bottom
before both affected calculations:
• card_width_height_based — height-based card sizing
• avail — budget fed to update_tableau_fan_frac for adaptive fan spacing
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The bottom bar's 7 icon buttons (≡ ← || ? ! M +) used TYPE_BODY = 14 px,
a fixed size that is too small on phone screens.
New behaviour:
- `action_bar_font_size(window_width)` returns `(window_width / 40).clamp(16, 30)`,
giving ~22 px on a 900 logical-px phone and ~16 px on narrow viewports.
- `ActionButtonLabel` marker added to each button's text node (Android only).
- `spawn_action_buttons` reads `Query<&Window>` at startup to apply the
correct initial size before the first frame renders.
- `resize_action_bar_labels` system re-runs whenever `LayoutResource`
changes (window resize / orientation change) to keep glyphs in sync.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>