Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 51fecb24b0 | |||
| 5559f32672 | |||
| 17c320c08f | |||
| c35c045f08 | |||
| 8fe0891866 | |||
| 677999a51e | |||
| 7177f0eb1b | |||
| 407cae2040 | |||
| eb906fe968 | |||
| 8d31a37a39 | |||
| 2bf990388b | |||
| 7238ef225e | |||
| b984161d46 | |||
| c69d732d5a | |||
| 5e0e8d000b | |||
| 27eed98922 | |||
| f304917d62 |
@@ -1,131 +0,0 @@
|
|||||||
name: Android Build
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [master]
|
|
||||||
# Rebuild whenever app/engine/asset code changes.
|
|
||||||
# Skip server-only, deploy, and doc changes.
|
|
||||||
paths-ignore:
|
|
||||||
- 'deploy/**'
|
|
||||||
- 'argocd/**'
|
|
||||||
- 'solitaire_server/**'
|
|
||||||
- '**.md'
|
|
||||||
|
|
||||||
env:
|
|
||||||
ANDROID_SDK: /opt/android-sdk
|
|
||||||
NDK_VERSION: "25.2.9519653"
|
|
||||||
BUILD_TOOLS_VERSION: "34.0.0"
|
|
||||||
PLATFORM: "android-34"
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build-apk:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Set short SHA
|
|
||||||
id: meta
|
|
||||||
run: echo "sha=${GITHUB_SHA::8}" >> "$GITHUB_OUTPUT"
|
|
||||||
|
|
||||||
# ── System dependencies ────────────────────────────────────────────
|
|
||||||
- name: Install system dependencies
|
|
||||||
run: |
|
|
||||||
sudo apt-get update -y
|
|
||||||
sudo apt-get install -y openjdk-17-jdk-headless unzip zip
|
|
||||||
|
|
||||||
# ── Android SDK (shared cache key with release workflow) ──────────
|
|
||||||
- name: Cache Android SDK
|
|
||||||
uses: actions/cache@v4
|
|
||||||
id: sdk-cache
|
|
||||||
with:
|
|
||||||
path: ${{ env.ANDROID_SDK }}
|
|
||||||
key: v2-android-sdk-ndk${{ env.NDK_VERSION }}-bt${{ env.BUILD_TOOLS_VERSION }}
|
|
||||||
|
|
||||||
- name: Install Android SDK + NDK
|
|
||||||
if: steps.sdk-cache.outputs.cache-hit != 'true'
|
|
||||||
run: |
|
|
||||||
sudo mkdir -p ${{ env.ANDROID_SDK }}/cmdline-tools
|
|
||||||
curl -sL \
|
|
||||||
"https://dl.google.com/android/repository/commandlinetools-linux-11076708_latest.zip" \
|
|
||||||
-o /tmp/cmdtools.zip
|
|
||||||
unzip -q /tmp/cmdtools.zip -d /tmp/cmdtools
|
|
||||||
sudo mv /tmp/cmdtools/cmdline-tools ${{ env.ANDROID_SDK }}/cmdline-tools/latest
|
|
||||||
yes | sudo ${{ env.ANDROID_SDK }}/cmdline-tools/latest/bin/sdkmanager \
|
|
||||||
--sdk_root=${{ env.ANDROID_SDK }} --licenses > /dev/null 2>&1 || true
|
|
||||||
sudo ${{ env.ANDROID_SDK }}/cmdline-tools/latest/bin/sdkmanager \
|
|
||||||
--sdk_root=${{ env.ANDROID_SDK }} \
|
|
||||||
"build-tools;${{ env.BUILD_TOOLS_VERSION }}" \
|
|
||||||
"platforms;${{ env.PLATFORM }}" \
|
|
||||||
"ndk;${{ env.NDK_VERSION }}"
|
|
||||||
|
|
||||||
# ── Rust toolchain ─────────────────────────────────────────────────
|
|
||||||
- name: Install Rust stable
|
|
||||||
run: |
|
|
||||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs \
|
|
||||||
| sh -s -- -y --default-toolchain stable --no-modify-path
|
|
||||||
echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
|
|
||||||
|
|
||||||
- name: Add Android cross-compilation targets
|
|
||||||
run: |
|
|
||||||
rustup target add \
|
|
||||||
aarch64-linux-android \
|
|
||||||
armv7-linux-androideabi \
|
|
||||||
x86_64-linux-android
|
|
||||||
|
|
||||||
# ── Cargo caches ───────────────────────────────────────────────────
|
|
||||||
- name: Cache Cargo registry
|
|
||||||
uses: actions/cache@v4
|
|
||||||
with:
|
|
||||||
path: |
|
|
||||||
~/.cargo/registry/index
|
|
||||||
~/.cargo/registry/cache
|
|
||||||
~/.cargo/git/db
|
|
||||||
key: cargo-registry-${{ hashFiles('**/Cargo.lock') }}
|
|
||||||
restore-keys: cargo-registry-
|
|
||||||
|
|
||||||
- name: Cache cargo-ndk binary
|
|
||||||
uses: actions/cache@v4
|
|
||||||
id: ndk-tool-cache
|
|
||||||
with:
|
|
||||||
path: ~/.cargo/bin/cargo-ndk
|
|
||||||
key: cargo-ndk-${{ runner.os }}-stable
|
|
||||||
|
|
||||||
- name: Cache build artifacts
|
|
||||||
uses: actions/cache@v4
|
|
||||||
with:
|
|
||||||
path: target
|
|
||||||
key: android-target-${{ hashFiles('**/Cargo.lock') }}-${{ github.sha }}
|
|
||||||
restore-keys: android-target-${{ hashFiles('**/Cargo.lock') }}-
|
|
||||||
|
|
||||||
- name: Install cargo-ndk
|
|
||||||
if: steps.ndk-tool-cache.outputs.cache-hit != 'true'
|
|
||||||
run: cargo install cargo-ndk --locked
|
|
||||||
|
|
||||||
# ── Build APK ──────────────────────────────────────────────────────
|
|
||||||
# Debug CI only builds arm64-v8a — full three-ABI debug builds blow
|
|
||||||
# past the runner's disk budget (~25 GB of target/ + intermediate
|
|
||||||
# APKs caused apksigner to OOM-on-disk in the previous run). Release
|
|
||||||
# CI still ships all three ABIs from android-release.yml.
|
|
||||||
- name: Build debug APK
|
|
||||||
env:
|
|
||||||
ANDROID_HOME: ${{ env.ANDROID_SDK }}
|
|
||||||
ANDROID_NDK_HOME: ${{ env.ANDROID_SDK }}/ndk/${{ env.NDK_VERSION }}
|
|
||||||
BUILD_TOOLS_VERSION: ${{ env.BUILD_TOOLS_VERSION }}
|
|
||||||
PLATFORM: ${{ env.PLATFORM }}
|
|
||||||
PROFILE: debug
|
|
||||||
ABIS: arm64-v8a
|
|
||||||
run: ./scripts/build_android_apk.sh
|
|
||||||
|
|
||||||
# ── Artifact ───────────────────────────────────────────────────────
|
|
||||||
# Pinned to v3 because Gitea Actions doesn't implement the github.com
|
|
||||||
# artifact service that upload-artifact@v4+ requires; v3 uses the
|
|
||||||
# older chunked HTTP API that Gitea's GHES-compatibility layer
|
|
||||||
# supports.
|
|
||||||
- name: Upload APK
|
|
||||||
uses: actions/upload-artifact@v3
|
|
||||||
with:
|
|
||||||
name: solitaire-quest-debug-${{ steps.meta.outputs.sha }}
|
|
||||||
path: target/debug/apk/solitaire-quest.apk
|
|
||||||
retention-days: 30
|
|
||||||
@@ -1,160 +0,0 @@
|
|||||||
name: Android Release
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
tags:
|
|
||||||
- 'v*.*.*'
|
|
||||||
|
|
||||||
env:
|
|
||||||
ANDROID_SDK: /opt/android-sdk
|
|
||||||
NDK_VERSION: "25.2.9519653"
|
|
||||||
BUILD_TOOLS_VERSION: "34.0.0"
|
|
||||||
PLATFORM: "android-34"
|
|
||||||
GITEA_API: https://git.aleshym.co/api/v1
|
|
||||||
REPO: funman300/Rusty_Solitare
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build-release-apk:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Extract version from tag
|
|
||||||
id: meta
|
|
||||||
run: echo "tag=${GITHUB_REF_NAME}" >> "$GITHUB_OUTPUT"
|
|
||||||
|
|
||||||
# ── System dependencies ────────────────────────────────────────────
|
|
||||||
- name: Install system dependencies
|
|
||||||
run: |
|
|
||||||
sudo apt-get update -y
|
|
||||||
sudo apt-get install -y openjdk-17-jdk-headless unzip zip jq
|
|
||||||
|
|
||||||
# ── Android SDK (shared cache key with debug workflow) ─────────────
|
|
||||||
- name: Cache Android SDK
|
|
||||||
uses: actions/cache@v4
|
|
||||||
id: sdk-cache
|
|
||||||
with:
|
|
||||||
path: ${{ env.ANDROID_SDK }}
|
|
||||||
key: v2-android-sdk-ndk${{ env.NDK_VERSION }}-bt${{ env.BUILD_TOOLS_VERSION }}
|
|
||||||
|
|
||||||
- name: Install Android SDK + NDK
|
|
||||||
if: steps.sdk-cache.outputs.cache-hit != 'true'
|
|
||||||
run: |
|
|
||||||
sudo mkdir -p ${{ env.ANDROID_SDK }}/cmdline-tools
|
|
||||||
curl -sL \
|
|
||||||
"https://dl.google.com/android/repository/commandlinetools-linux-11076708_latest.zip" \
|
|
||||||
-o /tmp/cmdtools.zip
|
|
||||||
unzip -q /tmp/cmdtools.zip -d /tmp/cmdtools
|
|
||||||
sudo mv /tmp/cmdtools/cmdline-tools ${{ env.ANDROID_SDK }}/cmdline-tools/latest
|
|
||||||
yes | sudo ${{ env.ANDROID_SDK }}/cmdline-tools/latest/bin/sdkmanager \
|
|
||||||
--sdk_root=${{ env.ANDROID_SDK }} --licenses > /dev/null 2>&1 || true
|
|
||||||
sudo ${{ env.ANDROID_SDK }}/cmdline-tools/latest/bin/sdkmanager \
|
|
||||||
--sdk_root=${{ env.ANDROID_SDK }} \
|
|
||||||
"build-tools;${{ env.BUILD_TOOLS_VERSION }}" \
|
|
||||||
"platforms;${{ env.PLATFORM }}" \
|
|
||||||
"ndk;${{ env.NDK_VERSION }}"
|
|
||||||
|
|
||||||
# ── Rust toolchain ─────────────────────────────────────────────────
|
|
||||||
- name: Install Rust stable
|
|
||||||
run: |
|
|
||||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs \
|
|
||||||
| sh -s -- -y --default-toolchain stable --no-modify-path
|
|
||||||
echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
|
|
||||||
|
|
||||||
- name: Add Android cross-compilation targets
|
|
||||||
run: |
|
|
||||||
rustup target add \
|
|
||||||
aarch64-linux-android \
|
|
||||||
armv7-linux-androideabi \
|
|
||||||
x86_64-linux-android
|
|
||||||
|
|
||||||
# ── Cargo caches ───────────────────────────────────────────────────
|
|
||||||
- name: Cache Cargo registry
|
|
||||||
uses: actions/cache@v4
|
|
||||||
with:
|
|
||||||
path: |
|
|
||||||
~/.cargo/registry/index
|
|
||||||
~/.cargo/registry/cache
|
|
||||||
~/.cargo/git/db
|
|
||||||
key: cargo-registry-${{ hashFiles('**/Cargo.lock') }}
|
|
||||||
restore-keys: cargo-registry-
|
|
||||||
|
|
||||||
- name: Cache cargo-ndk binary
|
|
||||||
uses: actions/cache@v4
|
|
||||||
id: ndk-tool-cache
|
|
||||||
with:
|
|
||||||
path: ~/.cargo/bin/cargo-ndk
|
|
||||||
key: cargo-ndk-${{ runner.os }}-stable
|
|
||||||
|
|
||||||
- name: Cache build artifacts
|
|
||||||
uses: actions/cache@v4
|
|
||||||
with:
|
|
||||||
path: target
|
|
||||||
key: android-release-target-${{ hashFiles('**/Cargo.lock') }}-${{ github.sha }}
|
|
||||||
restore-keys: android-release-target-${{ hashFiles('**/Cargo.lock') }}-
|
|
||||||
|
|
||||||
- name: Install cargo-ndk
|
|
||||||
if: steps.ndk-tool-cache.outputs.cache-hit != 'true'
|
|
||||||
run: cargo install cargo-ndk --locked
|
|
||||||
|
|
||||||
# ── Build & sign with release keystore ─────────────────────────────
|
|
||||||
- name: Decode keystore
|
|
||||||
run: |
|
|
||||||
secret_len=$(echo -n "${{ secrets.KEYSTORE_BASE64 }}" | wc -c)
|
|
||||||
echo "KEYSTORE_BASE64 secret length: ${secret_len} chars"
|
|
||||||
set +e
|
|
||||||
echo "${{ secrets.KEYSTORE_BASE64 }}" | base64 --decode > /tmp/solitaire-release.jks 2>/tmp/b64_err.txt
|
|
||||||
b64_exit=$?
|
|
||||||
set -e
|
|
||||||
size=$(wc -c < /tmp/solitaire-release.jks)
|
|
||||||
echo "base64 exit code: ${b64_exit}, keystore size: ${size} bytes"
|
|
||||||
[ -s /tmp/b64_err.txt ] && echo "base64 error: $(cat /tmp/b64_err.txt)" || true
|
|
||||||
[ "$size" -gt 0 ] || { echo "ERROR: KEYSTORE_BASE64 is empty or invalid base64"; exit 1; }
|
|
||||||
|
|
||||||
- name: Build signed release APK
|
|
||||||
env:
|
|
||||||
ANDROID_HOME: ${{ env.ANDROID_SDK }}
|
|
||||||
ANDROID_NDK_HOME: ${{ env.ANDROID_SDK }}/ndk/${{ env.NDK_VERSION }}
|
|
||||||
BUILD_TOOLS_VERSION: ${{ env.BUILD_TOOLS_VERSION }}
|
|
||||||
PLATFORM: ${{ env.PLATFORM }}
|
|
||||||
PROFILE: release
|
|
||||||
KEYSTORE: /tmp/solitaire-release.jks
|
|
||||||
KEYSTORE_PASS: ${{ secrets.KEYSTORE_PASS }}
|
|
||||||
KEY_ALIAS: ${{ secrets.KEY_ALIAS }}
|
|
||||||
KEY_PASS: ${{ secrets.KEY_PASS }}
|
|
||||||
APK_OUT: ferrous-solitaire-${{ steps.meta.outputs.tag }}.apk
|
|
||||||
run: ./scripts/build_android_apk.sh
|
|
||||||
|
|
||||||
# ── Publish to Gitea release ───────────────────────────────────────
|
|
||||||
- name: Create Gitea release
|
|
||||||
id: release
|
|
||||||
run: |
|
|
||||||
TAG="${{ steps.meta.outputs.tag }}"
|
|
||||||
RESPONSE=$(curl -s -o /tmp/release.json -w "%{http_code}" \
|
|
||||||
-X POST "$GITEA_API/repos/$REPO/releases" \
|
|
||||||
-H "Authorization: token ${{ secrets.CI_TOKEN }}" \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-d "{\"tag_name\":\"$TAG\",\"name\":\"$TAG\",\"draft\":false,\"prerelease\":false}")
|
|
||||||
if [ "$RESPONSE" = "409" ]; then
|
|
||||||
curl -sf "$GITEA_API/repos/$REPO/releases/tags/$TAG" \
|
|
||||||
-H "Authorization: token ${{ secrets.CI_TOKEN }}" \
|
|
||||||
> /tmp/release.json
|
|
||||||
elif [ "$RESPONSE" != "201" ]; then
|
|
||||||
echo "Release creation failed with HTTP $RESPONSE"
|
|
||||||
cat /tmp/release.json
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
RELEASE_ID=$(jq -r '.id' /tmp/release.json)
|
|
||||||
echo "release_id=$RELEASE_ID" >> "$GITHUB_OUTPUT"
|
|
||||||
|
|
||||||
- name: Upload signed APK
|
|
||||||
run: |
|
|
||||||
TAG="${{ steps.meta.outputs.tag }}"
|
|
||||||
APK="ferrous-solitaire-${TAG}.apk"
|
|
||||||
curl -sf -X POST \
|
|
||||||
"$GITEA_API/repos/$REPO/releases/${{ steps.release.outputs.release_id }}/assets?name=$APK" \
|
|
||||||
-H "Authorization: token ${{ secrets.CI_TOKEN }}" \
|
|
||||||
-H "Content-Type: application/octet-stream" \
|
|
||||||
--data-binary @"$APK"
|
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"db_name": "SQLite",
|
||||||
|
"query": "SELECT username FROM users WHERE id = ?",
|
||||||
|
"describe": {
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"name": "username",
|
||||||
|
"ordinal": 0,
|
||||||
|
"type_info": "Text"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 1
|
||||||
|
},
|
||||||
|
"nullable": [
|
||||||
|
false
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"hash": "06c945e50567c6801f1346d436cdc86a82a4e13dd45d8286295ba37cdbdc045e"
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"db_name": "SQLite",
|
||||||
|
"query": "UPDATE users SET avatar_url = ? WHERE id = ?",
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 2
|
||||||
|
},
|
||||||
|
"nullable": []
|
||||||
|
},
|
||||||
|
"hash": "15d08e02b102147bc74848ec3692b99edbf4c33078191f0132991ee94d4507b1"
|
||||||
|
}
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
{
|
|
||||||
"db_name": "SQLite",
|
|
||||||
"query": "INSERT OR IGNORE INTO analytics_events\n (id, user_id, session_id, event_type, payload, client_time, received_at)\n VALUES (?, ?, ?, ?, ?, ?, ?)",
|
|
||||||
"describe": {
|
|
||||||
"columns": [],
|
|
||||||
"parameters": {
|
|
||||||
"Right": 7
|
|
||||||
},
|
|
||||||
"nullable": []
|
|
||||||
},
|
|
||||||
"hash": "f23630e78ae88e72d7930184f7cd8fc67e71f3930609f1cf14061d35d6de8ec3"
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"db_name": "SQLite",
|
||||||
|
"query": "SELECT username, avatar_url FROM users WHERE id = ?",
|
||||||
|
"describe": {
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"name": "username",
|
||||||
|
"ordinal": 0,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "avatar_url",
|
||||||
|
"ordinal": 1,
|
||||||
|
"type_info": "Text"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 1
|
||||||
|
},
|
||||||
|
"nullable": [
|
||||||
|
false,
|
||||||
|
true
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"hash": "fae33e7159b43c756131b1545360dcb0250988b43550881e3f0a336d9516dd45"
|
||||||
|
}
|
||||||
@@ -1,497 +0,0 @@
|
|||||||
# CLAUDE_PROMPT_PACK.md
|
|
||||||
|
|
||||||
version: 1.0
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
# 0. GLOBAL INSTRUCTION (prepend to every prompt)
|
|
||||||
|
|
||||||
```
|
|
||||||
You must follow CLAUDE_SPEC.md strictly.
|
|
||||||
|
|
||||||
Rules:
|
|
||||||
- Do not expand scope beyond what is defined
|
|
||||||
- Do not refactor unrelated code
|
|
||||||
- Do not introduce new dependencies to solitaire_core or solitaire_sync without confirmation
|
|
||||||
- Prefer minimal, surgical changes
|
|
||||||
- Use existing patterns in the codebase
|
|
||||||
- Return minimal diffs or changed functions only
|
|
||||||
|
|
||||||
Before writing code:
|
|
||||||
1. List relevant constraints from CLAUDE_SPEC.md
|
|
||||||
2. Identify risks
|
|
||||||
3. Then implement
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
# 1. FEATURE IMPLEMENTATION
|
|
||||||
|
|
||||||
```
|
|
||||||
# TASK: Feature Implementation
|
|
||||||
|
|
||||||
feature: "<name>"
|
|
||||||
|
|
||||||
goal:
|
|
||||||
"<clear outcome>"
|
|
||||||
|
|
||||||
scope:
|
|
||||||
crates: []
|
|
||||||
systems: []
|
|
||||||
files: []
|
|
||||||
|
|
||||||
non_goals:
|
|
||||||
- ""
|
|
||||||
|
|
||||||
constraints:
|
|
||||||
- must follow CLAUDE_SPEC.md
|
|
||||||
- event-driven architecture required
|
|
||||||
- no blocking operations
|
|
||||||
- no cross-crate leakage
|
|
||||||
|
|
||||||
acceptance_criteria:
|
|
||||||
- ""
|
|
||||||
- ""
|
|
||||||
|
|
||||||
edge_cases:
|
|
||||||
- ""
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Required Patterns
|
|
||||||
|
|
||||||
Use this pattern for systems:
|
|
||||||
<PASTE EXISTING SYSTEM SNIPPET HERE>
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Output Format
|
|
||||||
|
|
||||||
intent:
|
|
||||||
plan:
|
|
||||||
constraints_used:
|
|
||||||
risks:
|
|
||||||
|
|
||||||
code_changes:
|
|
||||||
(minimal diffs only)
|
|
||||||
|
|
||||||
notes:
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
# 2. BUGFIX
|
|
||||||
|
|
||||||
```
|
|
||||||
# TASK: Bug Fix
|
|
||||||
|
|
||||||
bug_description:
|
|
||||||
"<what is broken>"
|
|
||||||
|
|
||||||
expected_behavior:
|
|
||||||
"<correct behavior>"
|
|
||||||
|
|
||||||
root_cause_hint (optional):
|
|
||||||
""
|
|
||||||
|
|
||||||
scope:
|
|
||||||
crates: []
|
|
||||||
files: []
|
|
||||||
|
|
||||||
constraints:
|
|
||||||
- minimal fix only
|
|
||||||
- no refactors unless required
|
|
||||||
- must add regression protection if applicable
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Requirements
|
|
||||||
|
|
||||||
1. Identify root cause
|
|
||||||
2. Fix it minimally
|
|
||||||
3. Preserve all invariants
|
|
||||||
4. Do not change unrelated logic
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Output Format
|
|
||||||
|
|
||||||
analysis:
|
|
||||||
root_cause:
|
|
||||||
fix_strategy:
|
|
||||||
|
|
||||||
code_changes:
|
|
||||||
(minimal diff)
|
|
||||||
|
|
||||||
regression_test (only if high-value):
|
|
||||||
|
|
||||||
notes:
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
# 3. REFACTOR
|
|
||||||
|
|
||||||
```
|
|
||||||
# TASK: Refactor
|
|
||||||
|
|
||||||
target:
|
|
||||||
"<what is being improved>"
|
|
||||||
|
|
||||||
goal:
|
|
||||||
"<what improves>"
|
|
||||||
|
|
||||||
scope:
|
|
||||||
crates: []
|
|
||||||
files: []
|
|
||||||
|
|
||||||
non_goals:
|
|
||||||
- no behavior changes
|
|
||||||
- no new features
|
|
||||||
|
|
||||||
constraints:
|
|
||||||
- must preserve behavior exactly
|
|
||||||
- must respect crate boundaries
|
|
||||||
- must not duplicate logic
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Refactor Type
|
|
||||||
|
|
||||||
- [ ] simplify logic
|
|
||||||
- [ ] reduce duplication
|
|
||||||
- [ ] improve readability
|
|
||||||
- [ ] performance (non-invasive)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Output Format
|
|
||||||
|
|
||||||
analysis:
|
|
||||||
issues_found:
|
|
||||||
|
|
||||||
refactor_plan:
|
|
||||||
|
|
||||||
code_changes:
|
|
||||||
(diff only)
|
|
||||||
|
|
||||||
verification:
|
|
||||||
- behavior unchanged: yes/no
|
|
||||||
- invariants preserved: yes/no
|
|
||||||
|
|
||||||
notes:
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
# 4. SYSTEM DESIGN (NEW FEATURE)
|
|
||||||
|
|
||||||
```
|
|
||||||
# TASK: System Design
|
|
||||||
|
|
||||||
feature:
|
|
||||||
"<name>"
|
|
||||||
|
|
||||||
goal:
|
|
||||||
"<what problem it solves>"
|
|
||||||
|
|
||||||
constraints:
|
|
||||||
- must fit existing architecture
|
|
||||||
- must follow plugin + event model
|
|
||||||
- must not violate crate boundaries
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Required Output
|
|
||||||
|
|
||||||
design:
|
|
||||||
|
|
||||||
components:
|
|
||||||
- plugins:
|
|
||||||
- systems:
|
|
||||||
- events:
|
|
||||||
- resources:
|
|
||||||
|
|
||||||
data_flow:
|
|
||||||
(step-by-step)
|
|
||||||
|
|
||||||
integration_points:
|
|
||||||
- where it connects to existing systems
|
|
||||||
|
|
||||||
risks:
|
|
||||||
- ""
|
|
||||||
|
|
||||||
tradeoffs:
|
|
||||||
- ""
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## DO NOT
|
|
||||||
|
|
||||||
- write full implementation
|
|
||||||
- modify unrelated systems
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
# 5. NEW BEVY SYSTEM
|
|
||||||
|
|
||||||
```
|
|
||||||
# TASK: Add Bevy System
|
|
||||||
|
|
||||||
system_name:
|
|
||||||
""
|
|
||||||
|
|
||||||
trigger:
|
|
||||||
(event or condition)
|
|
||||||
|
|
||||||
reads:
|
|
||||||
[Resources]
|
|
||||||
|
|
||||||
writes:
|
|
||||||
[Resources]
|
|
||||||
|
|
||||||
emits:
|
|
||||||
[Events]
|
|
||||||
|
|
||||||
constraints:
|
|
||||||
- must be event-driven
|
|
||||||
- must not directly mutate unrelated state
|
|
||||||
- must be single responsibility
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Output Format
|
|
||||||
|
|
||||||
system_signature:
|
|
||||||
|
|
||||||
implementation:
|
|
||||||
(code only)
|
|
||||||
|
|
||||||
notes:
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
# 6. CORE LOGIC FUNCTION (solitaire_core)
|
|
||||||
|
|
||||||
```
|
|
||||||
# TASK: Core Logic Implementation
|
|
||||||
|
|
||||||
function:
|
|
||||||
"<name>"
|
|
||||||
|
|
||||||
goal:
|
|
||||||
"<what it does>"
|
|
||||||
|
|
||||||
rules:
|
|
||||||
- no IO
|
|
||||||
- no async
|
|
||||||
- no Bevy
|
|
||||||
- deterministic
|
|
||||||
|
|
||||||
invariants:
|
|
||||||
- ""
|
|
||||||
- ""
|
|
||||||
|
|
||||||
errors:
|
|
||||||
- ""
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Output Format
|
|
||||||
|
|
||||||
constraints_checked:
|
|
||||||
|
|
||||||
implementation:
|
|
||||||
(code only)
|
|
||||||
|
|
||||||
edge_case_handling:
|
|
||||||
|
|
||||||
notes:
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
# 7. SYNC / MERGE LOGIC
|
|
||||||
|
|
||||||
```
|
|
||||||
# TASK: Sync Logic
|
|
||||||
|
|
||||||
goal:
|
|
||||||
"<what is being merged or synced>"
|
|
||||||
|
|
||||||
constraints:
|
|
||||||
- must be deterministic
|
|
||||||
- must be idempotent
|
|
||||||
- must be lossless
|
|
||||||
- must not delete data
|
|
||||||
|
|
||||||
rules:
|
|
||||||
- counters → max
|
|
||||||
- times → min
|
|
||||||
- collections → union
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Output Format
|
|
||||||
|
|
||||||
analysis:
|
|
||||||
|
|
||||||
merge_logic:
|
|
||||||
|
|
||||||
code_changes:
|
|
||||||
|
|
||||||
invariants_verified:
|
|
||||||
- deterministic
|
|
||||||
- idempotent
|
|
||||||
- lossless
|
|
||||||
|
|
||||||
notes:
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
# 8. PERFORMANCE OPTIMIZATION
|
|
||||||
|
|
||||||
```
|
|
||||||
# TASK: Optimization
|
|
||||||
|
|
||||||
target:
|
|
||||||
"<what is slow>"
|
|
||||||
|
|
||||||
constraints:
|
|
||||||
- no behavior change
|
|
||||||
- no architecture change
|
|
||||||
- minimal code changes
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Output Format
|
|
||||||
|
|
||||||
analysis:
|
|
||||||
bottleneck:
|
|
||||||
|
|
||||||
optimization_strategy:
|
|
||||||
|
|
||||||
code_changes:
|
|
||||||
|
|
||||||
impact_estimate:
|
|
||||||
|
|
||||||
notes:
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
# 9. TEST GENERATION (STRICT MODE)
|
|
||||||
|
|
||||||
```
|
|
||||||
# TASK: Test Generation
|
|
||||||
|
|
||||||
target:
|
|
||||||
"<function/system>"
|
|
||||||
|
|
||||||
reason:
|
|
||||||
- bugfix | complex logic | invariant protection
|
|
||||||
|
|
||||||
constraints:
|
|
||||||
- no redundant tests
|
|
||||||
- must test real behavior
|
|
||||||
- must fail if logic breaks
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Output Format
|
|
||||||
|
|
||||||
test_cases:
|
|
||||||
- ""
|
|
||||||
|
|
||||||
test_code:
|
|
||||||
|
|
||||||
notes:
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
# 10. DEBUGGING / INVESTIGATION
|
|
||||||
|
|
||||||
```
|
|
||||||
# TASK: Debug
|
|
||||||
|
|
||||||
problem:
|
|
||||||
"<symptom>"
|
|
||||||
|
|
||||||
context:
|
|
||||||
"<relevant code or system>"
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Required Steps
|
|
||||||
|
|
||||||
1. List possible causes
|
|
||||||
2. Narrow down most likely
|
|
||||||
3. Suggest verification steps
|
|
||||||
4. Provide minimal fix
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Output Format
|
|
||||||
|
|
||||||
hypotheses:
|
|
||||||
|
|
||||||
most_likely:
|
|
||||||
|
|
||||||
verification_steps:
|
|
||||||
|
|
||||||
fix:
|
|
||||||
|
|
||||||
notes:
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
# 11. HARD CONSTRAINT OVERRIDE (RARE)
|
|
||||||
|
|
||||||
```
|
|
||||||
# TASK: Exception Handling
|
|
||||||
|
|
||||||
reason:
|
|
||||||
"<why constraints must be bent>"
|
|
||||||
|
|
||||||
requested_exception:
|
|
||||||
"<rule being broken>"
|
|
||||||
|
|
||||||
justification:
|
|
||||||
"<why unavoidable>"
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Output Format
|
|
||||||
|
|
||||||
analysis:
|
|
||||||
|
|
||||||
alternatives_considered:
|
|
||||||
|
|
||||||
final_decision:
|
|
||||||
|
|
||||||
risk:
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
# 12. STOP CONDITIONS (always append)
|
|
||||||
|
|
||||||
```
|
|
||||||
Stop when:
|
|
||||||
- acceptance criteria are met
|
|
||||||
- code is minimal and correct
|
|
||||||
|
|
||||||
Do NOT:
|
|
||||||
- expand scope
|
|
||||||
- refactor unrelated code
|
|
||||||
- optimize prematurely
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
# END
|
|
||||||
@@ -1,296 +0,0 @@
|
|||||||
# CLAUDE_SPEC.md
|
|
||||||
|
|
||||||
version: 1.0
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 0. Global Rules
|
|
||||||
|
|
||||||
(Core determinism, panic policy, and event-driven engine constraints live in CLAUDE.md §2.1, §2.3, §3.1. Listed here only when they add information CLAUDE.md doesn't carry.)
|
|
||||||
|
|
||||||
rules:
|
|
||||||
|
|
||||||
* id: single_source_of_truth
|
|
||||||
description: "GameStateResource is the only mutable game state in runtime"
|
|
||||||
|
|
||||||
* id: sync_is_additive
|
|
||||||
description: "Remote data must never destructively overwrite local data"
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 1. Crate Graph
|
|
||||||
|
|
||||||
crates:
|
|
||||||
solitaire_core:
|
|
||||||
depends_on: [rand, serde, chrono]
|
|
||||||
forbidden_deps: [bevy, reqwest, tokio, std::fs]
|
|
||||||
|
|
||||||
solitaire_sync:
|
|
||||||
depends_on: [serde, serde_json, uuid, chrono]
|
|
||||||
role: "shared_types"
|
|
||||||
|
|
||||||
solitaire_data:
|
|
||||||
depends_on: [solitaire_core, solitaire_sync, reqwest, tokio, keyring]
|
|
||||||
role: "persistence_and_sync"
|
|
||||||
|
|
||||||
solitaire_engine:
|
|
||||||
depends_on: [bevy, kira, solitaire_core, solitaire_data]
|
|
||||||
role: "runtime_engine"
|
|
||||||
|
|
||||||
solitaire_server:
|
|
||||||
depends_on: [solitaire_sync, axum, sqlx, jsonwebtoken]
|
|
||||||
role: "backend"
|
|
||||||
|
|
||||||
solitaire_wasm:
|
|
||||||
depends_on: [solitaire_core, wasm-bindgen, serde-wasm-bindgen]
|
|
||||||
role: "wasm_replay_player"
|
|
||||||
|
|
||||||
solitaire_app:
|
|
||||||
depends_on: [solitaire_engine]
|
|
||||||
role: "entrypoint"
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2. Data Ownership
|
|
||||||
|
|
||||||
ownership:
|
|
||||||
GameState:
|
|
||||||
owner: solitaire_core
|
|
||||||
mutable_in: solitaire_engine
|
|
||||||
access_pattern: "via GameStateResource only"
|
|
||||||
|
|
||||||
StatsSnapshot:
|
|
||||||
owner: solitaire_data
|
|
||||||
|
|
||||||
PlayerProgress:
|
|
||||||
owner: solitaire_data
|
|
||||||
|
|
||||||
AchievementRecord:
|
|
||||||
owner: solitaire_data
|
|
||||||
|
|
||||||
SyncPayload:
|
|
||||||
owner: solitaire_sync
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3. State Transitions
|
|
||||||
|
|
||||||
state_machine:
|
|
||||||
GameState:
|
|
||||||
transitions:
|
|
||||||
- action: move_cards
|
|
||||||
returns: Result<GameState, MoveError>
|
|
||||||
|
|
||||||
```
|
|
||||||
- action: draw
|
|
||||||
returns: Result<GameState, MoveError>
|
|
||||||
|
|
||||||
- action: undo
|
|
||||||
returns: Result<GameState, MoveError>
|
|
||||||
|
|
||||||
invariants:
|
|
||||||
- "52 cards always exist"
|
|
||||||
- "no duplicate card IDs"
|
|
||||||
- "all cards belong to exactly one pile"
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4. Event System
|
|
||||||
|
|
||||||
events:
|
|
||||||
|
|
||||||
input:
|
|
||||||
- MoveRequestEvent
|
|
||||||
- DrawRequestEvent
|
|
||||||
- UndoRequestEvent
|
|
||||||
- NewGameRequestEvent
|
|
||||||
|
|
||||||
state:
|
|
||||||
- StateChangedEvent
|
|
||||||
- GameWonEvent
|
|
||||||
|
|
||||||
meta:
|
|
||||||
- AchievementUnlockedEvent
|
|
||||||
- SyncCompleteEvent
|
|
||||||
|
|
||||||
rules:
|
|
||||||
|
|
||||||
* "Input events trigger core logic"
|
|
||||||
* "Core logic emits state events"
|
|
||||||
* "UI reacts to state events only"
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 5. Sync Contract
|
|
||||||
|
|
||||||
sync:
|
|
||||||
|
|
||||||
provider_trait:
|
|
||||||
methods:
|
|
||||||
- pull() -> SyncPayload
|
|
||||||
- push(payload) -> SyncResponse
|
|
||||||
|
|
||||||
guarantees:
|
|
||||||
- "non-blocking during gameplay"
|
|
||||||
- "blocking allowed on exit only"
|
|
||||||
|
|
||||||
merge:
|
|
||||||
rules:
|
|
||||||
counters: "max"
|
|
||||||
best_times: "min"
|
|
||||||
collections: "union"
|
|
||||||
achievements: "never removed"
|
|
||||||
|
|
||||||
```
|
|
||||||
properties:
|
|
||||||
- deterministic
|
|
||||||
- idempotent
|
|
||||||
- lossless
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 6. Persistence
|
|
||||||
|
|
||||||
storage:
|
|
||||||
|
|
||||||
format: json
|
|
||||||
|
|
||||||
files:
|
|
||||||
- stats.json
|
|
||||||
- progress.json
|
|
||||||
- achievements.json
|
|
||||||
- settings.json
|
|
||||||
- game_state.json
|
|
||||||
|
|
||||||
guarantees:
|
|
||||||
- atomic_write: true
|
|
||||||
- crash_safe: true
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 7. Engine Rules
|
|
||||||
|
|
||||||
engine:
|
|
||||||
|
|
||||||
mutation_rules:
|
|
||||||
- "Only GameLogicSystem mutates GameState"
|
|
||||||
- "UI systems are read-only"
|
|
||||||
|
|
||||||
threading:
|
|
||||||
- "sync runs on AsyncComputeTaskPool"
|
|
||||||
- "main thread must never block"
|
|
||||||
|
|
||||||
plugins:
|
|
||||||
pattern: "feature_isolation"
|
|
||||||
communication: "events and resources"
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 8. Server Contract
|
|
||||||
|
|
||||||
server:
|
|
||||||
|
|
||||||
auth:
|
|
||||||
method: jwt
|
|
||||||
access_expiry: 24h
|
|
||||||
refresh_expiry: 30d
|
|
||||||
|
|
||||||
endpoints:
|
|
||||||
- POST /api/auth/register
|
|
||||||
- POST /api/auth/login
|
|
||||||
- GET /api/sync/pull
|
|
||||||
- POST /api/sync/push
|
|
||||||
|
|
||||||
limits:
|
|
||||||
payload_max: 1MB
|
|
||||||
rate_limit: "10 req/min auth routes"
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 9. Achievement System
|
|
||||||
|
|
||||||
achievements:
|
|
||||||
|
|
||||||
definition_location: solitaire_core
|
|
||||||
state_location: solitaire_data
|
|
||||||
|
|
||||||
types:
|
|
||||||
- condition_based
|
|
||||||
- event_driven
|
|
||||||
|
|
||||||
rule:
|
|
||||||
- "achievements cannot be revoked"
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 10. Testing Rules
|
|
||||||
|
|
||||||
testing:
|
|
||||||
|
|
||||||
philosophy:
|
|
||||||
- "test real failures"
|
|
||||||
- "avoid redundant tests"
|
|
||||||
|
|
||||||
required_coverage:
|
|
||||||
solitaire_core:
|
|
||||||
- move_validation
|
|
||||||
- undo_integrity
|
|
||||||
- win_detection
|
|
||||||
|
|
||||||
```
|
|
||||||
solitaire_sync:
|
|
||||||
- merge_correctness
|
|
||||||
- idempotency
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 11. Prohibited Patterns
|
|
||||||
|
|
||||||
(See CLAUDE.md §11 for the canonical forbidden-patterns list.)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 12. Extension Points
|
|
||||||
|
|
||||||
extensibility:
|
|
||||||
|
|
||||||
sync_backends:
|
|
||||||
pattern: "implement SyncProvider"
|
|
||||||
|
|
||||||
game_modes:
|
|
||||||
location: solitaire_core::GameMode
|
|
||||||
|
|
||||||
plugins:
|
|
||||||
rule: "new feature = new plugin"
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 13. Validation Checklist (for Claude)
|
|
||||||
|
|
||||||
validation:
|
|
||||||
|
|
||||||
* check: "crate dependency rules respected"
|
|
||||||
* check: "no panics in core"
|
|
||||||
* check: "events used for cross-system communication"
|
|
||||||
* check: "GameState mutations centralized"
|
|
||||||
* check: "merge function properties preserved"
|
|
||||||
* check: "no blocking operations in main loop"
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 14. Mental Model
|
|
||||||
|
|
||||||
model:
|
|
||||||
|
|
||||||
layers:
|
|
||||||
- core
|
|
||||||
- engine
|
|
||||||
- data
|
|
||||||
- server
|
|
||||||
|
|
||||||
flow:
|
|
||||||
- input -> engine -> core -> engine -> ui
|
|
||||||
- data <-> sync <-> server
|
|
||||||
@@ -1,335 +0,0 @@
|
|||||||
# CLAUDE_WORKFLOW.md
|
|
||||||
|
|
||||||
version: 1.0
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 0. Overview
|
|
||||||
|
|
||||||
This workflow defines a **two-agent system**:
|
|
||||||
|
|
||||||
* **Builder Agent** → writes and modifies code
|
|
||||||
* **Guardian Agent** → enforces architecture + rejects invalid changes
|
|
||||||
|
|
||||||
No code is considered valid unless it passes Guardian validation.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 1. Agent Roles
|
|
||||||
|
|
||||||
### 1.1 Builder Agent
|
|
||||||
|
|
||||||
role: "code_generation"
|
|
||||||
|
|
||||||
responsibilities:
|
|
||||||
|
|
||||||
* implement features
|
|
||||||
* refactor code
|
|
||||||
* generate tests (only when justified)
|
|
||||||
* follow CLAUDE_SPEC.md
|
|
||||||
|
|
||||||
constraints:
|
|
||||||
|
|
||||||
* cannot bypass validation
|
|
||||||
* must declare intent before writing code
|
|
||||||
|
|
||||||
output_contract:
|
|
||||||
must_produce:
|
|
||||||
- change_summary
|
|
||||||
- files_modified
|
|
||||||
- reasoning (short)
|
|
||||||
- code_diff
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 1.2 Guardian Agent
|
|
||||||
|
|
||||||
role: "architecture_enforcement"
|
|
||||||
|
|
||||||
responsibilities:
|
|
||||||
|
|
||||||
* validate against CLAUDE_SPEC.md
|
|
||||||
* detect violations
|
|
||||||
* reject or approve changes
|
|
||||||
* suggest minimal fixes (not full rewrites)
|
|
||||||
|
|
||||||
constraints:
|
|
||||||
|
|
||||||
* no feature implementation
|
|
||||||
* no large rewrites
|
|
||||||
* must be deterministic
|
|
||||||
|
|
||||||
output_contract:
|
|
||||||
must_produce:
|
|
||||||
- status: APPROVED | REJECTED
|
|
||||||
- violations[]
|
|
||||||
- required_fixes[]
|
|
||||||
- optional_improvements[]
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2. Workflow Pipeline
|
|
||||||
|
|
||||||
```text
|
|
||||||
User Request
|
|
||||||
↓
|
|
||||||
Builder Agent (proposal + code)
|
|
||||||
↓
|
|
||||||
Guardian Agent (validation)
|
|
||||||
↓
|
|
||||||
IF approved → commit
|
|
||||||
IF rejected → feedback → Builder retry
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3. Builder Protocol
|
|
||||||
|
|
||||||
### Step 1 — Intent Declaration
|
|
||||||
|
|
||||||
Builder MUST start with:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
intent:
|
|
||||||
feature: "<name>"
|
|
||||||
crates_touched: []
|
|
||||||
systems_affected: []
|
|
||||||
risk_level: low|medium|high
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Step 2 — Plan
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
plan:
|
|
||||||
- step: "..."
|
|
||||||
- step: "..."
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Step 3 — Implementation
|
|
||||||
|
|
||||||
* Only modify declared crates
|
|
||||||
* Follow ownership rules
|
|
||||||
* Use events for cross-system communication
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Step 4 — Output
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
change_summary: "..."
|
|
||||||
|
|
||||||
files_modified:
|
|
||||||
- path: ...
|
|
||||||
change: "..."
|
|
||||||
|
|
||||||
violations_self_check:
|
|
||||||
- none | list
|
|
||||||
|
|
||||||
notes: "short reasoning"
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4. Guardian Protocol
|
|
||||||
|
|
||||||
### Step 1 — Spec Validation
|
|
||||||
|
|
||||||
Check against:
|
|
||||||
|
|
||||||
* crate boundaries
|
|
||||||
* mutation rules
|
|
||||||
* event system usage
|
|
||||||
* sync guarantees
|
|
||||||
* forbidden patterns
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Step 2 — Invariant Validation
|
|
||||||
|
|
||||||
Must verify:
|
|
||||||
|
|
||||||
* GameState invariants preserved
|
|
||||||
* no new panic paths
|
|
||||||
* no blocking calls in engine
|
|
||||||
* merge properties unchanged
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Step 3 — Output Decision
|
|
||||||
|
|
||||||
#### APPROVED
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
status: APPROVED
|
|
||||||
|
|
||||||
notes:
|
|
||||||
- "no violations"
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### REJECTED
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
status: REJECTED
|
|
||||||
|
|
||||||
violations:
|
|
||||||
- id: core_purity_violation
|
|
||||||
file: "solitaire_core/src/..."
|
|
||||||
reason: "uses std::fs"
|
|
||||||
|
|
||||||
required_fixes:
|
|
||||||
- "move IO to solitaire_data"
|
|
||||||
|
|
||||||
optional_improvements:
|
|
||||||
- "simplify event naming"
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 5. Enforcement Rules
|
|
||||||
|
|
||||||
### Hard Fail (automatic rejection)
|
|
||||||
|
|
||||||
* core crate uses IO / Bevy / network
|
|
||||||
* GameState mutated outside GameLogicSystem
|
|
||||||
* blocking async on main thread
|
|
||||||
* duplicate logic across crates
|
|
||||||
* merge function altered incorrectly
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Soft Fail (allowed but flagged)
|
|
||||||
|
|
||||||
* unnecessary complexity
|
|
||||||
* redundant tests
|
|
||||||
* minor architectural drift
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 6. Iteration Loop
|
|
||||||
|
|
||||||
Max attempts per task: **3**
|
|
||||||
|
|
||||||
```text
|
|
||||||
Attempt 1 → Reject → Fix
|
|
||||||
Attempt 2 → Reject → Fix
|
|
||||||
Attempt 3 → Final decision
|
|
||||||
```
|
|
||||||
|
|
||||||
If still failing:
|
|
||||||
→ escalate to user
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 7. Diff Strategy
|
|
||||||
|
|
||||||
Builder MUST produce:
|
|
||||||
|
|
||||||
* minimal diffs
|
|
||||||
* no unrelated refactors
|
|
||||||
* no formatting-only changes
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 8. Test Strategy Integration
|
|
||||||
|
|
||||||
Builder rules:
|
|
||||||
|
|
||||||
* only add tests if:
|
|
||||||
|
|
||||||
* fixing a bug
|
|
||||||
* protecting complex logic
|
|
||||||
* validating invariants
|
|
||||||
|
|
||||||
Guardian rejects:
|
|
||||||
|
|
||||||
* redundant tests
|
|
||||||
* no-op tests
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 9. Optional Extensions
|
|
||||||
|
|
||||||
### 9.1 Third Agent (Optimizer)
|
|
||||||
|
|
||||||
role: performance + cleanup
|
|
||||||
|
|
||||||
runs AFTER approval:
|
|
||||||
|
|
||||||
* reduce allocations
|
|
||||||
* simplify logic
|
|
||||||
* improve ECS scheduling
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 9.2 CI Integration
|
|
||||||
|
|
||||||
Pipeline:
|
|
||||||
|
|
||||||
```text
|
|
||||||
Builder → Guardian → cargo check → clippy → tests
|
|
||||||
```
|
|
||||||
|
|
||||||
Guardian runs BEFORE compilation to catch structural issues early.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 10. Example Interaction
|
|
||||||
|
|
||||||
### Builder
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
intent:
|
|
||||||
feature: "undo stack limit fix"
|
|
||||||
crates_touched: [solitaire_core]
|
|
||||||
risk_level: low
|
|
||||||
```
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
change_summary: "limit undo stack to 64 entries"
|
|
||||||
|
|
||||||
files_modified:
|
|
||||||
- solitaire_core/src/game_state.rs
|
|
||||||
|
|
||||||
notes: "prevents unbounded memory growth"
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Guardian
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
status: APPROVED
|
|
||||||
|
|
||||||
notes:
|
|
||||||
- "respects core constraints"
|
|
||||||
- "no invariant violations"
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 11. Mental Model
|
|
||||||
|
|
||||||
* Builder = **creative**
|
|
||||||
* Guardian = **strict**
|
|
||||||
|
|
||||||
Builder explores
|
|
||||||
Guardian enforces
|
|
||||||
|
|
||||||
Neither replaces the other.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 12. Success Criteria
|
|
||||||
|
|
||||||
System is working if:
|
|
||||||
|
|
||||||
* architectural violations go to ~0
|
|
||||||
* code stays consistent across features
|
|
||||||
* refactors become safe
|
|
||||||
* complexity grows sub-linearly
|
|
||||||
@@ -4034,9 +4034,14 @@ checksum = "85ab80394333c02fe689eaf900ab500fbd0c2213da414687ebf995a65d5a6104"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
"byteorder-lite",
|
"byteorder-lite",
|
||||||
|
"color_quant",
|
||||||
|
"gif",
|
||||||
|
"image-webp",
|
||||||
"moxcms",
|
"moxcms",
|
||||||
"num-traits",
|
"num-traits",
|
||||||
"png 0.18.1",
|
"png 0.18.1",
|
||||||
|
"zune-core",
|
||||||
|
"zune-jpeg",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -7013,8 +7018,10 @@ dependencies = [
|
|||||||
"bevy",
|
"bevy",
|
||||||
"chrono",
|
"chrono",
|
||||||
"dirs",
|
"dirs",
|
||||||
|
"image",
|
||||||
"jni 0.21.1",
|
"jni 0.21.1",
|
||||||
"kira",
|
"kira",
|
||||||
|
"reqwest",
|
||||||
"resvg",
|
"resvg",
|
||||||
"ron",
|
"ron",
|
||||||
"serde",
|
"serde",
|
||||||
|
|||||||
@@ -110,6 +110,9 @@ ron = "0.12"
|
|||||||
# only `deflate` is needed because the importer rejects other
|
# only `deflate` is needed because the importer rejects other
|
||||||
# compression methods anyway (see Phase 7 spec).
|
# compression methods anyway (see Phase 7 spec).
|
||||||
zip = { version = "8.6", default-features = false, features = ["deflate"] }
|
zip = { version = "8.6", default-features = false, features = ["deflate"] }
|
||||||
|
# Image decoding for avatar bytes received from the server.
|
||||||
|
# Features mirror what Bevy already enables via bevy_image.
|
||||||
|
image = { version = "0.25", default-features = false, features = ["png", "jpeg", "webp", "gif"] }
|
||||||
|
|
||||||
# Importer-only test dependency: tests build zip archives in a
|
# Importer-only test dependency: tests build zip archives in a
|
||||||
# scratch directory so they don't pollute the real user themes path
|
# scratch directory so they don't pollute the real user themes path
|
||||||
|
|||||||
@@ -1,177 +0,0 @@
|
|||||||
# Ferrous Solitaire — Session Handoff
|
|
||||||
|
|
||||||
**Last updated:** 2026-05-12 — Leaderboard display name shipped (`03be4fc`). All commits pushed to origin.
|
|
||||||
|
|
||||||
Phase 8 closes the self-hosted-server connection arc end-to-end: login/register
|
|
||||||
modal, re-auth on token expiry, account deletion flow, server deployment
|
|
||||||
artifacts (Dockerfile + docker-compose), replay upload on win, web replay
|
|
||||||
player (WASM + HTML/CSS/JS served by the server), leaderboard opt-in/out,
|
|
||||||
and full server integration tests.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Current state
|
|
||||||
|
|
||||||
- **HEAD locally:** `03be4fc` (feat: leaderboard custom display name).
|
|
||||||
- **HEAD on origin:** `03be4fc` (fully pushed).
|
|
||||||
- **Working tree:** clean (only `solitaire-release.jks.bak2` untracked — intentional).
|
|
||||||
- **Build:** `cargo clippy --workspace --all-targets -- -D warnings` clean.
|
|
||||||
- **Tests:** **1300+ passing / 0 failing** across the workspace.
|
|
||||||
- **Tags on origin:** `v0.9.0` through `v0.22.0`.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## What shipped in Phase 8 (432061c – bd388fe)
|
|
||||||
|
|
||||||
| Commit | Summary |
|
|
||||||
|--------|---------|
|
|
||||||
| `432061c` | Sync setup modal (login/register/connect/disconnect) |
|
|
||||||
| `6ce5564` | Re-auth on expired session + server deployment artifacts |
|
|
||||||
| `272d31f` | Account deletion flow + `handle_sync_buttons` refactor |
|
|
||||||
| `bd388fe` | CHANGELOG v0.23.0 documentation |
|
|
||||||
|
|
||||||
Also shipped (pre-Phase 8 but post-v0.22.0, already in CHANGELOG):
|
|
||||||
- `solitaire_wasm` crate: WASM ReplayPlayer bindings for browser-side replay playback
|
|
||||||
- Server replay API: `POST /api/replays`, `GET /api/replays/recent`, `GET /api/replays/:id`
|
|
||||||
- Server web UI: `/replays/:id` HTML route + `ServeDir /web` static assets
|
|
||||||
- DB migration 002: `replays` table + two indexes
|
|
||||||
- Full server integration tests for replay endpoints
|
|
||||||
- `push_replay` in `sync_plugin` (uploads on win, writes share URL into replay history)
|
|
||||||
- Stats panel "Copy Share Link" button reads `share_url` from replay history
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Open punch list (ordered by priority)
|
|
||||||
|
|
||||||
### 1. Documentation debt (no code)
|
|
||||||
- [x] CHANGELOG [Unreleased] → v0.23.0 — done this session
|
|
||||||
- [x] ARCHITECTURE.md update — all 8 gaps closed, bumped to v1.3
|
|
||||||
- [x] SESSION_HANDOFF.md update — this file
|
|
||||||
|
|
||||||
### 2. Leaderboard wiring gaps
|
|
||||||
- [x] **Best-score auto-post.** Done (`303c78a`): `update_leaderboard_if_opted_in`
|
|
||||||
called from both first-push and merge paths in `sync.rs`; uses SQLite `MIN`/`MAX`
|
|
||||||
in the UPDATE so scores never regress on stale data.
|
|
||||||
- [x] **Display name = username.** Done (`03be4fc`): `leaderboard_display_name:
|
|
||||||
Option<String>` added to `Settings`; editor modal in leaderboard panel; persists
|
|
||||||
to `settings.json`; `handle_opt_in_button` prefers custom name over username.
|
|
||||||
|
|
||||||
### 3. Security hardening
|
|
||||||
- [x] **Refresh token rotation.** Done (`b129664`): `refresh_tokens` table
|
|
||||||
(migration 003); jti embedded in JWT; rotate-on-use pattern; 3 integration
|
|
||||||
tests.
|
|
||||||
- [x] **Sync endpoint rate limiting.** Done (`6e6f3ef`): `UserIdKeyExtractor`
|
|
||||||
decodes JWT for per-user identity; falls back to IP; burst 10 / 6 min
|
|
||||||
steady-state; integration test passes.
|
|
||||||
|
|
||||||
### 4. Android validation
|
|
||||||
- [x] **Android Keystore functional test.** Done (2026-05-11, Pixel 7 AVD,
|
|
||||||
Android 14): `load_access_token()` exercised via `start_pull`; logcat confirmed
|
|
||||||
`NotFound` returned cleanly — no JNI panic. See `docs/android/PLAYABILITY_TODO.md` P4.
|
|
||||||
- [x] **JNI clipboard functional test.** Done (2026-05-11): temporary `KEYCODE_C`
|
|
||||||
hook confirmed `ClipboardManager.setPrimaryClip()` succeeds on Android 14.
|
|
||||||
Hook reverted. Production path requires Interaction::Pressed + non-null `share_url`.
|
|
||||||
Note: `adb shell input tap` doesn't deliver touch events on headless AVD (documented).
|
|
||||||
- [x] **`cargo apk build --lib` noisy stderr** — upstream cargo-apk bug; `--lib`
|
|
||||||
is the canonical command (CLAUDE.md §15.1, docs/ANDROID.md). No in-repo fix possible.
|
|
||||||
|
|
||||||
### 5. Feature completeness
|
|
||||||
- [x] **Theme importer UI.** Done (`613bbf8`): "Scan for new themes" button in
|
|
||||||
Settings Appearance section. Shows import path label, scans user_theme_dir()
|
|
||||||
for .zip archives, fires InfoToastEvent per file, refreshes ThemeRegistry.
|
|
||||||
- [x] **`mirror_achievement` removed.** Done (`549a817`): method was a no-op
|
|
||||||
default never overridden and never called; achievements already sync via
|
|
||||||
`SyncPayload` push. Deleted from trait and blanket impl.
|
|
||||||
- [x] **WASM build script.** Done (`40d0712`): `build_wasm.sh` at repo root
|
|
||||||
documents `wasm-pack build --target web`, cleans up pkg metadata files,
|
|
||||||
includes dependency guard + install instructions.
|
|
||||||
- [x] **Server password reset.** Done (`7514684`): `--reset-password <username>`
|
|
||||||
subcommand reads new password from stdin, bcrypt-hashes it, invalidates all
|
|
||||||
active sessions for the user.
|
|
||||||
|
|
||||||
### 5b. Android UX polish (2026-05-12)
|
|
||||||
|
|
||||||
- [x] **UX-1 — Modal Done button in gesture zone.** `apply_safe_area_to_modal_scrims` system
|
|
||||||
added to `SafeAreaInsetsPlugin` (`safe_area.rs`). Pads every `ModalScrim` bottom by
|
|
||||||
`insets.bottom / scale`. Fires on resource change + `Added<ModalScrim>`. Verified on device.
|
|
||||||
- [x] **UX-5b — Home mode glyph corruption.** Geometric Shapes (U+25xx, absent from FiraMono)
|
|
||||||
replaced with card suits U+2660–2666 in `home_plugin.rs`. Affects Zen/Challenge/Daily mode
|
|
||||||
selector buttons at level 5+.
|
|
||||||
- [x] **UX-7 — Help text wrap.** Android HUD entry shortened to
|
|
||||||
`"Open menu (Stats, Settings, Profile...)"` in `help_plugin.rs` — fits one line.
|
|
||||||
- [x] **BUG-3 — Multi-modal stacking.** `handle_menu_button` now checks
|
|
||||||
`scrims: Query<(), With<ModalScrim>>` and guards `spawn_menu_popover` with `scrims.is_empty()`.
|
|
||||||
Verified on device: ≡ tap while Stats open does nothing.
|
|
||||||
|
|
||||||
**Note:** These 4 fixes are implemented and verified but not yet committed.
|
|
||||||
|
|
||||||
### 6. Testing gaps
|
|
||||||
- [x] **Server 401 → refresh → retry path.** Done (`198df75`): both
|
|
||||||
`jwt_refresh_on_401_succeeds` (pull) and
|
|
||||||
`push_retries_after_401_on_expired_access_token` (push) in
|
|
||||||
`solitaire_data/tests/sync_round_trip.rs`.
|
|
||||||
- [x] **WASM winning-replay step-through.** Done (`b4ada2a`): greedy solver
|
|
||||||
searches seeds 1–200 at test time; steps every move through `ReplayPlayer`;
|
|
||||||
asserts `is_won = true` on the final `StateSnapshot`.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ARCHITECTURE.md gaps (for the update pass)
|
|
||||||
|
|
||||||
Items missing from the doc:
|
|
||||||
1. `solitaire_wasm` crate (§2 workspace + §3 responsibilities)
|
|
||||||
2. Replay API endpoints (§9 API Reference — 3 new routes)
|
|
||||||
3. Web replay player route (`/replays/:id` + `ServeDir /web`)
|
|
||||||
4. `SyncProvider` trait: 6 added methods
|
|
||||||
5. Theme system in Bevy plugin table (§5)
|
|
||||||
6. `Settings` new fields: `color_blind_mode`, `high_contrast_mode`,
|
|
||||||
`reduce_motion_mode`, `window_geometry`, `selected_card_back`,
|
|
||||||
`selected_background`
|
|
||||||
7. DB migration 002 (§7)
|
|
||||||
8. Update "Last Updated" date
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Process notes
|
|
||||||
|
|
||||||
- **Commit attribution:** use `funman300` as git user. Co-author line:
|
|
||||||
`Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>`.
|
|
||||||
- **Commit format:** `type(scope): description` per CLAUDE.md §7.
|
|
||||||
- **Never commit without:** `cargo test --workspace` passing + clippy clean.
|
|
||||||
- **Sub-agents** stage/verify only; orchestrator commits.
|
|
||||||
- **`CARD_PLAN.md`** referenced in `theme/` module comments but not present in
|
|
||||||
repo. Clean up references or commit the file.
|
|
||||||
- **Token-port pattern** (v0.20.0): when migrating tokens, walk every concrete
|
|
||||||
artifact downstream — PNGs, SVGs, literals, comments. Three "walked past this"
|
|
||||||
follow-ups in v0.21.0 all had this shape.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Resume prompt
|
|
||||||
|
|
||||||
```
|
|
||||||
You are a senior Rust + Bevy developer working on Ferrous Solitaire.
|
|
||||||
Working directory: <Rusty_Solitaire clone path>.
|
|
||||||
Branch: master. v0.23.0 is the current version (HEAD: 03be4fc). Fully pushed.
|
|
||||||
|
|
||||||
READ FIRST (in order):
|
|
||||||
1. SESSION_HANDOFF.md — this file
|
|
||||||
2. CHANGELOG.md — [0.23.0] section has full Phase 8 detail
|
|
||||||
3. CLAUDE.md — unified-4.0 rule set
|
|
||||||
4. ARCHITECTURE.md — v1.3, fully up to date
|
|
||||||
5. docs/ui-mockups/ — design system + mockup library
|
|
||||||
6. docs/android/ — Android setup + build runbook
|
|
||||||
7. ~/.claude/projects/<this-project>/memory/MEMORY.md
|
|
||||||
|
|
||||||
OPEN WORK:
|
|
||||||
Phase 8 punch list is fully closed. All items verified complete.
|
|
||||||
Remaining nuisance: `cargo apk build --lib` noisy stderr (cosmetic, non-blocking).
|
|
||||||
|
|
||||||
4 Android UX fixes are implemented and verified but NOT YET COMMITTED:
|
|
||||||
- BUG-3 (hud_plugin.rs): multi-modal stacking guard
|
|
||||||
- UX-7 (help_plugin.rs): help text wrap on Android
|
|
||||||
- UX-5b (home_plugin.rs): FiraMono glyph corruption in mode selector
|
|
||||||
- UX-1 (safe_area.rs): modal Done button in gesture zone
|
|
||||||
|
|
||||||
Commit those first, then suggest Phase 9 planning.
|
|
||||||
```
|
|
||||||
|
Before Width: | Height: | Size: 3.1 KiB |
|
After Width: | Height: | Size: 6.3 KiB |
|
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 3.1 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 4.3 KiB |
|
Before Width: | Height: | Size: 3.9 KiB |
|
Before Width: | Height: | Size: 3.6 KiB |
|
Before Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 3.6 KiB |
|
Before Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 3.6 KiB |
|
Before Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 4.0 KiB |
|
Before Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 4.3 KiB |
|
Before Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 3.6 KiB |
|
Before Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 3.6 KiB |
|
Before Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 4.0 KiB |
|
Before Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 4.3 KiB |
|
Before Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 3.6 KiB |
|
Before Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 6.8 KiB |
|
After Width: | Height: | Size: 5.4 KiB |
|
After Width: | Height: | Size: 6.8 KiB |
|
After Width: | Height: | Size: 6.8 KiB |
|
After Width: | Height: | Size: 6.0 KiB |
|
After Width: | Height: | Size: 4.7 KiB |
|
After Width: | Height: | Size: 6.1 KiB |
|
After Width: | Height: | Size: 6.1 KiB |
|
After Width: | Height: | Size: 6.3 KiB |
|
After Width: | Height: | Size: 5.0 KiB |
|
After Width: | Height: | Size: 6.4 KiB |
|
After Width: | Height: | Size: 6.4 KiB |
|
After Width: | Height: | Size: 5.6 KiB |
|
After Width: | Height: | Size: 4.2 KiB |
|
After Width: | Height: | Size: 5.7 KiB |
|
After Width: | Height: | Size: 5.7 KiB |
|
After Width: | Height: | Size: 6.0 KiB |
|
After Width: | Height: | Size: 4.6 KiB |
|
After Width: | Height: | Size: 6.0 KiB |
|
After Width: | Height: | Size: 6.1 KiB |
|
After Width: | Height: | Size: 6.4 KiB |
|
After Width: | Height: | Size: 5.0 KiB |
|
After Width: | Height: | Size: 6.5 KiB |
|
After Width: | Height: | Size: 6.5 KiB |
|
After Width: | Height: | Size: 5.6 KiB |
|
After Width: | Height: | Size: 4.2 KiB |
|
After Width: | Height: | Size: 5.7 KiB |
|
After Width: | Height: | Size: 5.7 KiB |
|
After Width: | Height: | Size: 6.6 KiB |