- rename .forgejo/workflows/ to .gitea/workflows/ (git mv preserves history); Gitea Actions reads either path, but the directory now matches the active platform - collapse ANDROID_STORE_PASSWORD + ANDROID_KEY_PASSWORD into a single ANDROID_KEYSTORE_PASSWORD secret (PKCS12 keystores require the two passwords to be identical, so the duplication carried no information); build.gradle.kts still reads two env vars to stay format-agnostic, both now sourced from the same secret in CI - ignore android/*.keystore, android/*.keystore.*, android/*.jks, android/keystore.properties so the regenerated signing material never reaches a remote on accident - update prose references from Forgejo to Gitea in CLAUDE.md and the docs; the historical "migrated from Forgejo" note in CLAUDE.md is kept intentionally; forgejo/upload-artifact@v3 action refs are left untouched (canonical artifact action, resolves cleanly under Gitea Actions) Operator side: ANDROID_KEY_ALIAS, ANDROID_KEYSTORE_PASSWORD, and ANDROID_KEYSTORE_B64 secrets registered in Gitea before this lands.
4.2 KiB
Minstrel — Claude working instructions
Porting discipline (HARD RULE — overrides default behavior)
The Android native client (android/) is a port of the Flutter client
(flutter_client/). The Flutter client is the source of truth for behavior,
layout, copy, and data flow. When building or fixing any Android feature that
exists in Flutter:
-
Read the Flutter source FIRST. Open the actual
flutter_client/lib/...file(s) for the feature before writing any Kotlin. Do not work from memory, from a summary, or from an assumption about "the shape." If you have not opened the Flutter file in this session, you have not earned the right to write the Android version. -
Replicate the user-visible behavior exactly; implement it idiomatically. Match what the user sees and feels: layout structure, section order, empty/ loading/error states, copy strings, edge cases, and — critically — the responsiveness (the app caches to make the UI feel instant; preserve that). But you do NOT have to copy Flutter's implementation. Flutter uses drift
watch()+ Riverpodinvalidate; the well-supported Android idioms are Room + Flow, Compose state, WorkManager, Media3. Prefer the native mechanism that delivers the same or better UX over a literal transliteration of the Dart. Exact on behavior + feel; idiomatic on structure. If a more native approach genuinely improves the experience, do that (and note it in the parity map as an intentional, better-supported divergence). -
Never silently substitute a different design. If the faithful port seems hard, blocked, or impossible, STOP and verify by reading more of the Flutter source + the Android data layer. The blocker is usually wrong — the data or API you think is missing is often already there (e.g.
audio_cache_indexalready carriedlastPlayedAt; the offline pool was never actually blocked). If after reading it is genuinely blocked, raise it as a question rather than shipping a scoped-down or alternative design. -
Quote the Flutter file when you start a feature. Lead the work with "Here's
flutter_client/lib/<path>— here's what it does — here's the Android port," so divergence is caught before code is written, not after. -
Keep the parity map current.
docs/superpowers/parity-map.mdmaps every feature → Flutter source path → Android target → status. Re-read the relevant row before porting; update it after. This is the durable reference — rely on it, not on session memory, because context gets compacted on long sessions.
Repo conventions (already in force)
- No GitHub — Gitea only. PR/issue ops via the gitea MCP (server lives at
git.fabledsword.com, migrated from Forgejo); CI runs under
.gitea/workflows/. Never useghor github.com URLs. - Git flow: work on
dev→ PR to protectedmain→ tag release. Never push tomain. - No in-task tests/builds. Do not run flutter/gradle/npm
test/build/analyzeduring implementation — CI verifies. Codegen scripts only. - CI is operator-side. After
git push origin dev, the operator reports the result; don't poll Gitea. - Specs/plans/audits/parity-map live local.
docs/superpowers/is.gitignored— save there for operator review; never commit those. - detekt gates CI. Watch the recurring ones: 60-line
LongMethod, 11-function-per-fileTooManyFunctions(use@file:Suppresswith a one-line rationale for Compose-helper density),ReturnCount≤ 2,MagicNumber,MatchingDeclarationName. Keep lines ≤ 100 chars.
Android port shape (quick reference)
- Screens:
*Screen.ktwith the@HiltViewModelinline at the top of the file. - Repositories:
*/data/*Repository.kt. Wire models:models/wire/*Wire.kt. Domain models:models/*.kt. - App-lifetime singletons start via the "construct-the-singleton trick" — an
@Inject lateinit varinMinstrelApplicationwhoseinit {}wires up. - Server errors are
{"error":{"code":"...","message":"..."}}; surface them throughErrorCopy.fromThrowable(e), never rawe.message. - Cross-device reactivity: collect
EventsStream.eventsfiltered bykind.