Drop the Context injection from the ScreenModel. Emit a sealed
ImportSnackbarMessage carrying either a @StringRes id or a dynamic
text (for the error.message case where the bank importer surfaces a
useful 'BRI import not yet implemented' string). The screen resolves
resource ids via stringResource() in Composable scope and shows the
snackbar; the original error.message info is preserved via the
Dynamic variant instead of being dropped.
The three snackbar strings in ImportBankStatementScreenModel were
hardcoded English while the rest of the app uses stringResource. Move
them to res/values/strings.xml. Also drop the
error::class.simpleName fallback in the failure branch — it surfaced
Kotlin class names like NotImplementedError to end users. Now falls
back to the localized 'Import failed' string.
DateField seeded the picker with ZoneId.systemDefault() but converted
the selectedDateMillis back with ZoneId.of("UTC"). For any non-UTC
user, the field and the picker displayed different days, and
confirming without re-picking silently shifted the export range by a
day. Use UTC on both sides (the DatePickerState contract is that
selectedDateMillis is UTC midnight of the picked day).
screenModelScope is backed by PlatformMainDispatcher (Main.immediate),
so direct interactor calls run DB queries and file I/O on the UI
thread. Switch reactive flows with .flowOn(Dispatchers.IO) and wrap
suspend calls in withContext(Dispatchers.IO).
Move architecture, package structure, dependencies, and implementation
rules into .opencode/agent/implementor.md (default primary agent). Add
.opencode/agent/reviewer.md (read-only primary agent) with PR review
workflow that leaves inline review comments only -- no issue creation,
no edits, no commits. AGENTS.md is now the shared context both agents
load: workflow, git/PR conventions, and docs index. Set
default_agent: implementor in opencode.json.
Per review on PR #8 (#8):
- Split @Upsert into @Insert(OnConflictStrategy.REPLACE) + @Update in all 3 DAOs.
@Upsert returns -1 on the update path, so callers wanting the row ID would
get a junk value. Interactors now call insert vs update based on id == 0.
UpsertCategory returns category.id explicitly for the id != 0 branch.
- Add @Transaction to the 3 @Relation queries (ExpenseDao.subscribeAll,
ExpenseDao.subscribeByDateRange, RecurringExpenseDao.subscribeAll). This
silences the KSP warnings the PR body mentioned and makes the intent
explicit.
- Switch MainApplication seeding from a fire-and-forget CoroutineScope to
runBlocking(Dispatchers.IO). A fast first-tap on HomeScreen could otherwise
call GetCategories.awaitDefault() before seeding completed and crash.
- Add documenting comment on CategoryDao.getDefault() noting that the
'only one isDefault = 1' invariant is maintained at the interactor layer
(partial unique index would be a v2 migration).
- Add trailing newline to app/build.gradle.kts.
- File follow-up issue #9 for flipping exportSchema to true before any v2
migration lands.