Guidance for coding agents working in this repository. Humans: see CONTRIBUTING.md; everything there applies to agents too.
- Read the issue the change belongs to and its acceptance criteria, and the checklist of the pull request template.
- Run
./gradlew detekt ktlintCheck lint alohaArchitectureCheck alohaUnitTests alohaScreenshotTestsbefore and after; a change is finished when it is green. - After changing a constructor or any non-private signature, compile every test
source set (
./gradlew compileDebugUnitTestKotlin :app:compileGenericDebugUnitTestKotlin), not only the module's own: tests elsewhere build the class by hand. - docs/12-conventions-quality.md describes the test layers and every CI job.
- Follow the module graph (
ModuleRules.kt); add a row there in the same change when adding a module. - Verify every new dependency and version against Maven Central or Google Maven
before using it, and add it to
gradle/libs.versions.toml, never inline. - Never add a baseline entry to make a check pass.
- Server behaviour comes from the fixture corpus and the live dev instance, not from assumptions; name the server behaviour in a comment when code works around it.
- Commits use Conventional Commits,
git commit -s -S, and anAssisted-by: <tool>:<model>trailer. NoCo-Authored-Bytrailer, no session links. - Never leave two consecutive blank lines in a file.
- Data from anywhere but the reader's own server keeps that server's ids apart: every id a post carries (its own, its account's, its poll's, the one it answers, its quote) is namespaced or dropped, never sent to the reader's server.
- Acting for another account, or on another server's copy of a post, sets the state wanted (boosted, favourited, bookmarked) and skips it when it holds; it never flips a state the app cannot see.
- A request that failed answers
null, not an empty list or set. Nothing is marked done, dropped or filtered on an answer that may have been a failure. - A change to stored settings is made inside the store's
update, from what is stored then, not from aStateFlowsnapshot; a change across several stores holds aMutex. - An address found by discovery (host-meta, redirects, links) passes the same
checks as one typed: https, a host name, no IP address or
localhost. - Status rows are built in a view model through
StatusRowCache, never with aStatusRowMapperorRichTextCachemade in a composable; a row the screen shows is one its actions can find. rememberandpointerInputare keyed on stable ids, not on data objects that change while in use; per-item state (scroll positions, drags) survives the list around it changing.- A row in a keyed lazy list that can leave the list and come back (removed,
then undone) holds no
rememberSaveablestate such asrememberSwipeToDismissBoxState: the list restores it under the same key, and the row returns as it left, swiped away and removed again. - An object put into a
CompositionLocalis made once and reads its values throughState, so a value changing recomposes only what reads it. - Every animation goes through
motion()or checksrememberReducedMotion(). - Every long press, drag and swipe has a custom accessibility action that does the same; a handle carries no label of its own; a live region says a result, not every keystroke; a card shown only for its looks has its semantics cleared.
- A screen that replaces another removes the code paths only the old one used.