Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| fc87b13e5b | |||
| 560470b86b | |||
| 1fded5ff16 |
@@ -2,19 +2,6 @@
|
|||||||
# locally, run on every master push and pull request. Until this workflow
|
# locally, run on every master push and pull request. Until this workflow
|
||||||
# existed, nothing in CI ran the test suite at all — a direct push to
|
# existed, nothing in CI ran the test suite at all — a direct push to
|
||||||
# master was entirely unguarded.
|
# master was entirely unguarded.
|
||||||
#
|
|
||||||
# Build caching (2026-07-13): the Gitea actions cache never restored on
|
|
||||||
# this instance — every run back through run 597 logged "No cache found"
|
|
||||||
# even for exact keys saved successfully ("Cache saved successfully") by
|
|
||||||
# a run an hour earlier, including master→master restores. Until the
|
|
||||||
# cache server on the runner host is fixed, Swatinem/rust-cache is pure
|
|
||||||
# overhead here. `rust-host` is a HOST executor (its filesystem persists
|
|
||||||
# between runs — ~/.cargo and the rustup toolchain already carry over),
|
|
||||||
# so we get warm builds by pointing CARGO_TARGET_DIR at a persistent
|
|
||||||
# path on the runner instead of tarring gigabytes through a cache API
|
|
||||||
# that never returns them. Concurrent runs are safe: cargo serialises
|
|
||||||
# on the target-dir lock.
|
|
||||||
|
|
||||||
name: Test
|
name: Test
|
||||||
|
|
||||||
on:
|
on:
|
||||||
@@ -41,27 +28,8 @@ on:
|
|||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
# Seconds-long formatting gate in its own job so a rustfmt slip fails
|
|
||||||
# here instead of after a 35-minute cold build (run 600 spent its
|
|
||||||
# whole build budget to report an unformatted file).
|
|
||||||
fmt:
|
|
||||||
runs-on: rust-host
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Install Rust 1.95.0
|
|
||||||
uses: dtolnay/rust-toolchain@master
|
|
||||||
with:
|
|
||||||
toolchain: 1.95.0
|
|
||||||
components: rustfmt
|
|
||||||
|
|
||||||
- name: Format check
|
|
||||||
run: cargo fmt --check
|
|
||||||
|
|
||||||
test:
|
test:
|
||||||
runs-on: rust-host
|
runs-on: rust-host
|
||||||
needs: fmt
|
|
||||||
|
|
||||||
# Full debuginfo made the solitaire_engine test-binary link peak past the
|
# Full debuginfo made the solitaire_engine test-binary link peak past the
|
||||||
# runner's memory — ld was OOM-killed (signal 9) on runs 447 and 486.
|
# runner's memory — ld was OOM-killed (signal 9) on runs 447 and 486.
|
||||||
@@ -72,19 +40,10 @@ jobs:
|
|||||||
# test binaries concurrently; as the workspace grew (runs 514/516/519)
|
# test binaries concurrently; as the workspace grew (runs 514/516/519)
|
||||||
# two+ simultaneous ld processes OOM-killed the runner again even at
|
# two+ simultaneous ld processes OOM-killed the runner again even at
|
||||||
# line-tables-only. Two jobs keeps at most two links in flight — the
|
# line-tables-only. Two jobs keeps at most two links in flight — the
|
||||||
# compile-throughput cost is small next to the warm build.
|
# compile-throughput cost is small next to the cache-warm build.
|
||||||
#
|
|
||||||
# CARGO_INCREMENTAL=0: incremental artifacts bloat the persistent
|
|
||||||
# target dir for little benefit in CI (rust-cache used to set this
|
|
||||||
# for the same reason).
|
|
||||||
#
|
|
||||||
# CARGO_TARGET_DIR: persistent on the runner host — see the header
|
|
||||||
# comment. The prune step below keeps it from growing unbounded.
|
|
||||||
env:
|
env:
|
||||||
CARGO_PROFILE_DEV_DEBUG: line-tables-only
|
CARGO_PROFILE_DEV_DEBUG: line-tables-only
|
||||||
CARGO_BUILD_JOBS: '2'
|
CARGO_BUILD_JOBS: '2'
|
||||||
CARGO_INCREMENTAL: '0'
|
|
||||||
CARGO_TARGET_DIR: /home/runner/.cache/ferrous-solitaire/target
|
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
@@ -94,21 +53,10 @@ jobs:
|
|||||||
uses: dtolnay/rust-toolchain@master
|
uses: dtolnay/rust-toolchain@master
|
||||||
with:
|
with:
|
||||||
toolchain: 1.95.0
|
toolchain: 1.95.0
|
||||||
components: clippy
|
components: clippy, rustfmt
|
||||||
|
|
||||||
# Toolchain or lockfile bumps strand stale artifacts nothing will
|
- name: Cache cargo build
|
||||||
# ever reuse; reset the dir when it crosses 40 GiB rather than
|
uses: Swatinem/rust-cache@v2
|
||||||
# curating it (a cold rebuild every few weeks is cheaper than the
|
|
||||||
# bookkeeping).
|
|
||||||
- name: Prune persistent target dir when oversized
|
|
||||||
run: |
|
|
||||||
limit_kb=$((40 * 1024 * 1024))
|
|
||||||
used_kb=$(du -sk "$CARGO_TARGET_DIR" 2>/dev/null | cut -f1 || echo 0)
|
|
||||||
echo "persistent target dir: $((used_kb / 1024)) MiB (limit $((limit_kb / 1024)) MiB)"
|
|
||||||
if [ "${used_kb:-0}" -gt "$limit_kb" ]; then
|
|
||||||
echo "over limit — clearing for a fresh cold build"
|
|
||||||
rm -rf "$CARGO_TARGET_DIR"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Native link deps for the Bevy crates (engine/app/web) on a bare
|
# Native link deps for the Bevy crates (engine/app/web) on a bare
|
||||||
# ubuntu runner: ALSA + udev for input/audio, X11 + Wayland for winit.
|
# ubuntu runner: ALSA + udev for input/audio, X11 + Wayland for winit.
|
||||||
@@ -119,6 +67,9 @@ jobs:
|
|||||||
libasound2-dev libudev-dev pkg-config libx11-dev libxcursor-dev \
|
libasound2-dev libudev-dev pkg-config libx11-dev libxcursor-dev \
|
||||||
libxrandr-dev libxi-dev libwayland-dev libxkbcommon-dev
|
libxrandr-dev libxi-dev libwayland-dev libxkbcommon-dev
|
||||||
|
|
||||||
|
- name: Format check
|
||||||
|
run: cargo fmt --check
|
||||||
|
|
||||||
# SQLX_OFFLINE uses the checked-in `.sqlx/` query cache (no live DB),
|
# SQLX_OFFLINE uses the checked-in `.sqlx/` query cache (no live DB),
|
||||||
# same as the web-e2e workflow's server prebuild.
|
# same as the web-e2e workflow's server prebuild.
|
||||||
- name: Clippy (deny warnings)
|
- name: Clippy (deny warnings)
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
# Menu UX Redesign — July 2026
|
# Menu UX Redesign — July 2026
|
||||||
|
|
||||||
Status: PLANNING. Visual identity (Terminal / base16-eighties) is settled and
|
Status: IN PROGRESS. A / E / C / G shipped in v0.43.0; B implemented on
|
||||||
out of scope — this is about **structure and interaction**, not colors or type.
|
`feat/home-hierarchy` (2026-07-13); F / H and the I–M backlog remain open.
|
||||||
|
Visual identity (Terminal / base16-eighties) is settled and out of
|
||||||
|
scope — this is about **structure and interaction**, not colors or type.
|
||||||
|
|
||||||
## Diagnosis (from code survey, 2026-07-07)
|
## Diagnosis (from code survey, 2026-07-07)
|
||||||
|
|
||||||
@@ -197,8 +199,10 @@ slimming), then **B**, then **F/G/H** as independent follow-ups.
|
|||||||
## Open decisions
|
## Open decisions
|
||||||
|
|
||||||
1. ~~Settings: tabs vs. sub-pages~~ — **DECIDED 2026-07-07: tabs.**
|
1. ~~Settings: tabs vs. sub-pages~~ — **DECIDED 2026-07-07: tabs.**
|
||||||
2. Deal options: disclosure on Classic card (proposed) vs. keep global row?
|
2. ~~Deal options: disclosure on Classic card vs. keep global row~~ —
|
||||||
3. Time Attack + Seed: top-level cards (proposed, grid stays symmetric) vs.
|
**DECIDED 2026-07-09: disclosure on the Classic card / New Game hero.**
|
||||||
tucked under a "More" card?
|
3. ~~Time Attack + Seed: top-level cards vs. "More" card~~ —
|
||||||
4. Stitch mockups for Phase B, or iterate directly in-engine?
|
**DECIDED 2026-07-09: top-level cards, symmetric 2×3 grid.**
|
||||||
|
4. ~~Stitch mockups for Phase B, or iterate directly in-engine~~ —
|
||||||
|
**DECIDED 2026-07-09: directly in-engine.**
|
||||||
5. Phase F bottom bar: touch-only (proposed) or also desktop?
|
5. Phase F bottom bar: touch-only (proposed) or also desktop?
|
||||||
|
|||||||
@@ -9,7 +9,10 @@ use std::io;
|
|||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use solitaire_core::{DrawStockConfig, game_state::DifficultyLevel};
|
use solitaire_core::{
|
||||||
|
DrawStockConfig,
|
||||||
|
game_state::{DifficultyLevel, GameMode},
|
||||||
|
};
|
||||||
|
|
||||||
const SETTINGS_FILE_NAME: &str = "settings.json";
|
const SETTINGS_FILE_NAME: &str = "settings.json";
|
||||||
|
|
||||||
@@ -248,6 +251,12 @@ pub struct Settings {
|
|||||||
/// cleanly to `None` via `#[serde(default)]`.
|
/// cleanly to `None` via `#[serde(default)]`.
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub last_difficulty: Option<DifficultyLevel>,
|
pub last_difficulty: Option<DifficultyLevel>,
|
||||||
|
/// Mode of the last game the player launched from the home overlay.
|
||||||
|
/// The home hero "New Game" button replays this mode with one tap.
|
||||||
|
/// Older `settings.json` files written before this field existed
|
||||||
|
/// deserialize cleanly to `GameMode::Classic` via `#[serde(default)]`.
|
||||||
|
#[serde(default)]
|
||||||
|
pub last_mode: GameMode,
|
||||||
/// Custom public name displayed on the leaderboard. When `None`, the
|
/// Custom public name displayed on the leaderboard. When `None`, the
|
||||||
/// player's server `username` is used instead. Trimmed to 32 characters
|
/// player's server `username` is used instead. Trimmed to 32 characters
|
||||||
/// before submission. Older `settings.json` files written before this
|
/// before submission. Older `settings.json` files written before this
|
||||||
@@ -415,6 +424,7 @@ impl Default for Settings {
|
|||||||
disable_smart_default_size: false,
|
disable_smart_default_size: false,
|
||||||
replay_move_interval_secs: default_replay_move_interval_secs(),
|
replay_move_interval_secs: default_replay_move_interval_secs(),
|
||||||
last_difficulty: None,
|
last_difficulty: None,
|
||||||
|
last_mode: GameMode::Classic,
|
||||||
leaderboard_display_name: None,
|
leaderboard_display_name: None,
|
||||||
leaderboard_opted_in: false,
|
leaderboard_opted_in: false,
|
||||||
take_from_foundation: true,
|
take_from_foundation: true,
|
||||||
|
|||||||
+1119
-307
File diff suppressed because it is too large
Load Diff
@@ -178,6 +178,32 @@ pub fn spawn_modal<M: Component, F>(
|
|||||||
z_panel: i32,
|
z_panel: i32,
|
||||||
build_card: F,
|
build_card: F,
|
||||||
) -> Entity
|
) -> Entity
|
||||||
|
where
|
||||||
|
F: FnOnce(&mut ChildSpawnerCommands),
|
||||||
|
{
|
||||||
|
spawn_modal_sized(
|
||||||
|
commands,
|
||||||
|
plugin_marker,
|
||||||
|
z_panel,
|
||||||
|
MODAL_CARD_MAX_WIDTH,
|
||||||
|
build_card,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Default maximum card width in logical pixels — every [`spawn_modal`]
|
||||||
|
/// caller gets this. Surfaces that lay out side-by-side panes (e.g. the
|
||||||
|
/// two-pane Home on wide viewports) can raise it via [`spawn_modal_sized`].
|
||||||
|
pub const MODAL_CARD_MAX_WIDTH: f32 = 720.0;
|
||||||
|
|
||||||
|
/// [`spawn_modal`] with an explicit card `max_width`. Behaviour is
|
||||||
|
/// otherwise identical — same scrim, enter animation, and card chrome.
|
||||||
|
pub fn spawn_modal_sized<M: Component, F>(
|
||||||
|
commands: &mut Commands,
|
||||||
|
plugin_marker: M,
|
||||||
|
z_panel: i32,
|
||||||
|
max_width: f32,
|
||||||
|
build_card: F,
|
||||||
|
) -> Entity
|
||||||
where
|
where
|
||||||
F: FnOnce(&mut ChildSpawnerCommands),
|
F: FnOnce(&mut ChildSpawnerCommands),
|
||||||
{
|
{
|
||||||
@@ -235,7 +261,7 @@ where
|
|||||||
padding: UiRect::all(VAL_SPACE_5),
|
padding: UiRect::all(VAL_SPACE_5),
|
||||||
border: UiRect::all(Val::Px(1.0)),
|
border: UiRect::all(Val::Px(1.0)),
|
||||||
border_radius: BorderRadius::all(Val::Px(RADIUS_LG)),
|
border_radius: BorderRadius::all(Val::Px(RADIUS_LG)),
|
||||||
max_width: Val::Px(720.0),
|
max_width: Val::Px(max_width),
|
||||||
align_items: AlignItems::Stretch,
|
align_items: AlignItems::Stretch,
|
||||||
..default()
|
..default()
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user