Encodes the hard rule the operator asked for after repeated divergence: when porting/changing any Android feature that exists in Flutter, read the Flutter source FIRST and replicate it exactly; never silently substitute a different design or scope down — verify the perceived blocker by reading more, and raise it as a question if genuinely blocked. Points at docs/superpowers/parity-map.md as the durable feature→source→target→status reference (gitignored, local). Also captures the standing repo conventions (Forgejo-only, dev→main flow, no in-task builds, detekt gates) so they survive context compaction. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
3.5 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 it exactly. Match the behavior, the layout structure, the section order, the empty/loading/error states, the copy strings, the data sources, and the edge cases. Same number of rows, same fields per row, same tap targets.
-
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 — Forgejo only. PR/issue ops via the forgejo MCP; CI runs under
.forgejo/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 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-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.