754 Commits

Author SHA1 Message Date
funman300 4e480d7cb5 ci: scope server workflow to server paths; harden deploy push
Build and Deploy / build-and-push (push) Failing after 25s
Switch docker-build.yml from paths-ignore to an explicit paths allowlist
so the workflow only fires on changes to solitaire_server/, solitaire_sync/,
solitaire_core/, Cargo.toml, Cargo.lock, or the workflow file itself.

Also harden the "commit and push updated kustomization" step:
- exit 0 early when the kustomization has no staged diff (nothing to push)
- retry the pull+push loop up to 3 times with a 5 s delay to handle
  concurrent pushes that race the CI commit

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 18:55:43 -07:00
Gitea CI 42a0a0bb8a chore(deploy): bump image to ca5d8a9c [skip ci] 2026-05-15 01:53:50 +00:00
funman300 ca5d8a9c55 fix(engine): silence Android-target dead-code and unused-import warnings
Build and Deploy / build-and-push (push) Successful in 34s
All 10 warnings were caused by hotkey/keyboard UI code behind
#[cfg(not(target_os = "android"))] call sites whose definitions lacked
the matching gate. Fixes:
- help_plugin: gate keyboard-chip imports and font_kbd; #[allow(dead_code)]
  on ControlRow (keys field is data, not dead)
- hud_plugin/ui_modal: replace cfg shadow pattern with cfg!() expression
  so the hotkey parameter is read on every platform
- home_plugin: gate fn hotkey behind not(android)
- onboarding_plugin: gate HotkeyRow, HOTKEYS, spawn_slide_hotkeys and
  their exclusive imports behind not(android)
- replay_overlay: gate keybind_footer_hint_text behind not(android)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 18:53:24 -07:00
Gitea CI 48befd7e9b chore(deploy): bump image to 5559f326 [skip ci] 2026-05-15 01:16:54 +00:00
funman300 51fecb24b0 chore: remove stale docs, mockups, and one-off artifacts
Build and Deploy / build-and-push (push) Failing after 43s
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>
v0.26.0
2026-05-14 18:15:11 -07:00
funman300 5559f32672 feat(web): smart-move on double-click and right-click
Build and Deploy / build-and-push (push) Successful in 3m55s
Double-clicking or right-clicking a face-up card now auto-places it to
the best valid pile (foundation preferred for single cards, tableau
otherwise). Right-click also suppresses the browser context menu.
Theme button re-render now calls game.state() instead of reusing snap.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 18:13:00 -07:00
Gitea CI 17c320c08f chore(deploy): bump image to c35c045f [skip ci] 2026-05-15 01:02:19 +00:00
funman300 c35c045f08 fix(core): enable take-from-foundation by default (closes #3)
Build and Deploy / build-and-push (push) Successful in 4m55s
Standard Klondike allows returning the top card of a foundation pile to a
compatible tableau column. The flag was previously off by default, making the
move impossible for all players.

Changes:
- GameState::new_with_mode: take_from_foundation now initialises to true
- Settings: default changed to true; custom serde default function ensures
  older settings.json files without the key also resolve to true
- Tests: rename "blocked_by_default" → "allowed_by_default" (asserts the
  move now succeeds); add "blocked_when_disabled" to cover the flag=false path

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 17:57:25 -07:00
Gitea CI 8fe0891866 chore(deploy): bump image to 677999a5 [skip ci] 2026-05-15 00:31:41 +00:00
funman300 677999a51e feat(engine): wire avatar download and display into profile modal
Build and Deploy / build-and-push (push) Successful in 4m15s
- Add avatar_plugin: AvatarPlugin, AvatarResource, AvatarFetchEvent
  - After AvatarFetchEvent fires, spawns an async reqwest download task
  - On completion, decodes image bytes via image::load_from_memory →
    Image::from_dynamic and inserts into Assets<Image>
- Expand auth task to also call fetch_me_with_token immediately after
  login/register so avatar_url is available without a second round-trip
- poll_auth_task fires AvatarFetchEvent when avatar_url is Some, building
  the full URL from base_url + relative avatar path
- Profile modal shows 48px circular avatar ImageNode when AvatarResource
  is populated, or an initials disc (first letter of username) as fallback
- Add image = "0.25" and reqwest to solitaire_engine deps
- Add fetch_me_with_token helper to SolitaireServerClient for use when
  the access token hasn't been persisted to keychain yet

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 17:27:27 -07:00
Gitea CI 7177f0eb1b chore(deploy): bump image to 407cae20 [skip ci] 2026-05-15 00:19:45 +00:00
funman300 407cae2040 feat(auth): add /api/me endpoint, avatar upload, and profile picture support
Build and Deploy / build-and-push (push) Successful in 5m7s
- Add migration 005: nullable avatar_url column on users table
- Add GET /api/me: returns id, username, avatar_url from DB (fixes UUID-on-profile bug)
- Add PUT /api/me/avatar: accepts raw image bytes (≤1 MB, jpeg/png/webp/gif),
  writes to avatars/ dir, updates avatar_url in DB
- Serve /avatars via ServeDir so uploaded images are publicly accessible
- Update account.html: fetch username from /api/me instead of parsing JWT;
  add circular avatar display with initials fallback and click-to-upload
- Add SolitaireServerClient::fetch_me() for desktop/Android profile display
- Add avatar_url field to SyncBackend::SolitaireServer settings (serde default None)
- Update sqlx offline query cache for new avatar_url queries

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 17:14:42 -07:00
Gitea CI eb906fe968 chore(deploy): bump image to 8d31a37a [skip ci] 2026-05-14 23:59:49 +00:00
funman300 8d31a37a39 feat(web): add classic/dark card theme picker
Build and Deploy / build-and-push (push) Successful in 4m10s
- Reorganise card PNGs into assets/cards/faces/{classic,dark}/ and
  assets/cards/backs/{classic,dark}/
- Rasterise dark SVG theme alongside existing classic set
- Add "Dark / Classic" toggle button in the game HUD; persists to
  localStorage as fs_theme (defaults to classic)
- Preload both themes on page load so switching is instant

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 16:55:43 -07:00
Gitea CI 2bf990388b chore(deploy): bump image to 7238ef22 [skip ci] 2026-05-14 23:51:51 +00:00
funman300 7238ef225e feat(web): replace dark-theme card PNGs with classic (white) theme
Build and Deploy / build-and-push (push) Successful in 26s
Rasterized all 52 classic SVGs via rsvg-convert at 256×384. The web
game was showing dark-background cards; it now shows the traditional
white card face style.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 16:51:27 -07:00
funman300 b984161d46 ci: remove android build and release workflows
Build and Deploy / build-and-push (push) Has been cancelled
Building locally or via a different pipeline; the self-hosted runner's
resource constraints made the 3-ABI release build unreliable.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 16:41:01 -07:00
funman300 c69d732d5a ci(android): add disk-space diagnostic step before release build
Android Build / build-apk (push) Has been cancelled
Build and Deploy / build-and-push (push) Has been cancelled
Helps diagnose whether the 3-ABI release build is hitting disk limits.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 16:39:24 -07:00
Gitea CI 5e0e8d000b chore(deploy): bump image to 27eed989 [skip ci] 2026-05-14 23:21:28 +00:00
funman300 27eed98922 ci(android): remove workflow_dispatch — unsupported in this Gitea version
Android Build / build-apk (push) Successful in 12m3s
Build and Deploy / build-and-push (push) Successful in 41s
Android Release / build-release-apk (push) Has been cancelled
workflow_dispatch caused the entire android-release workflow to be silently
dropped; tag-triggered releases (v0.25.6) worked fine before this trigger
was added. Removing it restores tag-based release triggering.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
v0.25.7 v0.25.8
2026-05-14 16:08:40 -07:00
funman300 f304917d62 ci(android): add workflow_dispatch trigger to release workflow
Android Build / build-apk (push) Successful in 13m48s
Build and Deploy / build-and-push (push) Failing after 53s
Android Release / build-release-apk (push) Has been cancelled
Allows manually triggering the release build from the Gitea UI
(Actions tab → Android Release → Run workflow) without needing
to push a version tag.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 15:44:24 -07:00
funman300 d49c478efa fix(ci): diagnose KEYSTORE_BASE64 decode failure
Android Build / build-apk (push) Has been cancelled
Build and Deploy / build-and-push (push) Has been cancelled
Android Release / build-release-apk (push) Has been cancelled
base64 -d is failing (1s step, keystore 0 bytes). Add diagnostics:
- Print secret length to check for truncation
- Capture base64 error output
- Print exit code and file size
- Use --decode instead of -d for clarity

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
v0.25.5 v0.25.6
2026-05-14 15:37:17 -07:00
Gitea CI 29f9b9358e chore(deploy): bump image to 9ef5759f [skip ci] 2026-05-14 22:27:55 +00:00
funman300 9ef5759f40 fix(ci): fail fast on empty keystore before 7-min cargo build
Android Build / build-apk (push) Successful in 14m52s
Build and Deploy / build-and-push (push) Successful in 44s
Android Release / build-release-apk (push) Failing after 3m42s
If KEYSTORE_BASE64 is unset, base64 -d writes an empty file silently,
cargo ndk then spends ~7 min compiling all ABIs, and only then does
apksigner fail. Add a size check after decode so the job fails in
seconds with a clear error message instead of wasting a full build.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
v0.25.3 v0.25.4
2026-05-14 15:12:25 -07:00
funman300 9c9c0c76d3 fix(ci): restore 3-ABI release build now that LXC has 106 GB disk
Android Build / build-apk (push) Successful in 20m40s
Build and Deploy / build-and-push (push) Failing after 50s
Android Release / build-release-apk (push) Failing after 10m29s
The runner LXC was bumped from ~56 GB to 106 GB, giving ~70 GB of free
space — well above the ~40 GB a full 3-ABI release build needs. Revert
the disk-budget workarounds added in ab35fcf:

- Remove "Free disk space" step (no longer needed)
- Restore x86_64 target (arm64-v8a + armeabi-v7a + x86_64)
- Remove ABIS override so build script uses its full default set

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
v0.25.2
2026-05-14 14:04:09 -07:00
Gitea CI d4fb9e36a8 chore(deploy): bump image to 32991301 [skip ci] 2026-05-14 21:00:48 +00:00
funman300 ab35fcf906 fix(ci): free disk space + drop x86_64 from release build to fix OOM
Android Build / build-apk (push) Successful in 16m44s
Build and Deploy / build-and-push (push) Failing after 30s
Android Release / build-release-apk (push) Failing after 12m5s
Run 181 (v0.25.0 tag) failed at "Build signed release APK" after ~7 min —
same disk-exhaustion pattern that hit the debug build. The debug workflow
was already fixed to arm64-v8a only; the release workflow still built all 3
ABIs and exceeded the runner's disk budget.

Changes:
- Add "Free disk space" step before system deps: removes /usr/local/lib/android,
  /usr/share/dotnet, /opt/ghc, /usr/local/share/boost (~10 GB reclaimed).
- Limit ABIS to arm64-v8a + armeabi-v7a (drops x86_64, which is emulator-only).
- Remove x86_64 from rustup target add to match.

arm64-v8a covers all modern Android devices; armeabi-v7a covers legacy ARM.
x86_64 can be re-added later if a simulator-targeted test build is needed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
v0.25.1
2026-05-14 13:53:48 -07:00
funman300 32991301dd fix(engine): restore Dark as default theme; migrate stale theme IDs
Android Build / build-apk (push) Successful in 12m19s
Build and Deploy / build-and-push (push) Successful in 55s
- default_theme_id() returns "dark" (was briefly "classic" after the
  rename commit 20b7a61)
- sanitized() migrates "default" and "classic" → "dark" so existing
  settings.json files are upgraded automatically on next launch
- Registry lists Dark first so the Settings picker opens with it at top
- Classic remains available as an option in the picker

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 13:47:43 -07:00
Gitea CI c5fd928dcb chore(deploy): bump image to f6907671 [skip ci] 2026-05-14 20:20:49 +00:00
funman300 f6907671be fix(ci): pin upload-artifact to v3 for Gitea Actions compatibility
Android Build / build-apk (push) Successful in 12m49s
Build and Deploy / build-and-push (push) Successful in 47s
Android Release / build-release-apk (push) Failing after 10m11s
The disk-budget fix worked — debug APK now builds, signs, and verifies
in ~6 minutes on a single ABI. But the upload step failed with:

  GHESNotSupportedError: @actions/artifact v2.0.0+, upload-artifact@v4+
  and download-artifact@v4+ are not currently supported on GHES.

upload-artifact@v4 rewrote the upload path to use a new artifact
service hosted on github.com; Gitea's GHES-compatibility layer doesn't
implement that endpoint. v3 still uses the older chunked HTTP upload
API that Gitea supports.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
v0.25.0
2026-05-14 13:07:09 -07:00
Gitea CI a54fff7257 chore(deploy): bump image to 533bcec2 [skip ci] 2026-05-14 20:01:12 +00:00
funman300 533bcec2d8 fix(ci): limit debug APK to arm64-v8a so apksigner has disk to write
Android Build / build-apk (push) Failing after 9m10s
Build and Deploy / build-and-push (push) Successful in 52s
The previous run got all the way through compile + link + zipalign and
then died inside apksigner with `IOException: No space left on device`.
Cross-compiling all three Android ABIs (arm64-v8a, armeabi-v7a, x86_64)
in debug mode blows target/ past 25 GB, and by the time apksigner is
streaming the signed APK to disk the runner has nothing left.

Two changes:

  1. build_android_apk.sh now reads `ABIS` from the environment (defaults
     to all three for backwards compat) and uses it to assemble the
     cargo-ndk `-t` flags.
  2. android-build.yml passes ABIS=arm64-v8a, since the debug artifact
     is consumed by adb-installing to a single arm64 device and the
     other two ABIs were dead weight.

Also frees \$STAGING/app-unsigned.apk right after zipalign so it's not
sitting next to the aligned APK and the output APK during signing.

Release workflow is untouched — release APKs still ship all three ABIs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 12:51:17 -07:00
Gitea CI ba786f5a09 chore(deploy): bump image to 7ee7cb6d [skip ci] 2026-05-14 19:17:40 +00:00
funman300 7ee7cb6d93 ci(android): replace cargo-apk with cargo-ndk + manual APK assembly
Android Build / build-apk (push) Failing after 23m0s
Build and Deploy / build-and-push (push) Successful in 43s
cargo-apk 0.10 and its fork cargo-apk2 both failed to discover the
installed Android platform in this Gitea runner, despite ANDROID_HOME,
platforms;android-34, build-tools, and NDK all being present, readable,
and pointed at correctly. We never isolated whether the bug is in the
shared ndk-build crate's discovery logic or in the runner's env-var
propagation through cargo subcommand exec, so this commit stops fighting
either tool and assembles the APK from explicit toolchain steps instead:

  cargo ndk          -> per-ABI .so files
  aapt2 compile/link -> manifest + resources -> base APK
  zip                -> bundle native libs into lib/<abi>/
  zipalign           -> 4-byte alignment
  apksigner          -> v2/v3 signing (debug keystore for CI, real for release)

The pipeline lives in scripts/build_android_apk.sh so it's reproducible
locally (same env vars, same commands). AndroidManifest.xml is now
checked in under solitaire_app/android/ and mirrors what cargo-apk would
have generated from [package.metadata.android] — keep them in sync if
either is changed. Local `cargo apk build` still works on developer
machines where cargo-apk is happy; CI just stops depending on it.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 11:53:55 -07:00
funman300 14324b09ef ci(android): switch from cargo-apk 0.10.0 to cargo-apk2
Android Build / build-apk (push) Failing after 3m49s
Build and Deploy / build-and-push (push) Failing after 23s
cargo-apk 0.10.0 has been unable to discover an installed Android
platform in this runner environment despite ANDROID_HOME, NDK,
build-tools, and platforms;android-34 all being present and readable.
cargo-apk2 is the maintained community fork on crates.io that reads
the same `[package.metadata.android]` block, so the solitaire_app
Cargo.toml needs no changes. Cache keys updated to apk2- so we don't
restore the broken cargo-apk binary from prior runs.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 11:43:50 -07:00
Gitea CI 124f1f5cf5 chore(deploy): bump image to a6a73b5f [skip ci] 2026-05-14 18:38:04 +00:00
funman300 a6a73b5f36 fix(ci): add permission and env diagnostics to build step
Android Build / build-apk (push) Failing after 3m47s
Build and Deploy / build-and-push (push) Successful in 36s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 11:33:54 -07:00
Gitea CI b84fe79806 chore(deploy): bump image to 3248f00d [skip ci] 2026-05-14 18:32:22 +00:00
funman300 3248f00d66 fix(ci): deeper SDK verification — find android.jar actual location
Android Build / build-apk (push) Failing after 3m41s
Build and Deploy / build-and-push (push) Successful in 25s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 11:28:17 -07:00
Gitea CI c680a043ae chore(deploy): bump image to d0ab7ed9 [skip ci] 2026-05-14 18:26:23 +00:00
funman300 d0ab7ed97b fix(ci): add SDK verification step to diagnose platforms-not-found
Android Build / build-apk (push) Failing after 3m28s
Build and Deploy / build-and-push (push) Successful in 39s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 11:22:11 -07:00
Gitea CI 1144a96757 chore(deploy): bump image to eba1f66b [skip ci] 2026-05-14 18:18:13 +00:00
funman300 ac6668cee7 fix(ci): apply template-expansion pattern to release workflow
Android Build / build-apk (push) Failing after 2m59s
Build and Deploy / build-and-push (push) Failing after 46s
Mirror the fix from android-build.yml: rename ANDROID_HOME -> ANDROID_SDK
in the env block to avoid the Docker-image-baked ANDROID_HOME overriding
the workflow value in run scripts. Use ${{ env.ANDROID_SDK }} template
expressions throughout, and explicitly export ANDROID_HOME/ANDROID_NDK_HOME
before cargo-apk build so it finds the SDK at the right path.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 11:17:58 -07:00
funman300 eba1f66b45 fix(ci): use template-expanded paths in run scripts to bypass Docker ENV
Android Build / build-apk (push) Failing after 3m6s
Build and Deploy / build-and-push (push) Successful in 31s
Replace shell variable $ANDROID_HOME references in run blocks with
${{ env.ANDROID_SDK }} template expressions. Gitea runner v1 may not
override Docker-image-baked ENV vars via docker exec; template expansion
happens at workflow compilation time, so the literal path is hardcoded
into the script before the shell runs it.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 11:14:34 -07:00
Gitea CI 90959728b1 chore(deploy): bump image to 8b30f877 [skip ci] 2026-05-14 18:11:34 +00:00
funman300 8b30f8778b fix(ci): use fresh /opt/android-sdk path to avoid container ENV conflict
Android Build / build-apk (push) Failing after 3m9s
Build and Deploy / build-and-push (push) Successful in 50s
Remove SDK detection logic and install directly to /opt/android-sdk,
matching the release workflow. The container Docker image has ANDROID_HOME
baked in at /usr/local/lib/android/sdk; installing there with sudo while
cargo-apk resolves ANDROID_HOME from the image ENV created a divergence.
Using a controlled path we own eliminates that class of conflict entirely.
Add SDK cache shared with the release workflow (same key prefix v2-).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 11:07:46 -07:00
Gitea CI d6a7924f14 chore(deploy): bump image to 4db43fb3 [skip ci] 2026-05-14 18:02:16 +00:00
funman300 4db43fb3fb fix(ci): replace ANDROID_SDK_ROOT with ANDROID_HOME in release workflow
Android Build / build-apk (push) Failing after 3m49s
Build and Deploy / build-and-push (push) Successful in 54s
ANDROID_SDK_ROOT was never set; zipalign and apksigner were resolving
to empty paths and would fail. All three occurrences replaced with
ANDROID_HOME which is defined in the workflow env block.

Also adds sudo to the cache-miss SDK install (mkdir/mv/sdkmanager) to
match the debug workflow pattern — /opt/android-sdk requires root on
a fresh runner.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 10:56:42 -07:00
funman300 01d6b27e61 fix(ci): detect existing container SDK before installing, set ANDROID_HOME via GITHUB_ENV
Android Build / build-apk (push) Failing after 3m50s
Build and Deploy / build-and-push (push) Failing after 33s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 10:53:14 -07:00
funman300 3cffbc2c51 feat(engine): embed classic theme into binary like dark theme
Classic SVGs and manifest are now compiled in via include_bytes!(),
making the theme available on all platforms (desktop, Android) without
requiring filesystem assets. Removes the now-redundant Dockerfile COPY
of solitaire_engine/assets/themes/classic.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 10:53:14 -07:00