Files
minstrel/CLAUDE.md
T
bvandeusen 3cf829752b
flutter / analyze-test-build (push) Failing after 2s
test-go / test (push) Successful in 33s
android / Build + lint + test (push) Failing after 41s
test-web / test (push) Successful in 41s
android / Build signed release APK (push) Has been skipped
test-go / integration (push) Successful in 11m16s
chore(ci): migrate workflows to gitea + consolidate keystore secret
- 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.
2026-05-31 23:05:03 -04:00

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:

  1. 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.

  2. 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() + Riverpod invalidate; 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).

  3. 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_index already carried lastPlayedAt; 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.

  4. 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.

  5. Keep the parity map current. docs/superpowers/parity-map.md maps 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 use gh or github.com URLs.
  • Git flow: work on dev → PR to protected main → tag release. Never push to main.
  • No in-task tests/builds. Do not run flutter/gradle/npm test/build/ analyze during 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-file TooManyFunctions (use @file:Suppress with a one-line rationale for Compose-helper density), ReturnCount ≤ 2, MagicNumber, MatchingDeclarationName. Keep lines ≤ 100 chars.

Android port shape (quick reference)

  • Screens: *Screen.kt with the @HiltViewModel inline 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 var in MinstrelApplication whose init {} wires up.
  • Server errors are {"error":{"code":"...","message":"..."}}; surface them through ErrorCopy.fromThrowable(e), never raw e.message.
  • Cross-device reactivity: collect EventsStream.events filtered by kind.