Commit Graph

993 Commits

Author SHA1 Message Date
funman300 9e4d4a6716 Merge pull request 'feat(core): Spider rules as a second card_game::Game implementation' (#157) from feat/spider-core into master
Build and Deploy / build-and-push (push) Successful in 6m3s
Web E2E / web-e2e (push) Successful in 5m7s
Web WASM Rebuild / rebuild (push) Successful in 7m10s
Test / test (push) Successful in 9m31s
2026-07-07 23:40:43 +00:00
Gitea CI 4f849a23b8 chore(web): regenerate wasm artifacts
Build and Deploy / build-and-push (push) Successful in 6m20s
Web E2E / web-e2e (push) Successful in 4m55s
2026-07-07 23:31:55 +00:00
funman300 2c2f0b592a feat(core): Spider rules as a second card_game::Game implementation
Test / test (pull_request) Successful in 7m37s
Two-deck (104-card) Spider on the upstream Stack/Pile containers,
exercising the multi-deck Card encoding for the first time:

- SpiderGame: 10-pile deal (4x6 + 6x5), build-down-any-suit,
  same-suit-run pickup, deal-10 gated on no empty pile, automatic
  K->A run removal, win at 8 runs
- SpiderSuits difficulty (1/2/4 suits over the same 104 cards);
  1- and 2-suit games contain identical Card values by construction
  (documented — engine entity mapping will need positional keys)
- Seeded deals via inline SplitMix64 + Fisher-Yates (core has no
  rand dep; Spider's seed space is deliberately self-contained)
- SpiderGameState session wrapper mirroring GameState conventions:
  Result<_, MoveError> mutations, upstream undo/score bookkeeping,
  Microsoft-style scoring (500 base, -1/move, +100/run, -1/undo)
- 17 unit tests + card-conservation/validity proptest over random
  legal walks; stacked-deal win-path test included

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 16:20:51 -07:00
funman300 8c0eb3dfab Merge pull request 'fix(engine): force full board repaint when the decor-view poller fires (#130)' (#155) from fix/130-forced-repaint into master
Build and Deploy / build-and-push (push) Successful in 2m13s
Test / test (push) Successful in 8m7s
Web WASM Rebuild / rebuild (push) Successful in 7m29s
2026-07-07 23:14:11 +00:00
funman300 a39e04329e fix(engine): force full board repaint when the decor-view poller fires (#130)
Test / test (pull_request) Successful in 12m14s
The #152 poller fixes resolution + layout on a missed fold/unfold, but
a transient clip could survive if card sprites held stale visuals after
geometry converged. Emit StateChangedEvent alongside the synthetic
WindowResized so card_plugin re-renders every sprite from scratch —
silent self-heal, no player-facing prompt.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 16:01:25 -07:00
funman300 6f27e775f2 chore(deploy): mount theme store at /data/theme_store
Points THEME_STORE_DIR at the existing solitaire-db PVC so catalog
content survives pod restarts. Scan is startup-only; restart the
deployment after adding zips.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 15:47:28 -07:00
Gitea CI 358bbc7eb5 chore(web): regenerate wasm artifacts
Build and Deploy / build-and-push (push) Successful in 6m23s
Web E2E / web-e2e (push) Successful in 3m57s
2026-07-07 21:17:01 +00:00
funman300 444f8d7e33 Merge pull request 'feat: in-game theme store — server catalog + verified downloads + install UI' (#154) from feat/theme-store into master
Build and Deploy / build-and-push (push) Successful in 6m2s
Test / test (push) Failing after 8m38s
Web E2E / web-e2e (push) Successful in 6m55s
Web WASM Rebuild / rebuild (push) Successful in 9m25s
2026-07-07 20:23:52 +00:00
funman300 a80547c514 Merge pull request 'fix: July 7 code-review remediation (M1–M3, L1–L3)' (#153) from fix/review-2026-07-07 into master
Web WASM Rebuild / rebuild (push) Has been cancelled
Build and Deploy / build-and-push (push) Successful in 5m56s
Test / test (push) Successful in 13m51s
Web E2E / web-e2e (push) Successful in 4m18s
2026-07-07 20:16:19 +00:00
funman300 d87397b382 feat: in-game theme store — server catalog + verified downloads + install UI
Test / test (pull_request) Successful in 10m15s
Phase 1+2 of the theme-store roadmap: a free catalog served by
solitaire_server and an in-app browse/install flow, making custom
themes installable on Android for the first time (the manual
drop-a-zip flow can't reach the app-private themes dir there).

- solitaire_sync: ThemeCatalogEntry/ThemeCatalogResponse wire types
  (additive module; SyncPayload and SyncProvider untouched)
- solitaire_server: THEME_STORE_DIR scan at startup (meta-only
  theme.ron parse, sha256, 20 MiB cap, best-effort per archive);
  public GET /api/themes, /api/themes/{id}/download, /{id}/preview;
  compose volume + README_SERVER docs
- solitaire_data: ThemeStoreClient — catalog fetch + download with
  mandatory size/sha256 verification before bytes are released
- solitaire_engine: ThemeStorePlugin — 'Browse theme store' button in
  Settings → Cosmetic, modal catalog (leaderboard-style rebuild),
  download on AsyncComputeTaskPool, atomic .tmp+rename write into
  user_theme_dir, then the existing hardened import_theme pipeline and
  an in-place registry refresh

New deps: sha2 (workspace, server+data); ron/zip reused in server;
serde_json added to solitaire_sync dev-deps for DTO round-trip tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 13:12:41 -07:00
funman300 018b69285d fix(engine): unify light/dark theme manifest URL resolution
Test / test (pull_request) Successful in 13m4s
- set_theme resolved every id to themes:// — the bundled dark/classic
  themes live at embedded://, so switching to them via the public API
  would NotFound and silently keep the old theme. All three load paths
  now share one theme_manifest_url resolver.
- load_initial_theme's settings-absent fallback was "classic", a
  leftover from v0.33 when classic was the default; it now derives from
  Settings::default() so it can't drift from the data crate again, with
  a test pinning the default id to a bundled theme.
- settings.rs doc no longer claims "classic" is migrated to "dark";
  only the pre-rename "default" id is rewritten.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 12:25:35 -07:00
Gitea CI a4ad848c93 chore(web): regenerate wasm artifacts
Build and Deploy / build-and-push (push) Successful in 5m28s
Web E2E / web-e2e (push) Successful in 4m12s
2026-07-07 18:54:30 +00:00
funman300 ff8c00d2f4 Merge pull request 'fix(engine): poll decor-view size to catch fold resizes winit misses (#130)' (#152) from fix/130-fold-stale-relayout into master
Build and Deploy / build-and-push (push) Successful in 1m56s
Test / test (push) Failing after 16m17s
Web WASM Rebuild / rebuild (push) Successful in 8m23s
Android Release / build-apk (push) Successful in 4m19s
v0.42.1
2026-07-07 18:12:57 +00:00
funman300 abf1312cf5 fix(data): serialise token refreshes so overlapping 401s can't force a logout
Test / test (pull_request) Successful in 15m33s
Refresh tokens are single-use since the rotation change (PR #136). Two
in-flight requests hitting 401 together (replay upload racing a manual
sync) both called /api/auth/refresh; the second presented an already-
consumed token, got rejected, and surfaced as a spurious 'session
expired' re-login prompt.

refresh_token() now takes the stale access token that earned the 401
and runs behind a tokio::sync::Mutex; a loser of the lock race that
finds the stored token already rotated returns Ok and retries with it
instead of spending the new refresh token.

Untested caveat: exercising the race in a unit test needs an in-memory
auth_tokens double (the real keyring is unavailable on headless Linux
runners) — noted for a future test-support addition.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 11:04:30 -07:00
funman300 8b09c51271 docs(architecture): correct KlondikePile sketch — upstream has no Waste variant
§8 documented a Waste variant that doesn't exist in klondike 0.4; the
real convention (Stock denotes the waste pile in pile-coordinate space)
is now stated where the enum is sketched.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 11:01:42 -07:00
funman300 f61573513c fix(engine): remove latent unreachable! panic in difficulty seed cursor
The Random arm was only unreachable because seeds_for(Random) returns
None in a different function — a future catalog change would turn it
into a shipped runtime panic. Returning a system-time seed is the
correct Random behaviour either way. CLAUDE.md §2.3.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 11:01:25 -07:00
funman300 757c35e4a0 fix(engine): degrade gracefully when the platform data dir is missing
user_theme_dir() panicked on desktop when dirs::data_dir() returned
None (broken $HOME / $XDG_*). Return an empty path instead — exactly
what the wasm32 branch already did — so theme discovery reports 'no
user themes' and the bundled default keeps working. Warns once with
the set_user_theme_dir() workaround. CLAUDE.md §2.3: no panic! in
runtime logic.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 11:00:20 -07:00
funman300 113a933170 style: cargo fmt under rustfmt 1.9 and gate formatting in CI
The repo was formatted under an older stable; rustfmt 1.9 (Rust 1.95)
wraps signatures and call sites differently, so every touched file was
picking up unrelated formatting hunks. One mechanical pass, and a
'cargo fmt --check' step in the test workflow (same pinned 1.95.0
toolchain) so drift can't accumulate again.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 10:59:20 -07:00
funman300 18bb1fa0be fix(ci): cap test-build debuginfo so the linker fits runner memory
Two runs (447, 486) died with 'ld terminated with signal 9' linking the
solitaire_engine test binary — the runner OOMs on full dev debuginfo for
the Bevy dependency graph. line-tables-only preserves file:line panic
backtraces at a fraction of the link footprint.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 10:58:43 -07:00
funman300 38b81a4004 fix(engine): poll decor-view size to catch fold resizes winit misses
Test / test (pull_request) Successful in 23m3s
winit's Android backend does not forward content-rect changes that happen
while backgrounded (open TODOs in winit 0.30 logged on every resume), so a
fold/unfold cycle can leave Bevy rendering and laying out for the previous
screen: tableau clipped off the left edge, bottom third empty (#130).

Add a continuous decor-view size poller (JNI, every 30 frames) that, on
mismatch with the cached Window resolution: writes the real physical size
into window.resolution, emits a synthetic WindowResized in logical pixels,
and re-arms the safe-area inset poller — covering both the fold-size and
inset-staleness cases in one mechanism, without relying on
AppLifecycle::WillResume being delivered (it may never be; a new evidence
log in rearm_on_resumed settles that question on-device).

Closes #130

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 10:49:17 -07:00
Gitea CI ae7af9adf4 chore(web): regenerate wasm artifacts
Build and Deploy / build-and-push (push) Successful in 7m12s
Web E2E / web-e2e (push) Successful in 5m16s
2026-07-07 09:12:14 +00:00
funman300 c0cd7c2c15 Merge pull request 'docs(changelog): cut v0.42.0 section' (#151) from docs/changelog-v0.42.0 into master
Android Release / build-apk (push) Successful in 5m20s
v0.42.0
2026-07-07 04:37:30 +00:00
funman300 ac002d8255 docs(changelog): cut v0.42.0 section
Test / test (pull_request) Successful in 24m8s
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 21:37:29 -07:00
funman300 0fc1fa139e Merge pull request 'docs(changelog): record the July 6 evening arc' (#150) from docs/changelog-evening-arc into master 2026-07-07 04:14:51 +00:00
funman300 4f0c5bb808 docs(changelog): record the July 6 evening arc
Test / test (pull_request) Failing after 15m12s
CI gate, ambiguity gate + same-day 302-to-zero burn-down, 36% smaller
browser canvas, Quaternions API adoption, sync/auth concurrency fixes,
bounded exit push, server auth hardening.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 21:14:50 -07:00
funman300 a6b22df666 Merge pull request 'refactor(engine): ambiguity burn-down batch 4 — ZERO ambiguities, gate enforced' (#149) from refactor/ambiguity-tail into master
Build and Deploy / build-and-push (push) Successful in 2m18s
Test / test (push) Failing after 13m51s
Web WASM Rebuild / rebuild (push) Successful in 8m37s
2026-07-07 04:14:00 +00:00
funman300 b402c01918 refactor(engine): ambiguity burn-down batch 4 — zero ambiguities, gate enforced
Test / test (pull_request) Failing after 17m5s
Clears the final 47 pairs and turns the ratchet into a hard gate
(AMBIGUITY_BASELINE = 0, assert_eq):

- HudButtons: the 14 HUD button/popover handlers run as one chain,
  before ui_focus::FocusKeys — Esc/keyboard consumption order is now
  defined (restore prompt → buttons/popovers → focus navigation →
  settings toggle) instead of scheduler-dependent.
- HUD text updaters (update_hud, update_selection_hud,
  update_won_previously) chained, in UiTextFx, after the new
  AutoComplete set (update_hud reads AutoCompleteState).
- restore_hud_on_modal → apply_hud_visibility chained before
  UpdateOnResize: HudVisibility writes, application, and the layout
  read happen in a fixed order.
- New writer sets UndoRequestWriters / InfoToastWriters (same
  self-ambiguous pattern as NewGameRequestWriters).
- Logic-before-paint: check_no_moves and the AutoComplete chain order
  before BoardVisuals; SettingsMutation after UpdateOnResize.
- MarkerVisuals set wraps the table painter chain; chrome fx declare
  disjointness from it.
- update_hud_typography after BoardVisuals; avatar/settings-toggle
  interaction handlers declared disjoint from HudButtons.

302 → 198 → 171 → 47 → 0 in four batches, one day. New systems now
fail CI unless they declare their ordering or their disjointness.

Closes #143

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 21:13:58 -07:00
funman300 be478acde7 Merge pull request 'refactor(engine): ambiguity burn-down batch 3 — board paint chain (171 → 47)' (#148) from refactor/ambiguity-board-visuals into master
Web WASM Rebuild / rebuild (push) Has been cancelled
Build and Deploy / build-and-push (push) Successful in 2m20s
Test / test (push) Successful in 25m2s
2026-07-07 03:57:20 +00:00
funman300 379873765d refactor(engine): ambiguity burn-down batch 3 — board paint chain, 171 → 47
Test / test (pull_request) Failing after 17m32s
The Sprite/Transform cluster (112 pairs) was every board painter
racing every other one. Two structural moves:

- BoardVisuals set (card_plugin): all card/marker painters run as ONE
  deterministic chain in data-flow order — layout refinement → card
  authority (sync_cards_on_change) → flip anims → shadows → highlights
  → stock indicators → resize snapping → corner labels — with
  LayoutSystem::UpdateOnResize ordered before the whole set and the
  table plugin's marker painters chained after it. Paint order is now
  identical every frame instead of scheduler-dependent.

- UiTextFx set (ui_theme): chrome text effects (score pulse/floater,
  streak flourish, modal enter, focus-ring pulse) animate Transform on
  UI entities only; they are declared ambiguous with BoardVisuals and
  with each other — the entity domains are disjoint by construction.

All chain members are cheap and change-gated; sequential execution is
not a measurable cost for a card game. Existing ordering constraints
(fan-frac before sync, shadows after sync, snap after collect) are
preserved inside the chain.

Baseline ratchets 171 → 47. Remaining: long tail of small subjects
(input, event writers, AutoCompleteState, HudVisibility).

Refs #143

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 20:57:15 -07:00
funman300 713a292057 Merge pull request 'refactor(engine): ambiguity burn-down batch 2 — SettingsResource cleared (198 → 171)' (#147) from refactor/ambiguity-settings into master
Web WASM Rebuild / rebuild (push) Has been cancelled
Build and Deploy / build-and-push (push) Successful in 2m17s
Test / test (push) Successful in 19m18s
2026-07-07 03:48:11 +00:00
funman300 58c2dfd0a9 refactor(engine): ambiguity burn-down batch 2 — SettingsResource cleared, 198 → 171
Test / test (pull_request) Successful in 28m7s
New SettingsMutation set: the per-frame settings mutators
(handle_volume_keys → record_window_geometry_changes →
persist_window_geometry_after_debounce) run as a deterministic chain
ordered before GameMutation, so every reader already after GameMutation
observes the current frame's settings transitively. The four readers
outside that ordering (modal enter-speed chain, focus-ring pulse, HUD
avatar, and the game plugin's pre-mutation chain) are ordered after the
set explicitly.

SettingsResource ambiguities: 21 → 0. Baseline ratchets 198 → 171.

Refs #143

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 20:48:08 -07:00
funman300 710555bd7e Merge pull request 'refactor(engine): first ambiguity burn-down batch — 302 → 198 pairs' (#146) from refactor/ambiguity-burndown into master
Web WASM Rebuild / rebuild (push) Has been cancelled
Build and Deploy / build-and-push (push) Successful in 1m53s
Test / test (push) Successful in 20m32s
2026-07-07 03:37:53 +00:00
funman300 42a5f3bc3b refactor(engine): first ambiguity burn-down batch — 302 → 198 pairs
Test / test (pull_request) Failing after 15m16s
Two structural fixes from the #143 backlog:

- Game-state ordering spine: the three pre-mutation GameStateResource
  writers (tick_elapsed_time, sync_settings_to_game,
  handle_restore_prompt) are now a deterministic chain before
  GameMutation, and the remaining unordered readers
  (update_selection_hud, handle_hint_button, tick_hint_highlight,
  handle_right_click, snap_cards_on_window_resize,
  sync_pile_marker_visibility, auto_save_game_state) are ordered after
  it. Readers now see the current frame's moves deterministically
  instead of racing the mutators.

- NewGameRequestWriters set: every in-cluster writer of
  NewGameRequestEvent (buttons, modals, mode picker, seed poller,
  restore prompt) is registered in a shared set marked ambiguous with
  itself — writer-vs-writer append order is meaningless since consumers
  drain the whole queue. Out-of-cluster writers (home, challenge,
  time-attack, win-summary, play-by-seed, difficulty, stats plugins)
  can join the set when the test cluster grows.

AMBIGUITY_BASELINE ratchets 302 → 198. Remaining backlog is dominated
by the Sprite (72) / Transform (48) visual-domain cluster, which needs
per-domain set architecture — next batch.

Refs #143

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 20:37:51 -07:00
funman300 19647b5209 Merge pull request 'test(engine): ratchet on Bevy system-order ambiguities' (#145) from test/schedule-ambiguity into master
Web WASM Rebuild / rebuild (push) Has been cancelled
Build and Deploy / build-and-push (push) Successful in 2m6s
Test / test (push) Successful in 19m47s
2026-07-07 03:22:29 +00:00
funman300 d8a255869c test(engine): ratchet on Bevy system-order ambiguities
Test / test (pull_request) Failing after 16m20s
First measurement of schedule hygiene (issue #143): the headless
gameplay cluster (Game/Table/Card/Hud/AutoComplete/UiModal/UiFocus/
Settings) carries 302 system pairs with conflicting data access and no
ordering edge. Too many to triage in one pass and most are likely
benign event/resource writers — but unproven, and nothing stopped the
count from growing.

New schedule_checks test builds the cluster with ambiguity detection
promoted to error, parses the reported pair count, and asserts it never
exceeds AMBIGUITY_BASELINE (302). New ambiguous pairs now fail CI at
the PR; the legacy backlog can be burned down incrementally by adding
.before/.after or .ambiguous_with and lowering the baseline.

Refs #143

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 20:22:27 -07:00
funman300 f0336d784d Merge pull request 'fix(server): login timing pad, 409 on register race, avatar magic-byte check' (#144) from fix/server-low-findings into master
Build and Deploy / build-and-push (push) Successful in 6m5s
Test / test (push) Failing after 16m42s
Web E2E / web-e2e (push) Successful in 4m58s
2026-07-07 03:15:03 +00:00
funman300 a218999243 fix(server): login timing pad, 409 on register race, avatar magic-byte check
Test / test (pull_request) Failing after 17m26s
Closes the three actionable server Low findings from the 2026-07-06
review:

- #139: unknown-username logins now verify against a static bcrypt
  dummy hash so both failure paths pay the same cost — response timing
  no longer reveals which usernames exist.
- #140: register maps a unique-constraint violation to UsernameTaken
  (409) — the SELECT pre-check stays as the friendly fast path, the
  constraint is the arbiter for the concurrent case.
- #141: avatar uploads must start with the magic bytes of the declared
  image type; the stored extension (which decides how the file is
  re-served) is now backed by content, not the Content-Type header.

Unit tests: real/spoofed/truncated signatures for all four formats,
and the dummy hash's validity at BCRYPT_COST.

Closes #139
Closes #140
Closes #141

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 20:15:01 -07:00
funman300 55fa7df2bf Merge pull request 'fix(engine): bounded blocking sync push on exit' (#138) from fix/exit-push-bounded into master
Web WASM Rebuild / rebuild (push) Has been cancelled
Build and Deploy / build-and-push (push) Successful in 2m5s
Test / test (push) Successful in 19m38s
2026-07-07 03:05:16 +00:00
funman300 b2341c652b Merge pull request 'refactor(core,engine,wasm): canonical FOUNDATIONS/TABLEAUS consts; adopt upstream SUITS/RANKS' (#137) from refactor/quat-enum-consts into master
Web WASM Rebuild / rebuild (push) Has been cancelled
Build and Deploy / build-and-push (push) Successful in 6m3s
Test / test (push) Successful in 26m20s
Web E2E / web-e2e (push) Successful in 4m56s
2026-07-07 03:05:11 +00:00
funman300 021c5d6ad8 Merge pull request 'fix(server): transactional sync push; single-use refresh rotation' (#136) from fix/sync-auth-races into master
Build and Deploy / build-and-push (push) Successful in 5m24s
Test / test (push) Successful in 17m41s
Web E2E / web-e2e (push) Successful in 4m41s
2026-07-07 03:04:41 +00:00
funman300 d1264a7797 fix(engine): bounded blocking sync push on exit
Test / test (pull_request) Successful in 24m6s
The exit push spawned a detached task on AppExit, which process
teardown almost always killed before the network round-trip completed —
the final session's sync silently never happened (2026-07-06 review,
finding M3). Local persistence meant no data loss, but stats stayed
unsynced until the next launch.

push_on_exit now blocks the closing app's final frame for at most
EXIT_PUSH_TIMEOUT (2s) via tokio::time::timeout: long enough for one
healthy round-trip, short enough that quitting never feels hung when
the server is unreachable. Timeout and errors are logged and skipped —
the next launch's pull/push converges as before.

Closes review finding M3.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 20:03:41 -07:00
funman300 7a5f03987d refactor(core,engine,wasm): canonical FOUNDATIONS/TABLEAUS consts; adopt upstream SUITS/RANKS
Review findings 1+2 (Quat-underuse lens, 2026-07-06):

- solitaire_core gains pub const FOUNDATIONS / TABLEAUS — the canonical
  iteration source for the upstream pile enums (upstream klondike has no
  Foundation::ALL, and inherent impls cannot be added to foreign types).
  Deletes three identical private const-fn copies (radial_menu,
  table_plugin, input_plugin) and the hand-enumerated variants in
  card_plugin::sync::all_cards and solitaire_wasm.

- Hand-rolled [Suit; 4] / [Rank; 13] arrays replaced with upstream
  Suit::SUITS / Rank::RANKS. The order-sensitive CardImageSet indexing
  is re-keyed through canonical card_plugin::{suit_index, rank_index}
  helpers that match upstream order, with regression tests asserting
  the correspondence — one ordering everywhere instead of three
  divergent local ones.

Net -177 lines. No behaviour change; all consumers go through the
canonical helpers.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 19:58:33 -07:00
funman300 8eb316751d fix(server): transactional sync push; single-use refresh rotation
Two concurrency fixes from the 2026-07-06 review (findings M1, M2):

sync push (M1): the load→merge→store cycle ran as three separate DB
operations. Two devices pushing concurrently both read the same stored
payload, merged independently, and the second store overwrote the first
merge — the server visibly regressed until the losing device pushed
again. The whole cycle (including the leaderboard update) now runs in
one transaction; SQLite serialises the writers. The leaderboard
helper's stale docstring (claiming a single conditional UPDATE that was
actually two statements) is corrected — the transaction now provides
the atomicity it described.

refresh rotation (M2): SELECT-then-DELETE let two concurrent refreshes
with the same token both pass the liveness check and both mint fresh
token pairs. The SELECT is gone; rotation now gates on the DELETE's
rows_affected — whoever removes the jti row wins, everyone else gets
401. Sequential reuse was already covered by
consumed_refresh_token_is_rejected, which still passes unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 18:00:00 -07:00
funman300 d0c1db6c1d Merge pull request 'ci: add workspace clippy + test gate workflow' (#135) from ci/test-workflow into master
Test / test (push) Successful in 28m59s
2026-07-06 23:29:10 +00:00
funman300 f6e57b759e ci: pin toolchain to 1.95.0 and install Bevy native deps
Test / test (pull_request) Successful in 27m47s
Review feedback on #135: floating 'stable' + -D warnings lets every new
clippy release redden master with new lints — pin 1.95.0 like the
web-wasm-rebuild workflow. And ubuntu-latest lacks the ALSA/udev/X11/
Wayland dev packages the Bevy crates link against; install the standard
Bevy CI set (the project's own builder images have no desktop-Bevy
precedent — android-builder targets the NDK and web-e2e only builds the
server).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 16:28:28 -07:00
funman300 c9adcaa5e4 ci: add workspace clippy + test gate workflow
Test / test (pull_request) Failing after 1m35s
Until now no CI workflow ran the test suite or clippy at all — the
android-release, docker-build, web-e2e, web-wasm-rebuild, and
builder-image workflows cover packaging and e2e, but a direct push to
master (including the web-wasm-rebuild bot commit) never executed
cargo test or clippy. The gate discipline in CLAUDE.md §6 existed only
on developer machines.

test.yml runs the exact §6 commands (clippy --workspace --all-targets
-D warnings; cargo test --workspace) on master pushes and PRs, with
SQLX_OFFLINE against the checked-in .sqlx cache, path-filtered so
docs-only merges don't burn CI. Toolchain/cache mirror web-e2e.yml.

Found during the 2026-07-06 large-scale review (finding H1).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 16:21:19 -07:00
Gitea CI 0d5204b5ec chore(web): regenerate wasm artifacts
Build and Deploy / build-and-push (push) Successful in 6m30s
Web E2E / web-e2e (push) Successful in 4m38s
2026-07-06 23:05:53 +00:00
funman300 15bb136c79 Merge pull request 'perf(web): size-focused wasm-release profile for the canvas build (36.2 → 23.2 MB)' (#134) from perf/wasm-size-profile into master
Build and Deploy / build-and-push (push) Successful in 7m4s
Web E2E / web-e2e (push) Successful in 4m51s
Web WASM Rebuild / rebuild (push) Successful in 9m30s
2026-07-06 22:44:33 +00:00
funman300 16a1139eab perf(web): size-focused wasm-release profile for the canvas build
canvas_bg.wasm shipped at 36.2 MB — plain release (opt-level 3, thin
LTO) piped through wasm-opt -O2. Download size, not throughput, is the
binding constraint for the browser canvas, so solitaire_web now builds
with a dedicated wasm-release profile: opt-level "s", fat LTO, one
codegen unit. Local result: 23.2 MB after the unchanged wasm-opt -O2
pass — 35.9% smaller.

The binaryen pass stays at -O2 (the -Oz grey-screen miscompile note in
build_wasm.sh still applies). profile.strip is deliberately NOT set: on
wasm it also removes the target_features custom section, which makes
wasm-opt reject the module ('all used features should be allowed' on
trunc_sat).

Verified with the new artifact: all 5 play_canvas e2e specs pass (debug
bridge, draw3 param, apply/undo, replay diagnostics, 40-seed autoplay
invariants). Headless pixel verification is inconclusive in this
environment — wgpu cannot create a usable adapter locally and panics
identically on the OLD artifact too — so visual parity should be
confirmed against production after the next deploy.

pkg/ artifacts are intentionally not committed: the web-wasm-rebuild
workflow is the single source of truth and will regenerate them with
this profile on merge.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 15:44:16 -07:00
funman300 710cabcf0d Merge pull request 'docs(architecture): bring source-of-truth docs to post-migration reality' (#133) from docs/architecture-post-migration into master 2026-07-06 22:26:18 +00:00