Imported from BobbyESP/Docucraft (
AGENTS.md). Install upstream withnpx skills add BobbyESP/Docucraft. Copyright stays with the author.
AGENTS.md
Project Snapshot
- Deep-dive architecture docs and stabilization plans:
docs/(start atdocs/README.md). - Multi-module Android project:
:app(product),:composepdf(local PDF engine),:scanner-api(engine-agnostic scanning contract, plain Kotlin),:scanner-mlkit(ML Kit implementation of it). - Stack in use: Kotlin, Jetpack Compose, Navigation 3 typed routes, Koin DI, Room, ML Kit Document Scanner.
- Runtime DI entrypoint is
app/src/main/java/com/bobbyesp/docucraft/App.kt(startKoin).
Where to Work
- Scanner feature lives in
app/src/main/java/com/bobbyesp/docucraft/feature/docscanner(data/domain/presentation/di). - Viewer feature lives in
app/src/main/java/com/bobbyesp/docucraft/feature/pdfviewerand consumescom.composepdf.PdfViewer. - Shared app services live in
app/src/main/java/com/bobbyesp/docucraft/core(preferences, notifications, analytics, file ops, navigation helpers). - Rendering engine internals live in
composepdf/src/main/kotlin/com/composepdf.
Critical Flow (Scan -> Save -> Home)
HomeViewModel.startScan()callsDocumentScanner.scan()and suspends inviewModelScope.MlKitDocumentScanner(:scanner-mlkit) gets the IntentSender, launches it throughActivityResultHostand maps the result to aScanOutcome.MainActivityonly lends its activity result launcher to the host; it knows nothing about scanning.- The widget enters through
ScanRequestBus, which the ViewModel also collects. SaveScanDraftUseCasestores the file viaDocumentStorageand catalogues it.- The scanner outlives this process, so
HomeViewModelrecordsscan_in_flightin itsSavedStateHandleand rejoins throughDocumentScanner.resumePendingScan()on restore. - Home list comes from
ObserveDocumentsUseCase; query/filter/sort is finalized inHomeViewModel.applyFiltersAndSort.
Architecture Rules
- The scanning engine is swapped at one line: the
DocumentScannerbinding infeature/docscanner/di/DocumentScannerModule.kt. ML Kit types exist only in:scanner-mlkitand cannot be imported from:app(enforced by the module graph, not by convention). - Add business logic as use cases under
feature/docscanner/domain/usecase, then inject infeature/docscanner/di/ScannedDocumentModule.kt. - Navigation is typed (
Routeincore/presentation/common/Route.kt), rendered byNavigator.ktwith Navigation 3NavDisplay. - App-wide settings and services should flow via composition locals in
core/presentation/common/CompositionLocals.kt.
Integrations and Sensitive Points
- ML Kit options are derived from a
ScanRequestinscanner-mlkit'sMlKitDocumentScanner. - File sharing relies on
${applicationId}.fileprovider(AndroidManifest.xml+App.getAuthority). - Firebase Analytics/Crashlytics are enabled (
core/di/AnalyticsModule.kt,app/build.gradle.kts,google-services.json). - Home widget scan action enters app through
ACTION_SCAN_DOCUMENTinMainActivity. - Room schema export is active; keep
app/schemas/...updated when changing DB entities/migrations.
Build and Validation
- Debug APK:
./gradlew :app:assembleDebug(Windows:.\gradlew.bat :app:assembleDebug). - Unit tests:
./gradlew testDebugUnitTest :scanner-api:test(all modules). - Instrumented tests:
./gradlew :app:connectedDebugAndroidTest :composepdf:connectedDebugAndroidTest. - Formatting:
./gradlew spotlessApply(Spotless appliesktfmtto modules;spotlessCheckverifies). - Custom APK copies are generated under
app/build/outputs/apk_custom/<variant>/bybuildSrc/CopyApkPlugin.kt.