Files
minstrel/CLAUDE.md
T
bvandeusen 942d7e88cc docs: refine porting rule — exact on behavior+feel, idiomatic on implementation
Operator guidance: parity is about the user experience (layout, copy,
states, and especially responsiveness — caching exists to make the UI
feel instant), not literal transliteration of Flutter's Dart. Replicate
the behavior faithfully but reach it with the best well-supported native
mechanism (Room+Flow, Compose, WorkManager, Media3) rather than copying
drift watch() / Riverpod invalidate. A more native approach that improves
the UX is preferred — recorded as an intentional divergence in the parity
map.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 08:23:29 -04:00

4.1 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 — Forgejo only. PR/issue ops via the forgejo MCP; CI runs under .forgejo/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 Forgejo.
  • 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.