Imported from noamvb/cannsheet-mobile (
AGENTS.md). Install upstream withnpx skills add noamvb/cannsheet-mobile. Copyright stays with the author.
Cannsheet Mobile contributor instructions
Project overview
Cannsheet Mobile is a personal Android app for recording cannabis purchases and consumption. It is a single-module Kotlin application with a Compose UI, Room storage, an offline action queue, and a Google Apps Script/Google Sheets backend.
Required reading
Before substantial work, read:
README.mdandCONTRIBUTING.md;docs/PROJECT_STATE.mdfor the verified current state;docs/ARCHITECTURE.mdfor system boundaries and data flows;docs/DECISIONS.mdfor durable decisions; anddocs/HANDOFF.mdfor the latest cross-session transfer state.
Treat code and configuration as stronger evidence than documentation. If they disagree, verify the behavior and update the shared-context documents.
Stack and repository structure
- Kotlin, Jetpack Compose/Material 3, Navigation Compose, coroutines/StateFlow, Room/KSP, and DataStore Preferences.
- Retrofit, OkHttp, and Moshi communicate with the configured Google Apps
Script endpoint;
backend_additions.gsis the checked-in server source. app/src/main/java/com/example/ui: screens, navigation, UI state, and view models.app/src/main/java/com/example/data: Room, repositories, network contracts, preferences, synchronization rules, and analytics caching.app/src/test: local JVM tests;app/src/androidTest: device/emulator tests.tests: dependency-free Node.js backend tests and a Python benchmark test..github/workflows: pull-request validation and explicitly triggered release automation.
Keep detailed architecture and changing implementation status in the linked documents, not in this file.
Coding and documentation conventions
- Follow Kotlin's official code style and the existing Compose/data-layer split.
- Prefer immutable network and queue payloads, stable UUIDs, narrow responsibilities, and regression tests near the affected boundary.
- Keep one coherent change per pull request; put unrelated cleanup elsewhere.
- Use repository-relative paths in documentation.
- Do not invent test results, live deployment state, decisions, or rationale.
- Keep
docs/PROJECT_STATE.md,docs/DECISIONS.md, anddocs/HANDOFF.mdcurrent when their subject matter changes. - Keep vendor adapters such as
GEMINI.mdconcise. They should point agents to the canonical shared-context documents instead of duplicating project facts. - For Android adaptive icons, keep
<monochrome>drawables (ic_launcher_monochrome.xml) transparent (#00000000) except for emblem/symbol paths (#000000) so Android 13+ Material You themed icon tinting renders cleanly without solid background blobs. - For Jetpack Compose
DatePickercomponents, always use UTC-based date helpers (pickerDateToWire,currentLocalDateAsPickerMillis,parsePickerDateToMillis) inConsumptionDateTime.ktwhen converting betweenDatePickerState.selectedDateMillisand ISO"yyyy-MM-dd"strings. Never formatselectedDateMillisusing device-localSimpleDateFormatwithout setting UTC timezone, as midnight UTC shifts to the prior day in negative UTC offset timezones. - For the pen home-screen widget, quantities are displayed in seconds but must be converted with
secondsToUsesbefore reaching Room, the offline queue, or the wire; the undo window defers the Room write, the captured payload is removed only after the Room write is durable, and Undo never deletes a queued row. - NFC quick-log tags may carry only protocol version, the registered tag UUID, and whole uses
1..10. The handler must require an exact local registry UUID/quantity match; no product, endpoint, date, label, event ID, or seconds may be placed on the tag. NFC pending-payload removal remains gated on durable Room persistence, and direct-uses Undo never deletes a Room row. - Queue-integrity alerts are advisory only. Evaluation may read the existing aggregate pending-action count, but no notification or presenter may receive queue rows or entry details, and no alert path may write, acknowledge, or delete a queue row. Notification content must never include product names, quantities, or dates.
- Runway and spend projections are presentation-only estimates derived from
InsightsResponseDto. They must not be persisted, transmitted, or treated as confirmed values. In-app surfaces must degrade to showing nothing when the Insights snapshot is cached, stale, changing range, or incomplete because a local action is pending. A home-screen widget may instead render a cached projection, but only when it displays the snapshot's own as-of date alongside the figure, and it must still show nothing when no snapshot has ever been cached. - The Log screen may consume the Insights snapshot but must not drive unbounded analytics refreshes; runway-only refreshes are floored.
- Month and day arithmetic on analytics data uses the response's own
timeZoneandrangefields, never a device-localCalendarorLocalDate.now(). EXTRA_START_ROUTElives incom.example.domainand its string value is part of already-issued widgetPendingIntents; do not change it.
Change and release rules
- Propose feature and bug-fix work through a pull request targeting
main. - Keep each pull request to one coherent change. Put unrelated cleanup in another pull request.
- Do not change
versionCodeorversionName, create a tag or release, build a signed release, or modify signing configuration unless the task explicitly requests release work. - Do not change the production Apps Script endpoint, application ID, package/namespace, environment IDs, credentials, or secrets unless the task explicitly requests that exact change.
- Never commit keystores, credentials, tokens,
sandbox.properties, or other local secrets.
Verified checks
The project requires JDK 17 or newer, Gradle 9.3.1 through the wrapper, Android
SDK Platform 36.1, and Android Build Tools 36.0.0. Select a compatible JDK in
Android Studio or through JAVA_HOME.
On Windows:
.\gradlew.bat --no-daemon testDebugUnitTest assembleDebug
.\gradlew.bat --no-daemon lintDebug
node tests/backend_analytics_test.js
On macOS, Linux, or GitHub Actions, use ./gradlew for the same Gradle tasks.
Additional checked-in backend suites can be run directly with Node:
node tests/backend_contract_test.js
node tests/backend_corrections_test.js
node tests/backend_recovery_test.js
node tests/backend_spreadsheet_test.js
node tests/fake_sheets_batch_update_test.js
node tests/sandbox_performance_fixture_test.js
node tests/sandbox_provisioning_test.js
python -m unittest tests/test_backend_sync_benchmark.py
The JavaScript tests use Node built-ins and checked-in fake runtimes; there is no npm install step. No separate formatting or type-check command is configured. Gradle compilation and Android lint provide the available Kotlin checks.
Run the checks relevant to every change. Report every test, build, lint, device check, or manual check that was not run or did not pass; do not describe an unexecuted check as successful.
For a visible UI change, include screenshots or a short recording in the pull request. If screenshots cannot be produced, state why and describe the manual visual check that was performed instead.
Data safety and regression coverage
- Treat Room migrations and the offline purchase, consumption, and finish queues as user data. Preserve existing rows and stable IDs unless an explicitly approved migration requires otherwise.
- All queue synchronization must go through
SyncEngineunderCannsheetGraph.syncMutex. - Never use destructive database fallback as a shortcut. Add a forward migration and test upgrades from every supported prior schema when the schema changes.
- Preserve synchronization idempotency, request/action/event UUIDs, environment checks, acknowledgement handling, locking, and retry behavior. A timeout must not cause duplicate spreadsheet rows or silently discard a queued action.
- Delete a pending purchase, consumption, or finish action only after the existing acknowledgement rules prove that the server committed it or already has the same immutable ID.
- Keep spreadsheet writes narrow and recoverable. Consider partial writes, retries, concurrent requests, duplicate delivery, and reconciliation before changing Apps Script write paths.
- Treat purchase, consumption, inventory, analytics, and deletion behavior as data-sensitive. Document any destructive effect and provide a rollback or recovery path.
- Add regression tests whenever changing Room persistence or migrations, synchronization, analytics normalization/caching/pagination, spreadsheet write behavior, deletion, or another destructive path.
Pull request and self-review requirements
Every pull request description must include:
- summary;
- motivation or root cause;
- important implementation decisions;
- automated tests run and their exact results;
- manual validation performed;
- risks and data-safety considerations; and
- screenshots or recordings for visible UI changes, or an explanation of why they are absent.
Before committing, review the complete diff. Remove unrelated changes and confirm that it contains no secrets, accidental version changes, release/tag changes, signing changes, or unintended production endpoint, application ID, package, or environment changes.
Completion and handoff protocol
Before declaring work complete or transferring it to another account/session:
- Review the complete final diff.
- Run the relevant available validation.
- Record the exact commands and outcomes.
- Report validation that could not be performed; never imply it passed.
- Update
docs/PROJECT_STATE.mdwhen implementation state changes. - Record meaningful durable decisions in
docs/DECISIONS.md. - Refresh
docs/HANDOFF.mdbefore transferring work. - Check that no secrets, credentials, generated artifacts, or unrelated files were introduced.
Use .agents/skills/project-handoff/SKILL.md when preparing a formal
cross-account or cross-session handoff.
Use .agents/skills/ship-release/SKILL.md when the task explicitly requests
release work — that skill carries the branch, validation, exact-SHA main proof,
version bump, tag, publication, and phone-update steps, including the
requirements that make a tag publishable.