wtclaude c7c49a9d6b feat(theme): scale the shard's radii and card depth onto the app's scale (M12 phase 2)
The structure half of the admin's Appearance page. ShardStructure.resolve() turns
the four --radius-* tokens and --shadow-card into a Material shape scale, a pill
shape and a card elevation; RunicGatewayTheme feeds the scale to MaterialTheme and
the other two to a LocalShardStructure, mirroring phase 1's palette split.

Radii are applied as a ratio, never as a literal. The app's Shapes came from the
M5 mockup and the website's from theme.css, and the two scales differ - copying
the web value in would have restyled an untouched app on day one. Each field is
scaled by resolved / runic-gateway baseline instead, so the shipped theme and an
explicit runic-gateway both give ratio 1.0 and are provable no-ops.

Three things the spec did not survive contact with:

Card depth is not a no-op, and that is the org lead's decision. Material3's
filled Card is Level0 and FeatureCard drew none of the shadow its own docs
claimed, so the app has been flat since M5 - while the preset it was drawn from
selects the "Default" shadow. Section 5.4 is applied as written rather than
rebased on the flat baseline, which would have collapsed three of the admin's
four choices onto 0dp. Every card gains 4dp; sections 2, 5.4 and AC-1 record it.

The shadow is matched by nearest blur, not by exact string. The fantasy preset
publishes a --shadow-card that SHADOW_OPTIONS does not contain, because a
preset's tokens are copied verbatim and never pass through the admin dropdown -
an exact match would have missed the one preset whose point is a heavier shadow.

--radius-pill is resolved as a literal px, because CircleShape is a percentage
and has no shipped dp for a ratio to scale. It reaches exactly one composable:
the app's other two CircleShape uses are 8dp status dots, and a dot stays a dot.

ShardCard exists because Material's theme cannot carry elevation - Card takes it
as a default argument. All 24 Card( call sites across 20 files moved to the
wrapper, which is mechanical because every one of them passed only a modifier. A
Card( outside ThemeComponents.kt is now, by construction, an unthemable card.

Shapes does implement equals (unlike ColorScheme), so the structural no-op proof
is one assertion against a verbatim copy of the pre-M12 scale. 13 new tests, 386
green, lintDebug and assembleDebug clean.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-08 05:15:08 -05:00
2026-07-19 01:38:07 -05:00
2026-07-19 01:38:07 -05:00
2026-07-19 01:38:07 -05:00
2026-07-19 01:38:07 -05:00
2026-07-19 01:38:07 -05:00

Runic Gateway — Android app

A native Android client for a Runic Gateway shard's public site + player self-service. It is purely an API client of the website backend — it never talks to the link/ sidecar or the game shard directly, and it ships none of the shard/sidecar wiring. It surfaces the same content and player features as the website's browser client, minus every administrative/management console.

The authoritative design contract is docs/android/PLAN.md in the RunicGateway/docs repo. The authoritative API reference is the committed OpenAPI spec at website/server/swagger/swagger-output.json.

Status

M0 — repo scaffold. Gradle + Compose + Hilt skeleton with CI (lint + unit test + debug build). The functional Kotlin pass (M1M4) and the design pass (M5) follow — see the plan's milestones (§9).

Stack

Concern Choice
Language / UI Kotlin + Jetpack Compose (Material 3)
Navigation Navigation-Compose, single-activity
HTTP Retrofit + OkHttp, kotlinx.serialization
Async Coroutines + Flow
DI Hilt
Prefs / base URL Jetpack DataStore
Tokens at rest EncryptedSharedPreferences
Images Coil
Min SDK Android 10 (API 29)
Target / compile SDK 35

Dependency and plugin versions are pinned in gradle/libs.versions.toml.

Build

Requires JDK 17 and the Android SDK (ANDROID_HOME / local.properties).

./gradlew assembleDebug     # build a debug APK -> app/build/outputs/apk/debug/
./gradlew test              # JVM unit tests
./gradlew lint              # Android lint
./gradlew installDebug      # install on a connected device/emulator

The app self-configures its server URL on first run (PLAN.md §3), so a single build works against any shard's website — there is no compiled-in API host.

CI

.gitea/workflows/pr-checks.yml gates PRs into main with ./gradlew lint test assembleDebug on the org's self-hosted runner (JDK 17 + Android SDK). Debug builds are auto-signed, so the gate needs no secrets. This pipeline is verified green end-to-end on the runner (M0). A signed release APK attached to a Gitea release comes at M6.

The workflow carries a few runner-specific accommodations (each explained in comments in the file), because this self-hosted runner differs from a stock GitHub runner:

  • JDK 17 is installed via apt (not actions/setup-java) — the runner can't resolve api.adoptium.net, while the Ubuntu mirrors are reachable.
  • SDK packages are installed explicitly via sdkmanager, with set +o pipefail so yes dying of SIGPIPE doesn't fail the step.
  • gradlew is chmod +x'd in the run step — the runner's checkout does not preserve the git executable bit, so ./gradlew alone fails with "Permission denied".

Contributing

See CONTRIBUTING.md. AI-assisted contributions must be disclosed (org policy): tick the PR box naming the tool and add a Co-Authored-By trailer to AI-authored commits. Licensed GPL-3.0-or-later.

Description
No description provided
Readme 10 MiB
v0.5.0 Latest
2026-08-08 16:28:50 +00:00
Languages
Kotlin 99.8%
Python 0.2%