The app follows a standard three-layer architecture:
Presentation Layer (UI)
Domain Layer (Business Logic)
Data Layer
app/
├── ui/ # Presentation layer (ADR-0025)
│ ├── navigation/ # Route constants & NavHost orchestrator
│ │ ├── NavGraph.kt # Thin orchestrator — calls per-feature extensions
│ │ └── NavigationRoutes.kt
│ ├── features/ # One sub-package per screen/feature
│ │ ├── inbox/ # Capture & inbox
│ │ ├── processing/ # Processing flow (type/subtype classification)
│ │ ├── nextactions/ # Filtered actionable items
│ │ ├── edititem/ # Edit processed items
│ │ ├── reflection/ # Daily reflection
│ │ ├── someday/ # Someday/Maybe list
│ │ ├── reference/ # Reference items
│ │ ├── morning/ # Morning briefing
│ │ └── llm/ # Model download UI, status banner
│ ├── components/ # Shared composables (cross-feature)
│ └── theme/ # Material theme + design tokens
├── domain/
│ ├── model/ # Domain models
│ ├── usecase/ # Business logic use cases
│ ├── llm/ # LlmService, LlmConfig, LlmResult, PromptTemplate,
│ │ # PromptTemplateRepository, ReflectionSummaryState
│ └── repository/ # Repository interfaces
├── data/
│ ├── local/
│ │ ├── database/ # Room database
│ │ ├── dao/ # Data access objects
│ │ └── entity/ # Database entities
│ ├── llm/ # LeapLlmService, ModelManager, ModelState,
│ │ # LocalPromptTemplateRepository
│ └── repository/ # Repository implementations
└── di/ # Hilt modules (DatabaseModule, RepositoryModule,
# LlmModule, LlmProvidesModule)
Each major feature has a repository interface in the domain layer with concrete implementation in the data layer. This abstracts data sources from business logic.
interface InboxRepository {
suspend fun addItem(item: InboxItem): Long
fun getAllItems(): Flow<List<InboxItem>>
suspend fun deleteItem(id: Long)
}
Business operations are encapsulated in single-responsibility use cases:
class SummarizeDailyReflectionUseCase(
private val reflectionRepository: ReflectionRepository,
private val llmService: LlmService
) {
suspend operator fun invoke(dayKey: String): LlmResult
}
Current use cases: GetDailyReflectionUseCase, SaveReflectionUseCase,
SummarizeDailyReflectionUseCase. Processing logic currently lives in ViewModels; dedicated
use cases may be extracted as complexity grows.
The domain layer defines repository interfaces for each data domain:
InboxRepository — inbox item CRUDProcessedItemRepository — processed item CRUD and filteringReflectionRepository — daily reflection persistenceCategoryRepository — category definitions (user-customizable)ContextRepository — context definitions (user-customizable)TagRepository — tag operationsEach screen has a corresponding ViewModel that exposes UI state via StateFlow:
class InboxViewModel @Inject constructor(
private val inboxRepository: InboxRepository
) : ViewModel() {
val uiState: StateFlow<InboxUiState>
}