You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: AGENTS.md
+2-2Lines changed: 2 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -45,9 +45,9 @@ EasyWatermark (`me.rosuh.easywatermark`) โ a privacy-focused Android app that
45
45
- **Desktop release hygiene + packaging proof (S4d-161 -> S4d-166, accepted 2026-06-28):** S4d-161 (read-only decision) chose release-grade hygiene + a packaging proof over speculative UI widening. **S4d-162** (commit `bb5e770f`, `DesktopWindow.kt` only) made the window title release-facing (`EasyWatermark โ Desktop`, dropping the `(S4d-121)` dev marker) and disabled the Templates "Save current text" button when the watermark text is blank. **S4d-163** (commit `ccc9f3f7`, `desktopApp/build.gradle.kts` only) replaced the plain Gradle `application` plugin with the Compose Desktop `compose.desktop { application { mainClass; nativeDistributions { packageName = "EasyWatermark"; packageVersion = "1.0.0" } } }` DSL, so the packaging tasks now exist (`createDistributable`/`runDistributable`/`packageDistributionForCurrentOS`) and `createDistributable` builds an **unsigned `.app` image** (native launcher + `Info.plist` + bundled JRE + app jars, under `desktopApp/build/compose/binaries/main/app/`). The `run` task moved into the Compose DSL and `:desktopApp:run --args='--headless'` still works (witnesses unchanged). **JDK caveat:** `createDistributable` **fails** under the jenv/Homebrew OpenJDK because Compose Desktop's `checkRuntime` rejects Homebrew vendors (CMP issue #3107); it **succeeds** with the already-installed Amazon Corretto 17 (`/Library/Java/JavaVirtualMachines/amazon-corretto-17.jdk/Contents/Home`). The accepted path is to use a **supported packaging JDK** โ do **NOT** add `compose.desktop.packaging.checkJdkVendor=false`. **S4d-166** adds `.github/workflows/desktop_packaging.yml`, a narrow `workflow_dispatch` + path-filtered PR job using Ubuntu + Zulu 17 that runs `:desktopApp:run --args='--headless'` before `:desktopApp:createDistributable`; the green Actions run is the proof that Zulu satisfies the vendor guard. **Overclaim guard:** unsigned app-image proof/workflow ONLY โ no installer formats (DMG/PKG/MSI/DEB), signing/notarization, app icon, real product-version source yet (`packageVersion = "1.0.0"` is a placeholder), macOS packaging, or Android v2.10.0 1:1 UI/UX parity.
46
46
- Typed `@Serializable` Navigation routes (`ui/Routes.kt`); `ImageFormat` replaces `Bitmap.CompressFormat` in the model (encode mapping at the edge, `utils/ktx/ImageFormatExt`); `AboutViewModel` is StateFlow (C1.1 started).
47
47
- **`WaterMarkRepository` now in commonMain (S4d-84..S4d-89, accepted 2026-06-27, final move commit `f23bf5d`):** the watermark-config DataStore consumer moved to `shared/src/commonMain/kotlin/me/rosuh/easywatermark/data/repo/WaterMarkRepository.kt` (same package/FQN) โ the **second commonMain DataStore Preferences consumer** after `UserConfigRepository`. It was reached by a staged Android-edge removal (each its own slice, all behavior-preserving): **S4d-84** `Color.parseColor("#FFB800")` โ const `0xFFFFB800.toInt()` (pinned by a Robolectric equality test) + `ArrayMap` โ `mutableMapOf()`; **S4d-85** `java.io.IOException` โ `okio.IOException`; **S4d-86** the localized default text injected as `defaultTextProvider: () -> String` (RepositoryModule passes `context.getString(R.string.config_default_water_mark_text)`, a per-emission lambda); **S4d-87** the persisted tile-id read mapper injected as `tileModeFromStorageId: (Int?) -> WatermarkTileMode` (RepositoryModule passes the **SDK-gated `toWatermarkTileMode`** in `TileModeExt.kt`, so the legacy **pre-Android-12 stored DECAL id 3 โ REPEAT** behavior is preserved โ NOT the pure `fromStorageId`); **S4d-88** the one diagnostic `Log.e` injected as `logError: (String) -> Unit` (RepositoryModule passes `Log.e("WaterMarkRepository", message)`, identical tag/message); **S4d-89** the byte-identical file relocation (git rename, 0 content changes). **Persisted bytes/keys unchanged** โ `SP_NAME = "sp_water_mark_config"`, all `SP_KEY_*` (incl. `SP_KEY_ICON_URI`, `SP_KEY_CHANGE_LOG`), `MAX_*`/legacy constants, storage ids, and the DataStore file naming + `SharedPreferencesMigration` semantics are identical; **no DataStore migration, no golden rebaseline** (strict 49/0). `RepositoryModule` (the three injected edges) and `DataStoreModule` (still creates the Android `waterMarkDataStore` from `WaterMarkRepository.SP_NAME` via the S4d-74 `createPreferencesDataStore`) are the Android wiring edges; `TileModeExt.kt`/`EditorScreen`/the test resolve the same FQN from `:shared` unchanged. Verified by shared Desktop/iOS/iOS-sim + android + app compile, assembleDebug + assembleRelease (R8 retained the class, APK size unchanged), strict goldens 49/0. These are **code-migration slices, not UI-parity slices**.
48
-
- **Room/templates now in commonMain (S4d-90 readiness โ S4d-91 toolchain proof โ S4d-92 move, accepted + committed `8d245d9`):** the production templates Room path moved to `:shared/commonMain` at the **same FQNs** โ `data/model/entity/Template`, `data/db/DateConverter`, `data/db/dao/TemplateDao`, `data/db/AppDatabase` (now `@ConstructedBy(AppDatabaseConstructor::class)` + `@Suppress("KotlinNoActualForExpect") expect object AppDatabaseConstructor : RoomDatabaseConstructor<AppDatabase>`), and `data/repo/TemplateRepository`. **S4d-90** mapped readiness (Room is atomic; prepopulated-asset data-risk). **S4d-91** proved the Room KMP toolchain (Room Gradle plugin + KSP-MP, `kspAndroid`/`kspDesktop`/`kspIosArm64`/`kspIosSimulatorArm64`) compiles/links for all 4 targets, with the proof Room/SQLite **`compileOnly` in commonMain** so the unused `libsqliteJni.so` did **not** ship to `:app` (r1 confinement; proof package since deleted). **S4d-92** did the production move. **Android driver decision = compatibility mode (no explicit `SQLiteDriver`)** โ Android keeps the framework SupportSQLite open-helper + `createFromAsset`, so the prepackaged `ewm-db-ch.db`/`ewm-db-eng.db` open byte-identically and **no** `sqlite-bundled`/`libsqliteJni.so` (or extra `sqlite-framework`) ships; release APK unchanged. Android creation lives in `:shared/androidMain` `data/db/TemplateDatabaseBuilder.android.kt` `buildTemplateDatabase(context)` (locale `createFromAsset` + in-memory fallback, same `ewm-db` path); `AppModule`/`RepositoryModule` call it (`TemplateRepository` now takes an injected `ioContext: CoroutineContext` โ Koin passes `Dispatchers.IO`, since `Dispatchers.IO` isn't accessible in commonMain on Native). `:shared` commonMain Room dep is `implementation(room.runtime)` (no sqlite artifact); `app/build.gradle.kts` untouched (its existing room deps cover the `RoomDatabase` supertype). `exportSchema=false`, `version=1` unchanged; no schema/migration change, no golden rebaseline. Proven by a Robolectric **prepopulated-DB smoke** (`TemplatePrepopulatedDbSmokeTest`, 2/0 โ both seed assets open + read seeded templates non-empty), all-target compile, iOS link, strict goldens 51/0, debug+release APK (no `libsqliteJni.so`). **Off-Android builders now exist where there is a real consumer path:** Desktop has `buildTemplateDatabase(dir)` plus locale-aware seed packaging from S4d-224/S4d-225; **S4d-231 (accepted 2026-06-29, commit `278d585`) added the iOS empty-store builder** in `:shared/iosMain` (`BundledSQLiteDriver`, `Dispatchers.Default` query context, `NSDocumentDirectory` overload) and an iOS simulator roundtrip over `TemplateRepository`/`TemplateEditor`. iOS seed packaging/default templates and Swift Templates UI remain deferred. **Residual:** the release APK was not run on a device (release retention is covered by R8 keep-rule analysis + the debug smoke). Code-migration slices, not UI-parity.
48
+
- **Room/templates now in commonMain (S4d-90 readiness โ S4d-91 toolchain proof โ S4d-92 move, accepted + committed `8d245d9`):** the production templates Room path moved to `:shared/commonMain` at the **same FQNs** โ `data/model/entity/Template`, `data/db/DateConverter`, `data/db/dao/TemplateDao`, `data/db/AppDatabase` (now `@ConstructedBy(AppDatabaseConstructor::class)` + `@Suppress("KotlinNoActualForExpect") expect object AppDatabaseConstructor : RoomDatabaseConstructor<AppDatabase>`), and `data/repo/TemplateRepository`. **S4d-90** mapped readiness (Room is atomic; prepopulated-asset data-risk). **S4d-91** proved the Room KMP toolchain (Room Gradle plugin + KSP-MP, `kspAndroid`/`kspDesktop`/`kspIosArm64`/`kspIosSimulatorArm64`) compiles/links for all 4 targets, with the proof Room/SQLite **`compileOnly` in commonMain** so the unused `libsqliteJni.so` did **not** ship to `:app` (r1 confinement; proof package since deleted). **S4d-92** did the production move. **Android driver decision = compatibility mode (no explicit `SQLiteDriver`)** โ Android keeps the framework SupportSQLite open-helper + `createFromAsset`, so the prepackaged `ewm-db-ch.db`/`ewm-db-eng.db` open byte-identically and **no** `sqlite-bundled`/`libsqliteJni.so` (or extra `sqlite-framework`) ships; release APK unchanged. Android creation lives in `:shared/androidMain` `data/db/TemplateDatabaseBuilder.android.kt` `buildTemplateDatabase(context)` (locale `createFromAsset` + in-memory fallback, same `ewm-db` path); `AppModule`/`RepositoryModule` call it (`TemplateRepository` now takes an injected `ioContext: CoroutineContext` โ Koin passes `Dispatchers.IO`, since `Dispatchers.IO` isn't accessible in commonMain on Native). `:shared` commonMain Room dep is `implementation(room.runtime)` (no sqlite artifact); `app/build.gradle.kts` untouched (its existing room deps cover the `RoomDatabase` supertype). `exportSchema=false`, `version=1` unchanged; no schema/migration change, no golden rebaseline. Proven by a Robolectric **prepopulated-DB smoke** (`TemplatePrepopulatedDbSmokeTest`, 2/0 โ both seed assets open + read seeded templates non-empty), all-target compile, iOS link, strict goldens 51/0, debug+release APK (no `libsqliteJni.so`). **Off-Android builders now exist where there is a real consumer path:** Desktop has `buildTemplateDatabase(dir)` plus locale-aware seed packaging from S4d-224/S4d-225; **S4d-231 (accepted 2026-06-29, commit `278d585`) added the iOS empty-store builder** in `:shared/iosMain` (`BundledSQLiteDriver`, `Dispatchers.Default` query context, `NSDocumentDirectory` overload) and an iOS simulator roundtrip over `TemplateRepository`/`TemplateEditor`; **S4d-232 (accepted 2026-06-29, commit `44be9f7`) added iOS locale-aware seed packaging** by bundling byte-identical Android seed DBs into `iosApp.app`, loading them with `NSBundle`, and seed-copying to `$NSDocumentDirectory/ewm-db` before Room opens on first creation. Swift Templates UI remains deferred. **Residual:** the release APK was not run on a device (release retention is covered by R8 keep-rule analysis + the debug smoke). Code-migration slices, not UI-parity.
49
49
- **First shared editor use-case extracted (S4d-94 โ S4d-95 โ S4d-96, accepted; S4d-96 committed `7ee7e77`):** **S4d-94** (read-only) decided the **Koin common/platform split stays deferred** โ no non-Android DI consumer exists (Koin is started only in `MyApp`; desktop/iOS construct their one repo manually), so a commonMain Koin module would be a framework layer with no consumer. **S4d-95** (read-only) mapped the ViewModels and found `MainViewModel` cannot move (deep `Bitmap`/`Uri`/`MediaStore`/`File`/`Context` IO) and both VMs extend Android `androidx.lifecycle.ViewModel`; the smallest non-speculative step is a commonMain **use-case** consumed by the Android VM. **S4d-96** landed it: `shared/src/commonMain/.../domain/WatermarkConfigEditor.kt` โ the **first small shared editor config use-case** over the commonMain `WaterMarkRepository`, with the 12 `update*` config edits as `suspend` methods and the three inline rules moved **verbatim** (text-size `coerceAtLeast(0f)`, `WatermarkConfigRules.alphaPercentToByte`, non-empty `MediaRef` icon guard). It is a **behavior-preserving extraction, NOT a shared ViewModel or reducer**: `MainViewModel` stays Android-side, still owns `viewModelScope`/`launch` and the save/compress/gallery/picker/crash/IO flows, and constructs the use-case from the already-injected `WaterMarkRepository` (**no Koin/AppModule/Gradle/lifecycle/renderer/storage change**). Public VM method names/signatures unchanged. Verified: all-target compile + `:app`, `:shared:linkDebugTestIosSimulatorArm64`, strict goldens **51/0** (no rebaseline), debug+release APK (release size unchanged), daemon stop. **S4d-97 (accepted, committed `ec9317c`)** added the sibling `shared/commonMain/.../domain/OutputPrefsEditor.kt` โ `class OutputPrefsEditor(repo: UserConfigRepository)` with one `suspend save(format, level)` wrapping `UserConfigRepository.updateFormat(format)` then `updateCompressLevel(level)` (same order, no validation/clamping added); `MainViewModel.saveOutput` keeps its signature/defaults and still owns `viewModelScope.launch` with `resetJobStatus()` after the launch/outside the coroutine, built from the already-injected `userRepo` (no DI change). Same gates green (51/0, release size unchanged). **S4d-98 (accepted, committed `d26c95b`)** added the third sibling `shared/commonMain/.../domain/TemplateEditor.kt` โ `class TemplateEditor(repo: TemplateRepository)` with `isDaoNull()` (wraps `checkIfIsDaoNull()` so the VM keeps the null-DAO โ `UiState.DatabaseError` mapping Android-side โ `UiState` is **not** in commonMain), `suspend add(content)` constructing `Template(0, content, Clock.System.now(), Clock.System.now())` (two separate `now()` calls, verbatim) then inserting, and `suspend update/delete` delegations. `MainViewModel` keeps its public signatures + the synchronous null-DAO-check structure, owns `viewModelScope`/`launch`/`UiState`, built from the already-injected `templateRepo` (no DI change). A **cheap** commonTest (`TemplateEditorTest`, 2/0) pins `isDaoNull()` true/false (possible here because `TemplateDao` is an interface); the suspend writes are NOT directly unit-tested (commonTest has no coroutine runner) โ compile/link + unchanged goldens are the build/regression net, not a unit test of those writes. The three small editor use-cases are now extracted; the remaining shared-state work (the deliberate multiplatform-`lifecycle` decision for moving `AboutViewModel`, and real Desktop/iOS editor UI consumers) stays separate.
50
-
- **NOT yet done** (the bulk โ months of work): the broader shared ViewModel/use-case extraction remains consumer-gated โ the three small shared editor use-cases landed (`WatermarkConfigEditor` S4d-96, `OutputPrefsEditor` S4d-97, `TemplateEditor` S4d-98), but `MainViewModel`/`AboutViewModel` themselves stay Android-side. **S4d-191 (read-only readiness, accepted)** confirmed there is no safe non-speculative `MainViewModel` business-IO slice right now: the consumed business logic is already shared, remaining pure-looking methods are Android `UiState` only, and the rest is Android IO/render (`ContentResolver`/`MediaStore`/`Bitmap`/`Canvas`/`Compressor`/native `WatermarkRenderer`). Do NOT create a shared ViewModel/navigation reducer/IO `expect` layer without a named real off-Android consumer or an explicit owner decision. **S4d-99 (read-only readiness, accepted)** found `AboutViewModel` is technically ready but owner-gated/speculative until a real Desktop/iOS About consumer exists; the **golden test harness (C1.7)** that gates deeper engine rewrite (C2); the Koin common/platform split (deliberately deferred by S4d-94 while no non-Android DI consumer exists); iOS seed-DB packaging/default-template parity remains deferred; Desktop locale-aware seeding landed in S4d-225 (English default landed in S4d-224) (S4d-90..S4d-92); the full Compose Desktop editor beyond the current minimal product-flow window; Desktop renderer feature parity (Desktop reactive preview landed in S4d-198); and the remaining real PHPicker grid-cell proof. Android text/icon/composition/decode production remains native by default. KMP/CMP migration is NOT complete.
50
+
- **NOT yet done** (the bulk โ months of work): the broader shared ViewModel/use-case extraction remains consumer-gated โ the three small shared editor use-cases landed (`WatermarkConfigEditor` S4d-96, `OutputPrefsEditor` S4d-97, `TemplateEditor` S4d-98), but `MainViewModel`/`AboutViewModel` themselves stay Android-side. **S4d-191 (read-only readiness, accepted)** confirmed there is no safe non-speculative `MainViewModel` business-IO slice right now: the consumed business logic is already shared, remaining pure-looking methods are Android `UiState` only, and the rest is Android IO/render (`ContentResolver`/`MediaStore`/`Bitmap`/`Canvas`/`Compressor`/native `WatermarkRenderer`). Do NOT create a shared ViewModel/navigation reducer/IO `expect` layer without a named real off-Android consumer or an explicit owner decision. **S4d-99 (read-only readiness, accepted)** found `AboutViewModel` is technically ready but owner-gated/speculative until a real Desktop/iOS About consumer exists; the **golden test harness (C1.7)** that gates deeper engine rewrite (C2); the Koin common/platform split (deliberately deferred by S4d-94 while no non-Android DI consumer exists); iOS Swift Templates UI remains deferred after S4d-231/S4d-232 completed builder + seed packaging; Desktop locale-aware seeding landed in S4d-225 (English default landed in S4d-224) (S4d-90..S4d-92); the full Compose Desktop editor beyond the current minimal product-flow window; Desktop renderer feature parity (Desktop reactive preview landed in S4d-198); and the remaining real PHPicker grid-cell proof. Android text/icon/composition/decode production remains native by default. KMP/CMP migration is NOT complete.
51
51
52
52
Session memory (planning-with-files): `task_plan.md`, `findings.md`, `progress.md` at repo root โ read them at session start, update them as you work.
0 commit comments