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
+1Lines changed: 1 addition & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -40,6 +40,7 @@ EasyWatermark (`me.rosuh.easywatermark`) — a privacy-focused Android app that
40
40
- **Desktop minimal product flow (S4d-119 readiness -> S4d-130, accepted 2026-06-27):** S4d-119 proved `:desktopApp` was still a CLI witness and selected the smallest non-speculative Desktop product-flow path. S4d-120 added desktop `createWaterMarkDataStore(...)`, a Desktop `WaterMarkRepository`/`WatermarkConfigEditor` roundtrip test, and a headless **open -> edit -> render -> save** flow writing `desktopApp/build/s4d120-desktop-headless/watermarked.png` (or CLI output path). S4d-121 added the minimal Compose Desktop `Window`: no-arg `:desktopApp:run` opens `EasyWatermark — Desktop (S4d-121)` and the sample button runs the same save spine; `--headless` remains the bounded automation path. **S4d-122 made Desktop text rendering and `composeOverRealImage` consume persisted `WaterMark.textColor` + `TextTypeface`** (color now defaults to shared amber `WaterMark.default.textColor`, and typeface maps to Compose `FontWeight`/`FontStyle`) and passed `TextPaintStyle` through. **S4d-123 completed shared drawStyle threading**, so `TextPaintStyle.Stroke` is raster-honored on Desktop/iOS Skiko; Android text production remains native. **S4d-124 selected the next Desktop slice, and S4d-125 added the first interactive file input:** the Desktop window now has an **"Open image..."** button using native JDK/AWT `FileDialog` to pick a real image, read bytes off the UI thread, and call the existing `DesktopWatermarkFlow.runSaveFlow(inputBytes, inputLabel)` spine. Cancel is a no-op; `--headless` stays unchanged. **S4d-126/S4d-130 split output-format safely:** S4d-127 added the Desktop encoder primitive (`ImageFormat.PNG` byte-identical `encodePng`; `ImageFormat.JPEG` ARGB -> opaque RGB flatten + JDK `ImageIO` quality), with `composeOverRealImage` still defaulting PNG for composer goldens. S4d-128 then wired the Desktop save spine to `UserConfigRepository.userPreferences.first()`: empty Desktop stores now follow shared/Android defaults (`JPEG/80`) and default output paths become format-aware (`watermarked.jpg` or `watermarked.png`), while PNG remains available when prefs select PNG. S4d-130 added the minimal window selector: two preset buttons (`JPEG / 80`, `PNG / 100`) write through the shared `OutputPrefsEditor` over the same store the save flow reads. **Still not a full Desktop editor and not 1:1 parity:** no drag/drop, interactive image preview, templates, share substitute, icon watermark, arbitrary quality selector, or final Android v2.10.0 UI parity.
41
41
- **Desktop product-flow continuation (S4d-132 -> S4d-143b, accepted 2026-06-27/28):** S4d-132..S4d-137 completed the Desktop icon/source mini-epic at functional-code level: icon composition primitive, persisted Image-mode save branch, "Open icon...", "Use text watermark", and remembered source-image reuse. S4d-138/S4d-140 then added the release-code map, `DesktopSaveDecision` harness, and destination-only "Save as..." control. **S4d-141..S4d-143b closed the first templates persistence lane:** Desktop has an empty-store Room builder (`buildTemplateDatabase(dir)` with `BundledSQLiteDriver`), a `:desktopApp --headless` TemplateRepository/TemplateEditor witness, and a read-only proof that both Android seed DBs match generated Room identity hash `72366f557b971b39675d0f26cbc46e0a`. Seed packaging via `createFromFile` is technically viable but intentionally deferred until default templates or a visible templates UI become product priority. **Still not a full Desktop editor and not 1:1 parity:** no drag/drop, interactive image preview/editor, templates UI, share substitute, arbitrary quality selector, batch, or final Android v2.10.0 UI parity.
42
42
- **Desktop window controls + share substitute + drag/drop + templates UI (S4d-145 -> S4d-160, accepted 2026-06-28):** Desktop window enrichment stayed one-file and functional: manual watermark text control, preview output, degree/color/opacity/gap/text-size/tile/typeface/style controls, scrolling, a no-new-dependency share substitute, dropped-image input, and a minimal Templates section. The share substitute tracks only the last successful real save from Render & Save / Save as / Open image / image drop; Preview temp output is deliberately not share state. Drag/drop uses Compose Desktop `dragAndDropTarget` + AWT file-list flavor and reuses the Open-image save spine. **S4d-159** chose templates UI over reactive preview because `Template.content` is just watermark text; reactive preview would reverse the deliberate manual-preview contract. **S4d-160** added Save current text / list / Use / Delete over `buildTemplateDatabase(dir)` + `TemplateRepository` + `TemplateEditor`; Use applies via `WatermarkConfigEditor.updateText` and still requires manual Preview/Render. **Still not 1:1 parity:** reactive preview/editor polish, template edit-in-place/seed defaults, batch, and final Android v2.10.0 screenshot/recording parity remain separate gates.
43
+
- **Desktop release hygiene + packaging proof (S4d-161 -> S4d-163, 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`. **Overclaim guard:** unsigned app-image proof ONLY — no installer formats (DMG/PKG/MSI/DEB), signing/notarization, app icon, CI packaging gate, or real product-version source yet (`packageVersion = "1.0.0"` is a placeholder); still not Android v2.10.0 1:1 UI/UX parity.
43
44
- 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).
44
45
- **`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**.
45
46
- **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); `sqlite-bundled`/`sqlite` removed from the catalog; `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`). **Desktop/iOS production templates builders are intentionally unwired** (the DB type compiles/links on those targets but no builder is created — no templates UI there yet). **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.
Copy file name to clipboardExpand all lines: findings.md
+2-1Lines changed: 2 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1,6 +1,6 @@
1
1
# Compose Migration Findings
2
2
3
-
## Desktop product flow should prove the spine before widening the window (S4d-119..160, 2026-06-27/28)
3
+
## Desktop product flow should prove the spine before widening the window (S4d-119..163, 2026-06-27/28)
4
4
5
5
-**A headless product spine is less speculative than a first Compose Desktop dependency.** S4d-119 found `:desktopApp` was still a console witness, so the smallest real Desktop product step was not "add a window"; it was to prove the same core path a window will later drive: persistent watermark config -> shared editor update -> persisted readback -> real-image render -> saved output. S4d-120 did exactly that without a build-file change, making the future `Window` slice a thin UI wrapper over a proven flow instead of a UI+storage+render bundle.
6
6
-**Desktop off-Android `WaterMarkRepository` uses the same edge pattern as iOS.** Fresh Desktop stores should inject platform-neutral edges: default text string, pure `WatermarkTileMode.fromStorageId` (Android's SDK-gated legacy DECAL-id-3 -> REPEAT mapper is Android-only legacy-data handling), and a simple logger. `createWaterMarkDataStore(dir, name = WaterMarkRepository.SP_NAME)` mirrors `createUserConfigDataStore`, stays a plain function (not `expect`/`actual`), and leaves Android byte-faithful `Context`+migration store creation untouched.
@@ -12,6 +12,7 @@
12
12
-**Desktop flow glue now has the no-dependency harness it needed before more logic widening.** S4d-138 found `:desktopApp` has no test source set; the window/flow glue was compile + `--headless` witnessed only, while `shared/src/desktopTest` covers renderer/encode/decode/DataStore primitives structurally. S4d-139 took the lazy route: extract only the pure `runSaveFlow` decisions into `desktopMain` (`DesktopSaveDecision`) and test them from existing `desktopTest`. S4d-140 then correctly stayed a window destination slice: it calls existing `runSaveFlow(outputFile = target)` and does not widen flow/shared logic. Do not add a `:desktopApp` test source set or new test dependency for this lane yet; keep the next Desktop slice read-only templates readiness before changing Room/templates code.
13
13
-**Desktop templates persistence is proven for empty stores; seed packaging is viable but optional.** S4d-141 mapped the templates path after Save-As and correctly split it into empty builder -> app witness -> seed decision. S4d-142 added the Desktop Room builder with `BundledSQLiteDriver` and tested `TemplateRepository`/`TemplateEditor` add/list/delete on an empty DB; S4d-143a proved the same path from `:desktopApp --headless` after adding the needed app-level `room.runtime` declaration. S4d-143b proved both Android seed DBs and generated Room share identity hash `72366f557b971b39675d0f26cbc46e0a`, so reuse is technically viable. S4d-160 surfaced the empty-store path in the Desktop window; seed packaging still waits for default-template parity/product priority, not merely because the UI can list templates.
14
14
-**Shared renderer fixes should land once, below platform windows.** S4d-122 discovered that commonMain `WatermarkCellComposer.composeTextCell` called `multiParagraph.paint(canvas, content.color)`, dropping `TextStyle.drawStyle`, so Desktop and iOS `TextPaintStyle.Stroke` were value-wired but still painted like Fill. S4d-123 fixed that at the commonMain paint call and tightened Desktop/iOS stroke tests. Lesson: when a field is already mapped at both platform edges, fix the shared renderer instead of adding another desktopApp/window workaround.
15
+
- **Desktop packaging is proven as an unsigned app image, and needs a supported (non-Homebrew) JDK.** S4d-162 finished the release hygiene (release-facing window title; blank-template-save guard). S4d-163 then made `:desktopApp` minimally packageable by swapping the plain Gradle `application` plugin for the Compose Desktop `compose.desktop { application { … nativeDistributions { … } } }` DSL (one build file; reuses the already-applied `org.jetbrains.compose` plugin — no new dependency). The chief risk was the `run` task: the Compose DSL re-provides it and forwards `--args`, so `:desktopApp:run --args='--headless'` stays the witness gate. **Toolchain caveat:** `:desktopApp:createDistributable` fails under the jenv/Homebrew OpenJDK (Compose Desktop's `checkRuntime` rejects Homebrew vendors, CMP issue #3107) and succeeds under the already-installed Amazon Corretto 17 — use a supported packaging JDK; do **not** bypass with `compose.desktop.packaging.checkJdkVendor=false`. Keep claims narrow: this is an unsigned `.app` image proof only — installer formats, signing/notarization, app icon, a real `packageVersion` source, and a CI packaging gate are separate later slices, and none of this is Android v2.10.0 1:1 parity.
15
16
16
17
## First off-Android consumer of the shared watermark editor: iOS watermark editor fields (S4d-101..S4d-113, 2026-06-27)
-**S4d-161 (read-only decision, accepted):** chose Desktop release-grade hygiene + a packaging proof over speculative UI widening (reactive preview, template edit-in-place, batch were deferred). Evidence: no Compose Desktop packaging task existed, and the window title still carried a dev marker.
6
+
-**S4d-162 (implementation, accepted; commit `bb5e770f Polish Desktop release hygiene`, `DesktopWindow.kt` only):** the window title is now release-facing `EasyWatermark — Desktop` (dropped the `(S4d-121)` marker), and the Templates "Save current text" button is disabled when the watermark text is blank. Headless witnesses unchanged.
7
+
-**S4d-163 (implementation, accepted; commit `ccc9f3f7 Add Desktop packaging proof`, `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. `createDistributable`/`runDistributable`/`packageDistributionForCurrentOS` now exist; `:desktopApp:run --args='--headless'` still works (the Compose `run` forwards `--args`); `createDistributable` builds an unsigned `EasyWatermark.app` (native launcher + `Info.plist` + bundled JRE + app jars) under `desktopApp/build/compose/binaries/main/app/`.
8
+
-**JDK caveat (recorded honestly):**`createDistributable` fails under the jenv/Homebrew OpenJDK (Compose Desktop's `checkRuntime` rejects Homebrew vendors — CMP #3107) and succeeds under the already-installed Amazon Corretto 17 (`/Library/Java/JavaVirtualMachines/amazon-corretto-17.jdk/Contents/Home`). The accepted path is a supported packaging JDK; `compose.desktop.packaging.checkJdkVendor=false` was NOT used. `packageVersion = "1.0.0"` is a placeholder.
9
+
-**Scope guard (no overclaim):** unsigned app-image proof only — no installer formats, signing/notarization, app icon, CI packaging gate, real product-version source, or Android v2.10.0 1:1 UI/UX parity.
10
+
-**Next:** Desktop is functionally complete (picker/drop -> edit -> preview -> save/save-as -> share substitute -> templates) AND minimally packageable. Remaining gaps: packaging hardening (real version, installer formats, signing, app icon, CI gate), reactive preview/editor polish, template edit-in-place/default seeding, batch, and final Android v2.10.0 screenshot/recording parity after release-grade migration.
-**S4d-159 (read-only decision, accepted; ACSP `20260628-052647...`):** chose a minimal Desktop templates UI over reactive/live preview. Evidence: Android `Template.content` is watermark text applied through `updateText`, Desktop already had `buildTemplateDatabase`, `TemplateRepository`, and `TemplateEditor`, while reactive preview would reverse the current manual Preview/Render contract.
0 commit comments