Code Conventions
How to write code that fits Scanly's architecture.
Layer rules
| Layer | May call | Must not call |
|---|---|---|
feature/ | domain/usecase/, core/ui/ | Room DAOs, DataStore, filesystem |
domain/ | Repository interfaces | Android framework, Room, Compose |
data/ | Room, files, network, core/ | Compose, ViewModels |
core/ | Other core/ | Feature screens, ViewModels |
Rule of thumb: if a ViewModel needs new I/O, add a use case and repository method — do not inject a DAO.
Adding a new screen
- Destination —
*Destinationobject withroutePatternandroute()factory. - ViewModel —
@HiltViewModel, inject use cases, exposeStateFlowUI state. - Screen — Compose UI in
feature/{name}/, wrap inScanlyTheme. - Navigation — register composable in
ScanlyNavHost.kt. - Use case — add in
domain/usecase/for new business logic. - Repository — extend interface + implement in
data/for new I/O. - DI — bind in appropriate
di/*Module.kt. - Tests — unit tests for logic; instrumented for critical UI.
- Docs — update relevant
docs/pages for behavior changes.
Room migrations
When changing schema:
- Update entities in
data/local/db/entity/. - Add
Migration_X_YinScanlyDatabase.kt. - Bump
@Database(version = ...). - Register migration in
DatabaseModule. - Test on device with data from previous version.
- Update ../releases.md, ../../CHANGELOG.md, ../../VERSION.md.
Existing migrations: 1→2 (preferred filter preset on documents), 2→3 (document groups), 3→4 (page filter adjustments), 4→5 (page filter intensity; legacy preset remap).
Image processing rules
- Never overwrite
raw/files. - Regenerate
processed/andthumbs/on every edit. - Model assets: corner variants (
document_corners_*.tflite) andscanly_document_gate_float16.tfliteunderapp/src/main/assets/models/— seemodels/README.txt. - Keep
noCompress += "tflite"inapp/build.gradle.kts. - Mark
NEEDS_REVIEWwhen processing cannot produce a quad. - Filters defined in
PageFilterPreset;OpenCvPageFilterProcessoris the facade overcore/processing/filter/(analyze → background model → look → engine); strength isPageFilterAdjustments.intensity; optional tonal tweaks viaPageFilterAdjustmentsApplier. - Crop AI Detect goes through
DetectDocumentCornersUseCase/PageImageProcessor.detectDocumentCorners(do not call detectors from Compose). - Live vs post models and the document gate are Settings preferences; do not hard-code a single model path in UI code.
- Editor sub-tools: Filters and Adjust are full-screen overlays sharing
PageEditorViewModel; Crop is a NavHost sibling route.
Storage layout
Do not change on-disk paths without a migration plan:
files/documents/{documentId}/raw|processed|thumbs/
cache/exports/
cache/library-archive/
files/library-archive-journal/
User-visible paths (via MediaStore or SAF):
Downloads/Scanly/ # Default export base
Downloads/Scanly/backup/ # Reserved for .scanly library archives
Dependency management
- Add version + library to
gradle/libs.versions.toml. - Reference via
libs.*inapp/build.gradle.kts. - Update
app/src/main/assets/settings/licenses.jsonfor new third-party libs. - Update ../reference/tech-stack.md.
UI conventions
- Wrap screens in
ScanlyTheme. - Reuse
feature/components/andcore/ui/before one-off layouts. - Use
AdaptiveLayoutfor phone vs tablet. - Theme via
AppSettingsViewModel+ DataStore.
Kotlin style
- Official Kotlin style (
kotlin.code.style=officialingradle.properties). - Match existing naming and file organization in the package you're editing.
Documentation updates
| Change | Update |
|---|---|
| User-facing behavior | ../../README.md, ../../CHANGELOG.md |
| Architecture / data flow | Relevant docs/architecture/ or docs/data/ page |
| Processing pipeline | docs/processing/ pages |
| Build workflow | This file or setup.md |
| Public release | ../../VERSION.md, ../releases.md |
| Agent guidance | ../../Agents.md |
Related docs
- setup.md — build commands
- ../architecture/overview.md — layer diagram
- testing.md — test conventions