Skip to content

Commit 1a9463f

Browse files
committed
Document iOS template Room builder
1 parent 278d585 commit 1a9463f

6 files changed

Lines changed: 23 additions & 11 deletions

File tree

AGENTS.md

Lines changed: 2 additions & 2 deletions
Large diffs are not rendered by default.

docs/CONTEXT.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,9 @@ Domain vocabulary and invariants. Agents and humans: use these words with these
3333
| **Desktop window controls/share substitute/drag-drop/templates UI** | **S4d-145..S4d-160 (accepted 2026-06-28)** widened the Desktop window through small one-file slices: watermark text editing, preview output, degree, color, opacity, h/v gaps, text size, tile mode, typeface/style, scrolling, share substitute, dropped-image input, and minimal templates UI. **S4d-157** added the selected no-new-dependency share substitute: only successful real saves update `lastSavedFile`, so Render & Save / Save as / Open image outputs can be shown in their folder or copied as a path; Preview temp output is deliberately excluded. **S4d-158** added Compose Desktop `dragAndDropTarget` over the existing Open-image save spine: dropped local image files are filtered by the same extension set, read on IO, saved through `DesktopWatermarkFlow.runSaveFlow`, and update `lastImage` plus successful-save `lastSavedFile`. **S4d-159/S4d-160** chose and added the minimal Templates section: Save current text / list / Use / Delete over `buildTemplateDatabase(dir)` + `TemplateRepository` + `TemplateEditor`; Use applies text through `WatermarkConfigEditor.updateText`. **S4d-198 (accepted 2026-06-28, commit `1f0019b5`)** then added **reactive preview**: a successful explicit editor/mode action (the 6 Apply fields, tile/typeface/style, the Use-text/Open-icon mode flips, template Use) **and** Open image / drop source changes now auto-refresh the on-screen preview via one `refreshPreview()` helper over the SAME `runSaveFlow` → `DesktopImageDecoder` temp-file spine the manual `Preview` button uses — bounded to explicit clicks/source events (not per-keystroke); preview writes only the temp file and never sets `lastSavedFile` (share-substitute stays real-save-bound); Render & Save / Save as stay real-save-only. This is functional Desktop product-flow coverage, not Android v2.10.0 UI/UX parity. **S4d-224 (accepted 2026-06-29, commit `c7959d0`)** then added Desktop template default seeding from the English Android seed DB via a `:shared` desktopMain resource + helper. **S4d-225 (accepted 2026-06-29, commit `875b105`)** made that seeding locale-aware (`ewm-db-ch.db` for `zh` JVM locales, `ewm-db-eng.db` otherwise). **S4d-226 (accepted 2026-06-29, commit `1fa945f`)** then added the minimal row-level template update action (Update → `TemplateEditor.update`, preserving id/creationDate). Remaining Desktop gates include batch and final screenshot/recording parity after release-grade migration. **S4d-215 (accepted 2026-06-28, commit `873dbcbd`, `DesktopWindow.kt` only)** made the interactive window persist its user state — watermark config, output prefs, and the templates Room DB — under the stable per-user dir `~/.easywatermark` (new same-file helper `resolveDesktopAppDataDir()`, matching the `CreateDataStore.desktop.kt` convention; `user.home`-null falls back to a repo-local `build/` dir with a warning, no crash) instead of the old repo-local `build/` dev paths. **Intentionally unchanged (do NOT "fix"):** `DesktopWatermarkFlow` build-local *defaults* and `Main.kt`'s headless/demo witness paths stay build-local by design, and the transient preview temp stays `build/s4d147-desktop-preview/preview.img`. Runtime-proven: a window launch creates `~/.easywatermark/ewm-db` (templates DB) and no `build/s4d160-desktop-templates`; the two DataStore preference files (`sp_water_mark_config`, `sp_water_mark_user_config`) are written **lazily on first GUI edit**, so they are diff-proven + left as a manual GUI acceptance check (type text → save template → restart-persistence), not an automated proof. Release-grade Desktop persistence correctness, **not** 1:1 parity; template edit-in-place has already landed (S4d-226); locale-aware seeding has already landed (S4d-225). **S4d-217 (accepted 2026-06-28, commit `ce677bb2`, `DesktopWindow.kt` only)** then routed the window's REAL save *output* off the repo-local `build/` default: image **drop**, **Render & Save sample**, and **Open image…** now write to a user output dir — `~/Pictures` if that directory exists, else `~/.easywatermark/output` (`File(resolveDesktopAppDataDir(), "output")`) (new same-file helper `resolveDesktopOutputDir()`, created if missing) — passing `outputFile = File(outputDir, DesktopSaveDecision.defaultOutputFileName(fmt))` with `fmt = userConfigRepo.userPreferences.first().outputFormat`. **Unchanged by design:** `Save as…` still writes the user-chosen dialog path; the preview temp stays `build/s4d147-desktop-preview/preview.img`; and `DesktopWatermarkFlow` defaults + `Main.kt` headless/demo witness stay build-local (the `--headless` run still wrote `build/s4d120-desktop-headless/watermarked.jpg`). The output filename is fixed by format (`watermarked.jpg`/`.png`), so repeated real saves **overwrite** — collision-avoiding names remain deferred. Proof = compile + headless witness + diff/branch routing (on this host `~/Pictures` exists → that branch); the GUI save **click was not automated**, so the file actually landing in the user dir is a coordinator manual acceptance check, not a runtime-automated proof. Release-grade Desktop save-location correctness, **not** 1:1 parity. **S4d-219 (accepted 2026-06-29, commit `97baa9d2`, `DesktopWindow.kt` only)** made the watermark **icon** persistence durable: "Open icon…" no longer persists the user's original absolute path — it copies the picked icon into app-private storage `~/.easywatermark/watermark_icons/icon.<ext>` (preserving the picked extension) and persists THAT copy path, so Image-mode no longer hard-fails (`DesktopWatermarkFlow.require(iconFile.isFile)`) when the source moves/renames/deletes or the config opens on another machine — parity with iOS `IosIconPersistence` (S4d-116) at the release-grade persistence level. **Copy-then-prune** (never clear-before-copy): re-picking the existing copy is a no-op (`selected.canonicalFile == copied.canonicalFile`); else copy source → `incoming.tmp` then `java.nio.file.Files.move(tmp → copied, ATOMIC_MOVE, REPLACE_EXISTING)`, and only AFTER `copied` exists prune the other files → bounded to one helper-owned icon; an unsupported atomic move or a failed read throws → the existing `Failed: …` path with the prior copy untouched (preview refresh + mode→Image preserved). GUI icon-pick **end-to-end proof was not automated**; evidence is compile + headless + diff + an offline algorithm sim, with a manual GUI acceptance path. Release-grade Desktop icon-persistence durability, **not** 1:1 parity. **S4d-221 (accepted 2026-06-29, commit `1fe8ab9e`)** extracted that S4d-219 copy-then-prune logic out of `DesktopWindow.kt` into a tested `shared/desktopMain` helper `DesktopIconPersistence.persistIcon(source, iconsDir)` (verbatim semantics: `~/.easywatermark/watermark_icons/icon.<ext>`, extension preserved blank→png, same-copy no-op, copy to `incoming.tmp` then atomic `Files.move(ATOMIC_MOVE, REPLACE_EXISTING)`, prune others only after the target exists, prior copy survives a failed source read); `DesktopWindow.kt` now just calls it before `editor.updateIcon(MediaRef(copied.absolutePath))`, with the UI filter/mode-flip/preview-refresh/status **unchanged**. The logic is now CI-covered by `DesktopIconPersistenceTest` (`:shared:desktopTest`, 5 tests / 0 failures) — parity with the tested iOS `IosIconPersistence`; the GUI icon-picker click remains a thin manual/UI-wiring acceptance path, not the proof of copy/prune correctness. **S4d-222 (accepted 2026-06-29, commit `0eaafdd5`)** added collision-avoiding default save filenames: the three default-name real-save sites (image drop, Render & Save sample, Open image) now call `DesktopSaveDecision.resolveUniqueOutputFile(outputDir, fmt)`, producing `watermarked.<ext>` if free else the smallest `watermarked_n.<ext>` suffix; tested in `:shared:desktopTest` (`DesktopSaveDecisionTest`, 6 new cases, 11/0 total). `Save as…`, preview temp, headless/demo, and `DesktopWatermarkFlow` defaults stay build-local by design. Release-grade Desktop save safety, **not** 1:1 parity. **S4d-228 (accepted 2026-06-29, commit `c833aea`)** then made image **drag/drop** multi-file: every supported dropped image is watermarked and saved **sequentially** (`DesktopSaveDecision.supportedImageFiles` → per-file `resolveUniqueOutputFile` → `runSaveFlow`; per-file failures continue the batch, a setup failure resets `busy` and shows a status), while **Open image** was still single-file and **Save as** stayed single-output. **S4d-229 (accepted 2026-06-29, commit `4c895aa`)** then made **Open image** multi-select too (native AWT `FileDialog.isMultipleMode = true` + `dialog.files`, filtered via the same `DesktopSaveDecision.supportedImageFiles`, saved through the same sequential resolve-after-write spine; single selection = one-item batch; real modal multi-select is a manual GUI residual), while **Save as** stays single-output. Remaining Desktop batch UX (folder batch, progress/cancel) and final screenshot/recording 1:1 parity remain deferred. |
3434
| **DynamicColorCapability** | Platform-neutral Material You gate (replaces direct `:cmonet`/`CMonet` in the live Compose consumers). **Landed (S4d-43, accepted 2026-06-25)** as `interface DynamicColorCapability { isAvailable(): Boolean; setForcedSupport(enabled: Boolean) }` in `shared/commonMain/.../platform`, bound as a Koin `single`. Android actual `AndroidDynamicColorCapability` delegates **verbatim** to unchanged `:cmonet` (same OEM allowlist + `dynamic_color_force` SP key); Desktop/iOS return `false` (static `Theme.kt` schemes are the fallback). Migrated consumers: `ComposeMainActivity` `AppTheme` gate + About-route initial toggle state, `AboutViewModel` toggle write — these must **not** call `CMonet` directly anymore. `:cmonet` is **retained** behind the actual; `MyApp.init`, `ContextExtension` getters, and the logo path still use `:cmonet` directly (owner-gated absorption follow-up). |
3535
| **ImageFormat (planned)** | App-owned export format enum (replaces `Bitmap.CompressFormat` in models/UI). JPEG honors quality (snapped to multiples of 20, min 20); PNG ignores it. |
36-
| **Template** | Saved watermark text snippets (Room entity), prepopulated from locale-selected bundled DBs (`ewm-db-ch.db` / `ewm-db-eng.db`). |
36+
| **Template** | Saved watermark text snippets (Room entity) now live in `shared/commonMain` with `TemplateRepository` + `TemplateEditor`; Android still opens locale-selected prepopulated DB assets (`ewm-db-ch.db` / `ewm-db-eng.db`) in compatibility mode. |
3737
| **Desktop templates persistence** | **S4d-141..S4d-143b (accepted 2026-06-28)** proved the first Desktop templates persistence lane before making templates visible. Desktop now has a `shared/desktopMain` `buildTemplateDatabase(dir)` that uses Room KMP with `BundledSQLiteDriver` for an empty local DB, plus `TemplateRepository`/`TemplateEditor` roundtrip coverage and a `:desktopApp --headless` app-level witness. `desktopApp` declares `room.runtime` because it directly consumes the public `RoomDatabase` supertype exposed by `AppDatabase`; Android stays on compatibility mode and remains `sqlite-bundled`/native-sqlite clean. **S4d-160** surfaced that empty-store path in the Desktop window with Save/List/Use/Delete, backed by the same `TemplateRepository` + `TemplateEditor`. **S4d-224 (accepted 2026-06-29, commit `c7959d0`)** implemented Desktop default seeding from the English Android seed DB via a `:shared` desktopMain resource + `TemplateDatabaseSeeds.unpackDefaultTemplateSeed` + `buildTemplateDatabase(dir, seedFile)`; the empty-store `buildTemplateDatabase(dir)` path is preserved. Because Room KMP off-Android does not expose `createFromFile`/`createFromAsset`, the seeded path copies the seed file to the final DB location before Room opens it and Room validates the `room_master_table` identity hash. Existing user DBs are not reseeded. **S4d-225 (accepted 2026-06-29, commit `875b105`)** made the seed choice locale-aware: `ewm-db-ch.db` for `zh` JVM locales, `ewm-db-eng.db` otherwise. |
38+
| **iOS templates persistence** | **S4d-231 (accepted 2026-06-29, commit `278d585`)** added the first iOS production templates Room builder: `shared/iosMain` `buildTemplateDatabase(dir, queryContext = Dispatchers.Default)` and a no-arg `NSDocumentDirectory` overload use Room KMP + `BundledSQLiteDriver` and the generated `AppDatabaseConstructor`. The runtime gate (`:shared:iosSimulatorArm64Test`) proves an **empty-store** `TemplateRepository`/`TemplateEditor` roundtrip (empty -> add/read -> update preserving id/creationDate -> delete empty). This is runtime persistence proof only: iOS seed packaging/default templates and Swift UI integration remain future slices. |
3839
| **Recovery mode** | Crash-loop self-heal: uncaught-exception handler counts crashes, relaunches into a recovery screen (today: legacy `MainActivity` + `activity_recovery` layout) offering reset. Must survive `MainActivity` retirement (plan C1.6). |
3940

4041
## User flows

0 commit comments

Comments
 (0)