Delete CLAUDE_PROMPT_PACK/SPEC/WORKFLOW (superseded by CLAUDE.md),
SESSION_HANDOFF files, old android investigation notes, phase-plan docs
under docs/superpowers/, and all docs/ui-mockups/ (HTML+PNG mockups
from the pre-Terminal design pass). Also removes local artifacts:
analytics_impl_prompt.md, review_project.py, delete_runs.sh, ruvector.db
files, and the stray solitaire_wasm/solitaire_server/ build artefact.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace all display-name occurrences across web pages, Rust source,
docs, and Cargo metadata. Update localStorage token key from sq_token
to fs_token. Tagline "Klondike Solitaire" retained as genre descriptor.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
A2: docs/ANDROID.md — remove stale "permanent fix to come" note;
clarify --lib is the canonical command; root-cause the upstream
cargo-apk bug. SESSION_HANDOFF.md closes the open item.
A3: Remove dead CARD_PLAN.md references from four source module
doc comments (theme/importer.rs, theme/plugin.rs, assets/mod.rs,
assets/svg_loader.rs). Also fix stale "future picker UI" language
in plugin.rs (picker shipped in Phase 7).
A4: ui_modal.rs spawn_modal_button — add min_height: Val::Px(48.0)
so every modal action button meets Material's 48 dp touch target
minimum. Modal button height was 42 px (2×SPACE_3 + TYPE_BODY_LG);
now clamped to 48 px minimum. Cards at 40 dp on 360 dp phones are
layout-constrained (7 columns) and cannot be widened.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
reqwest/hyper-util's GaiResolver calls tokio::runtime::Handle::current()
which panics with "no reactor running" when driven by Bevy's
AsyncComputeTaskPool (async-executor, not Tokio). Fixed all three spawn
sites in sync_plugin.rs (start_pull, handle_manual_sync_request,
push_replay_on_win) and the push_on_exit fallback by wrapping each HTTP
future in tokio::runtime::Builder::new_current_thread().enable_all().
Also fixes a clippy type_complexity warning in hud_plugin.rs by
extracting HudScoreFont / HudMovesFont / HudTimeFont type aliases for
the update_hud_typography query parameters.
Closes P4 AVD JNI bridge test: keystore JNI verified working on
Android 14 x86_64 AVD (load_access_token returned NotFound correctly);
clipboard JNI compiled and linked, runtime test deferred to a real-device
session with a won game and active sync server.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
P3 — App-icon density buckets:
- Created solitaire_app/res/mipmap-{mdpi,hdpi,xhdpi,xxhdpi,xxxhdpi}/
ic_launcher.png from assets/icon/ (48→mdpi, 64→hdpi, 128→xhdpi,
256→xxhdpi+xxxhdpi). aapt downscales oversized buckets; no quality loss.
- Added resources = "res" to [package.metadata.android] so cargo-apk/aapt
packages the mipmap tree into the APK.
- Added icon = "@mipmap/ic_launcher" to [package.metadata.android.application]
so the launcher references the density-bucketed icon instead of the
default grey system icon.
P3 — Density-aware card scaling: investigated, no code change required.
WindowResized fires with logical pixels; 256×384 card textures are
downscaled on all current phone targets (40dp logical → 120px physical
at 3× DPI). Upscaling only occurs on tablets wider than ~765dp at 3× DPI.
P4 — B0004 hierarchy warnings: investigated, no fix required.
.despawn() is recursive in Bevy 0.18; warnings are startup timing
artifacts (UI components propagating before parent initialises), not
gameplay bugs. No crashes or defects in 2+ min AVD runtime.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Closes the final two P2 Android playability items:
1. HUD typography — new `update_hud_typography` system fires on
`WindowResized` and adjusts Tier-1 font sizes: below 480 logical px
Score drops HEADLINE(26)→BODY_LG(18) and Moves/Timer drop
BODY_LG(18)→CAPTION(11), so all three fit in the 180dp HUD column
on a 360dp phone without wrapping.
2. Orientation lock — `[package.metadata.android.application.activity]`
with `orientation = "portrait"` in solitaire_app/Cargo.toml; cargo-apk
maps this to `android:screenOrientation="portrait"` in the generated
AndroidManifest.xml.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Touch screens have no right mouse button, so right-click radial was
inaccessible on Android. New system radial_open_on_long_press counts
up while a touch is held on a face-up card without crossing the drag
threshold; after 0.5 s it transitions RightClickRadialState to Active,
which the existing visual overlay and destination-ring infrastructure
then renders unchanged.
Three supporting changes to wire up the touch-driven confirm path:
- radial_track_cursor: falls back to the first active Touches position
when cursor_world returns None, so the hover ring tracks a sliding
held finger on Android.
- radial_handle_release_or_cancel: confirms on Touches::iter_just_released
(finger lift) in addition to right-mouse release. Cancels on
Touches::iter_just_canceled. No new event reader — uses the Touches
resource which is already in scope after the track_cursor addition.
- handle_double_tap: skips when the radial is active. Guards the
narrow edge case where the finger lifts on the exact same frame
as the 0.5 s long-press threshold fires; prevents a spurious
double-tap move from racing with the radial confirm.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Two improvements to drag responsiveness on Android:
1. Guard start_drag against touch-simulated mouse presses.
start_drag (mouse path) now bails when Touches::iter_just_pressed()
finds an active touch, so touch_start_drag always owns drag state on
touch-screen devices. Without the guard, Bevy/Winit versions that
synthesise MouseButton::Left from the primary touch would have the
mouse drag path claim drag state first (start_drag runs before
touch_start_drag in the system chain), leaving the card tracked via
cursor_world instead of the Touches resource.
2. Lower mobile drag commit threshold 10 px → 8 px.
Matches Android ViewConfiguration.getScaledTouchSlop() exactly.
Smaller threshold reduces the snap-to-finger displacement at commit
and makes drag feel more immediate.
Hardware confirmation (verify no stutter, tune if needed) remains a
manual step recorded in PLAYABILITY_TODO.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When handle_double_tap recognises a double-tap and fires MoveRequestEvent,
the moved card(s) are immediately tinted STATE_SUCCESS (lime #acc267) with
a 0.35 s HintHighlight so the player sees visual confirmation before the
card animation begins.
- Priority 1 (single top card): flashes that card only.
- Priority 2 (whole face-up stack): flashes every card in drag.cards.
Reuses the existing tick_hint_highlight cleanup path (restores sprite
to WHITE when timer expires) so no new system or component is needed.
The flash duration (0.35 s) slightly outlasts a typical card animation
(~0.3 s), giving the tint a brief moment at the destination before clearing.
Marks P1 "Double-tap auto-move visible feedback" as closed in
PLAYABILITY_TODO (hardware trigger-verification still manual).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
On a 360 dp portrait phone the card width is set by the 9-column
horizontal packing (360/9 = 40 dp); the fixed 0.25 fan fraction then
places the worst-case 13-card column in the top ~44 % of the screen,
leaving the bottom 56 % empty black.
`compute_layout` now solves for the fan fraction that exactly uses the
available vertical space below the tableau row:
ideal = avail / (12 * card_height)
On height-limited (desktop) windows ideal ≈ 0.25 and the clamp to the
minimum keeps existing behaviour. On width-limited (portrait phone)
windows the fan expands — ≈ 0.84 at 360 × 800 dp — stretching the
tableau to fill the screen.
Both `tableau_fan_frac` and `tableau_facedown_fan_frac` (scaled
proportionally) are stored on the `Layout` struct. `card_plugin` and
`input_plugin` read from the struct so rendering and hit-testing stay
in sync at every viewport size.
Three new regression tests:
- portrait phone expands fan_frac beyond desktop minimum
- expanded fan fits inside phone viewport (no overflow)
- desktop fan_frac stays at minimum 0.25
Closes P1 "Portrait-first card spacing" in PLAYABILITY_TODO.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Action buttons sized to text + 8 px padding made "Undo" end up
~46 x 33 px — fine for a mouse but at the threshold of a finger.
Adds `min_width: 48 px` and `min_height: 48 px` to the button
Node so every button meets Material's 48 dp thumb-target guideline.
Applied universally; the floor is a no-op for buttons whose
content already exceeds 48 px on either axis (Menu, Modes,
New Game, Pause, Help all clear 48 px wide; height was the
binding constraint at ~33 px).
Closes P1 #2 of docs/android/PLAYABILITY_TODO.md. Engine tests
pass; clippy clean.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The U / Esc / F1 / N caption chips next to the HUD action buttons
are meaningless on a touch device and visibly clutter the
narrow-viewport action row (visible as "Esc A [] N" in the v0.22.3
screenshot). `spawn_action_button` now rebinds `hotkey` to `None`
under `#[cfg(target_os = "android")]` so the chip-spawn branch is
skipped on touch builds.
Menu / Modes chevrons are unaffected — they indicate dropdown
behaviour and still apply on touch. Other hint surfaces
(onboarding, pause modal Esc hint, mode-card chips, replay
footer, modal toggle chips, help screen) live behind navigation
and are tracked as a P3 sweep in PLAYABILITY_TODO.md.
Closes P1 #1 of docs/android/PLAYABILITY_TODO.md. 855 engine
tests pass; clippy clean.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
`compute_layout` runs `window.max(MIN_WINDOW)`, which acts as a
component-wise floor: any window smaller than MIN_WINDOW on either
axis gets clamped up. The previous floor of 800x600 was set with
desktop in mind, but on Android the OS-provided window size is the
device resolution (~360 dp wide on a typical phone) and the clamp
silently re-laid the board for an 800 dp width.
Side effect: total grid width (9 * card_width) became ~800 px on a
360 dp viewport, so the leftmost foundation x-position fell past
-180 and the rightmost tableau pile past +180 — both clipped at
the visible edges, matching the v0.22.3 hardware screenshot.
Lowered MIN_WINDOW to 320x400, below the smallest reasonable phone
(~360x640), so every real device flows through compute_layout
unclamped. The floor is preserved as a sentinel against degenerate
windows (Bevy can briefly report 0-size during startup or after
minimisation on some compositors). Desktop's "minimum supported
playable size" is enforced separately via WindowResizeConstraints
in solitaire_app.
Updates `layout_below_minimum_clamps_to_minimum` to use values
below the new floor, and adds a new regression test
`phone_portrait_layout_fits_horizontally` that asserts all 13
piles fit inside a 360 x 800 dp viewport.
Closes P0 #4 of docs/android/PLAYABILITY_TODO.md. 855 engine tests
pass; clippy clean.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The v0.22.3 hardware screenshot showed the 6-button action row
(~510 px when laid out) overflowing into a 360 dp viewport from
the right anchor, with Menu and Undo clipped off-screen left and
Pause/Help/Modes/New_Game overlapping the left HUD column's
Score / Moves / Timer text.
Cap both clusters at `max_width: 50 %` so on mobile each takes
half the viewport (~180 px) and on desktop the cap is wider than
either cluster's natural width so the existing single-line
layout is preserved.
- Action button row: adds `flex_wrap: Wrap`, `row_gap`, and
`justify_content: FlexEnd` so the row breaks to multiple
right-aligned lines instead of clipping. 6 buttons become 2-3
lines of 2-3 buttons.
- HUD column tier rows: add `flex_wrap: Wrap` and `row_gap` to
the shared `row_node` helper so a long Mode/Challenge/Draw-cycle
combo soft-wraps onto two lines instead of pushing into the
action button column.
Closes P0 #2 of docs/android/PLAYABILITY_TODO.md. All 854 engine
tests pass; clippy clean.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Captures the gap between "boots without crashing" (v0.22.3 status)
and "actually playable on a phone." Tracks P0-P4 work items grouped
by impact: safe-area, HUD layout, card-back rendering, viewport
overflow, touch UX, density.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Player feedback after the 2-colour revert: "I do not like the
grey corners on the cards." The visible artifact was anti-
aliasing physics — the 1 px suit-coloured stroke (red for
hearts/diamonds, near-white for clubs/spades) faded through
gray pixels into the dark play surface at each rounded corner,
producing a visible "gray sliver" at the four arcs of every
card.
Fix: drop the stroke entirely. The card body fill defines the
shape against the play surface; the 5-unit brightness gap
between `#1a1a1a` body and `#151515` surface is enough to read
as a card edge without an explicit stroke. Anti-aliasing on a
fill-only rounded rect blends `#1a1a1a → #151515` over a few
pixels — barely perceptible compared to the
`stroke → transparent` gradient that produced the artifact.
### Changes
- `card_face_svg.rs`: removed `stroke="{colour}" stroke-width="2"`
from the card body rect. Reverted the 1 px stroke inset back
to `(x=0, y=0, width=256, height=384)` since there's no
longer a stroke to keep inside the pixmap. Module-level
comment updated to document the reasoning.
- `design-system.md` § Game Cards line 225 updated: "Border:
1px solid in suit color" → "Border: none." with the
artifact rationale recorded as audit trail.
- `card_face_svg_pin.rs` rebaselined: all 52 face hashes drift
(every card's perimeter pixels changed); 5 back hashes
unchanged.
Workspace clippy + cargo test --workspace clean. 1191 passing.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Per player feedback after the brief 4-colour-deck experiment:
"can we make the card suit colors the same as a regular
solitaire game would." Reverts the 4-colour split (`62b61cc`)
and bumps both 2-colour hues to read more like a real
Microsoft-Solitaire-on-dark-mode deck.
### Constants
- `RED_SUIT_COLOUR`: `#fb9fb1` (Terminal pink, then briefly
hearts-only) → `#e35353` (saturated red). More chromatic, less
pastel; reads as "the red suit" rather than "a Terminal-
themed pink." Visually distinct from `ACCENT_PRIMARY`
`#a54242` (the brick-red CTA accent) so chrome and suit don't
collapse to the same hue.
- `BLACK_SUIT_COLOUR`: `#d0d0d0` (matched `TEXT_PRIMARY`) →
`#e8e8e8` (near-white). Bumped slightly brighter so it reads
as a chromatic-neutral counterpart to the new saturated red,
not as "the same gray as body text." `TEXT_PRIMARY_HC`
(`#f5f5f5`) is still brighter for the high-contrast boost
path.
- `RED_SUIT_COLOUR_HC`: `#ff8aa0` (pinkish boost matching the
v0.21.0 pink default) → `#ff6868` (brighter saturated red).
Now reads as "more chromatic" than the new default red, not
"less saturated."
- `DIAMOND_SUIT_COLOUR` and `CLUB_SUIT_COLOUR` deleted — the
4-colour split is gone, hearts/diamonds re-pair under
`RED_SUIT_COLOUR` and clubs/spades under
`BLACK_SUIT_COLOUR`.
### `card_face_svg.rs`
- Module-level constants collapse from four (`SUIT_HEART` /
`SUIT_DIAMOND` / `SUIT_CLUB` / `SUIT_SPADE`) back to two
(`SUIT_RED` / `SUIT_DARK`) at the new saturated-red /
near-white values.
- `suit_paint()` reverts to the 2-colour pairing: hearts
filled-red, diamonds outlined-red, spades filled-near-white,
clubs outlined-near-white. Filled-vs-outlined glyph
differentiation stays the always-on CBM fallback.
### `card_plugin.rs`
- `text_colour()` reverts to a `card.suit.is_red()`
bifurcation. Comment block updated to reflect the new
truth table: red suits → saturated red (or CBM lime / HC
brighter red); dark suits → near-white (or HC brighter
near-white).
### Tests
Test block restructured back to the pre-4-colour shape: two
red/black pairing tests instead of one 4-colour distinctness
test. CBM/HC compose tests retuned to the 2-colour world (red
suits compose, dark suits compose; no separate diamonds-immune
or clubs-immune cases). 1191 passing / 0 failing — net 0 from
the prior commit (3 tests removed: the 4-colour distinctness
test + the diamonds/clubs-immune test; 2 tests added back: the
red-pairing + dark-pairing tests; existing tests amended to
new colour assumptions).
### `card_face_svg_pin`
All 52 face hashes drift (every suit's colour shifted); 5 back
hashes unchanged. Surgical rebaseline.
### `design-system.md`
§Suit Colors retitled "Two-color traditional pairing", table
updated with the new hex values, CBM section text simplified
back to red→lime swap on both red suits.
Workspace clippy clean.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Hearts pink (`#fb9fb1`), Diamonds gold (`#ddb26f`), Clubs lime
(`#acc267`), Spades gray (`#d0d0d0`) — each suit picks up its
own base16-eighties accent so a player scanning the table can
distinguish the suit by hue alone (faster recognition than the
2-colour traditional red/black scheme; common in poker decks).
All four colours already exist in the palette as semantic
state-token accents, so this is a pure remapping at the suit-
glyph site, not a palette extension.
The outlined-glyph differentiation (♦ ♣ outlined, ♥ ♠ filled)
is preserved on top of the colour split — it stays the always-
on colour-blind fallback per `design-system.md` §Accessibility,
and matters more than ever now that CBM hearts (lime) and
default clubs (lime) share a hue.
### Changes
- `card_face_svg.rs`: split `SUIT_RED` / `SUIT_DARK` into four
per-suit constants (`SUIT_HEART` / `SUIT_DIAMOND` / `SUIT_CLUB`
/ `SUIT_SPADE`). `suit_paint()` returns each suit's own
colour. Card border picks up the suit colour automatically
via the existing `(colour, paint)` destructure.
- `card_plugin.rs`: new `DIAMOND_SUIT_COLOUR` + `CLUB_SUIT_COLOUR`
constants; `text_colour()` rewritten as a per-suit match (was
red/black bifurcation). Both rendering paths (PNG production +
constant fallback under MinimalPlugins) stay in lockstep.
- CBM behaviour clarified: only hearts swap to lime now;
diamonds + clubs + spades are already hue-distinct from
the heart pink and stay unchanged. Under CBM the heart
(lime) and club (lime) share a hue but stay distinguishable
via the always-on filled-vs-outlined glyph differentiation.
- HC behaviour: only hearts (→ HC red) and spades (→ HC white)
have defined boosts. Diamonds (gold) and clubs (lime) are
already mid-luminance accents and stay at their default.
New test `text_colour_diamonds_and_clubs_are_immune_to_accessibility_flags`
pins all four flag combinations as no-ops for the gold +
lime suits.
- `design-system.md` §Suit Colors retitled "Four-color deck"
with the 4-colour table; CBM section text updated to
describe the hearts-only swap and the hearts/clubs hue
collision under CBM.
- `card_face_svg_pin.rs` rebaselined: 26 hashes drift
(13 clubs + 13 diamonds — the two suits whose colours
changed). Hearts, spades, and the 5 backs all keep their
prior hashes. Surgical scope, exactly what the pin test
was designed to surface.
### Tests
1191 passing / 0 failing — net 0 from the prior baseline:
two old 2-colour tests removed
(`text_colour_is_red_for_hearts_and_diamonds`,
`text_colour_is_black_for_clubs_and_spades`), one consolidated
4-colour test added
(`text_colour_4_colour_deck_assigns_each_suit_its_own_hue`)
plus a pairwise-distinct invariant guard, and one new test
covering the gold/lime suits' immunity to CBM/HC flags. Six
existing CBM/HC tests rewritten to use only the suits each flag
actually affects under the new scheme (hearts for CBM, hearts +
spades for HC).
Workspace clippy clean.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Project-wide palette shift at user request. Replaces the cyan
primary accent everywhere it surfaces — splash boot screen,
home menu glyphs, action chevrons, replay overlay banner +
scrub fill + chip border, achievement checkmarks, leaderboard
#1 indicator, radial menu fill, focus ring, card-back canonical
badge, etc. — with `#a54242` from the same base16-eighties
family as the existing pink suit colour.
Knock-on changes that all land in this commit per the
lockstep rule:
- ui_theme.rs: ACCENT_PRIMARY (#a54242), ACCENT_PRIMARY_HOVER
(#c25e5e brightened companion), FOCUS_RING (same hue, 0.85
alpha). Module-level palette comment + STOCK_BADGE_FG +
CARD_SHADOW_ALPHA_DRAG doc strings updated to match.
- card_plugin.rs: card_back_colour(0) now returns the brick-red
ACCENT_PRIMARY (was cyan). RED_SUIT_COLOUR_CBM swapped from
cyan to lime #acc267 — the CBM alternative needs to stay
hue-distinct from the new red-family primary, lime is the
next-best non-red base16-eighties accent. text_colour doc
+ CBM tests renamed cyan→lime in lockstep
(text_colour_color_blind_mode_swaps_red_suits_to_lime).
- card_face_svg.rs: BACK_ACCENTS[0] now "#a54242" (canonical
Terminal back).
- splash_plugin.rs / ui_modal.rs / replay_overlay.rs /
selection_plugin.rs: descriptive "cyan" comments swapped to
"accent" / "primary-accent" wording so the doc strings stay
decoupled from any specific hue. Future palette tweaks won't
require comment churn.
- design-system.md: YAML token frontmatter updated (primary,
surface-tint, suit-red-cb, primary-container,
on-primary-container, inverse-primary). Palette table gains
a project-specific `base08` slot for the new red. CTA /
Selection / Card-back badge / Primary button / Bottom-bar
active-icon / glow / CBM swap text all retuned. Historical
references preserved (e.g. "Was cyan #6fc2ef before the
2026-05-08 swap") so the audit trail stays in the spec.
- card_face_svg_pin.rs: rebaselined. Exactly one hash drift
(back_0 — the canonical Terminal back's badge changed
colour). Other 56 hashes identical (face SVGs don't
reference the accent; back_1..4 use unchanged accents). The
one-hash-drift signal confirms the change scope was
surgical.
Workspace clippy + cargo test --workspace clean, 1184 passing.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The user noticed the bottom-right large suit glyphs were
rendering upside-down — point-up hearts, stem-up spades — because
the SVG transform pipeline applied a `rotate(180)` to match the
traditional playing-card inverted-corner convention.
That convention exists so a card reads correctly when flipped or
read from the opposite side of the table. Single-orientation
digital play doesn't benefit from it; most modern digital decks
have abandoned it. User preference is upright.
Drops the rotate from face_svg's bottom-right `<g transform>`
and adjusts the translate so the visible glyph still lands at
(178, 286)–(242, 350) — same screen footprint, same scale, just
no flip.
design-system.md § Game Cards updated in lockstep — line 220
no longer says "rotated 180°", instead documents the deliberate
deviation from the traditional convention.
Knock-on lockstep changes in this commit:
- EXPECTED in tests/card_face_svg_pin.rs rebaselined: 52 face
hashes shift, 5 back hashes unchanged.
- assets/cards/faces/*.png regenerated (52 face PNGs).
- solitaire_engine/assets/themes/default/*_*.svg regenerated
(52 theme face SVGs that production rasterises at startup).
Workspace clippy + cargo test --workspace clean. Pin test
passes against the new hashes.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Lays out the lockstep migration from legacy white-card PNGs +
constants to the Terminal aesthetic. Steps 4 + 5 (artwork +
constant + test updates) must land in one commit so the PNG
path and the constant-fallback path don't visually diverge.
Tracks Option D from the SESSION_HANDOFF Resume prompt.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Closes the spec gap flagged after v0.20.0: the 24 mockups in
docs/ui-mockups/ are 23 mobile + 1 desktop, but desktop is still
the primary delivery surface. Stitch's variant generation kept
timing out on layout-only adaptation prompts, so the deterministic
fix is rules-based: a markdown spec that captures (a) the desktop
viewport assumptions, (b) seven universal adaptation rules that
apply to every screen, and (c) per-screen geometry rules for the
priority surfaces (Game Table, Win Summary, Settings, Help, Pause,
Home, Splash, Stats, Profile / Achievements / Theme Picker / Daily
Challenge).
Why rules > visual mockups for this gap:
- Apply uniformly to every screen — including the 9 missing-plugin
surfaces (splash, challenge, time-attack, weekly-goals, leader-
board, sync, level-up, replay-overlay, radial-menu) that have
only mobile mockups today.
- Reference-able from code comments and commit messages without
loading an image.
- Layout-agnostic by construction: tells the engine "use percent /
flex / min(720, 50%) widths" instead of pinning a specific
desktop pixel layout.
- Cheaper than re-running Stitch generation per screen, which is
flaky for layout-only adaptation work.
Cross-check confirms that v0.20.0's port (modal scaffold, toasts,
table chrome, card chrome, gameplay-feedback, splash cursor) is
already layout-agnostic — the spec gap mattered for *next* ports,
not the work that just shipped.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adds the spec the recent visual-identity port pass referenced:
- design-system.md — base16-eighties palette, type scale, spacing
scale, motion budget, component library, accessibility notes
(color-blind toggle, high-contrast mode, glyph differentiation),
and the canonical "Terminal" card-back theme.
- 24 Stitch-rendered mockups (HTML + PNG): 12 redesigned existing
screens, 1 desktop home variant, 2 onboarding steps, and 9
missing-plugin screens (splash, challenge, time-attack,
weekly-goals, leaderboard, sync, level-up, replay, radial-menu).
These mockups are the source the engine plugins were ported
against in commits 0d477ac through 9891ae4 (token system,
modal scaffold, gameplay-feedback layer, toasts, table chrome,
card chrome, splash cursor, hint highlight). Future plugin work
should diff against the matching mockup before touching pixels.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Captures the toolchain install for Debian 13 (the path Quat ran on
this dev box, including the JDK 21 / unzip / SDK-licence prompts),
the `cargo apk build` invocation, the cosmetic post-pass panic
workaround, and the table of what's wired vs. stubbed for the
android target. Runnable on a fresh box from a clone — no
machine-local context required.
Pairs with the workspace cfg-gating in fb8b2ac. Future Phase-Android
work (dirs::data_dir port, JNI ClipboardManager, Android Keystore,
gpgs) is listed as the not-yet-done section so a contributor can
pick it up without re-deriving the punch list.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The GitHub repo was renamed from Rusty_Solitare to Rusty_Solitaire
(adding the missing 'i'). The local origin remote has been updated
via `git remote set-url`; this commit updates the three doc
references that hardcoded the old URL.
SESSION_HANDOFF.md's "Canonical remote" section now names the new
URL and explains the rename for future readers, including the note
that local clone directories may still be named Rusty_Solitare —
that's a local-only name and works fine, only the GitHub repo URL
changed.
docs/SESSION_HANDOFF.md (older snapshot, unchanged otherwise) gets
its single URL line corrected.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Mirrors the move of the canonical remote from git.aleshym.co to
GitHub. The git remote itself was switched via 'git remote set-url
origin'; this updates the one stale URL in docs/SESSION_HANDOFF.md
that named the old host.
OnboardingPlugin spawns a centered welcome banner at PostStartup
when Settings.first_run_complete is false. Any key or mouse
press dismisses it, sets the flag, and persists settings.json
so returning players never see it again.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- solitaire_data::Settings { sfx_volume, first_run_complete } with
atomic JSON persistence and clamping sanitizer.
- SettingsPlugin (engine): [ / ] adjust SFX volume by 0.1, clamped;
persists on change; emits SettingsChangedEvent. No-op at rails.
- AudioPlugin applies sfx_volume to kira's main track at startup
and on every change so live tweaks take effect without restart.
- Brief "SFX: N%" toast on each change. Help cheat sheet updated.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- New MoveRejectedEvent fires from end_drag when the cursor is over
a real pile but the placement is illegal. AudioPlugin plays
card_invalid.wav on it.
- New PausePlugin + PausedResource: Esc toggles a full-window
overlay and the flag. tick_elapsed_time and advance_time_attack
skip work while paused. Help cheat sheet updated.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- New solitaire_assetgen crate with gen_sfx binary: synthesizes
five 44.1kHz mono 16-bit PCM WAVs (flip/place/deal/invalid/fanfare)
from an LCG noise source + sine/square synths. Output committed
under assets/audio/.
- AudioPlugin (engine): embeds the WAVs via include_bytes!, decodes
once with kira::StaticSoundData, plays on Draw / Move / NewGame /
GameWon events. card_invalid is loaded but unused — wiring it
needs a MoveRejectedEvent.
- AudioManager kept on the main thread (NonSend) since cpal is !Send
on some platforms; degrades gracefully if no audio device present.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- HelpPlugin: full-window cheat sheet listing every keybinding,
toggled with H or ?. Three unit tests cover open/close/slash.
- AnimationPlugin: ChallengeAdvancedEvent now surfaces as a
3-second "Challenge N cleared!" toast.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Core: GameMode::TimeAttack variant (no scoring/undo changes — session marker only)
- Engine: TimeAttackPlugin with TimeAttackResource, TimeAttackEndedEvent,
T hotkey (gated to level >= 5), auto-deal on win, summary toast
- Engine: Stats overlay (S) gains an Unlocks subsection (card backs /
backgrounds, sorted/deduped) and a live Time Attack panel while active
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Introduces the plumbing layer for Phase 3: GameStateResource wraps
solitaire_core::GameState, DragState tracks in-progress drags, and
SyncStatusResource holds runtime sync status. GamePlugin routes
Draw/Move/Undo/NewGame request events into GameState and emits
StateChangedEvent and GameWonEvent for downstream systems.
Also adds the Phase 3 implementation plan under docs/superpowers/plans/.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>