Skip to content

Commit 557dc35

Browse files
committed
Document Desktop user save output
1 parent ce677bb commit 557dc35

3 files changed

Lines changed: 10 additions & 1 deletion

File tree

docs/CONTEXT.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ Domain vocabulary and invariants. Agents and humans: use these words with these
3030
| **iOS Swift bridge retention** | **S4d-82 (accepted 2026-06-27, commit `98e13d9`)**`iosApp/iosApp/WatermarkWorkflow.swift` retains one `IosUserConfigBridge` via `IosUserConfigBridgeKt.defaultIosUserConfigBridge()`; `loadUserConfigWitness()` calls `try await currentPreferences()` once on launch (read-only; `ContentView` `.task` triggers it) and stores a **non-visible** `@Published private(set) userConfigWitness` (not in any View body). **Link/async-interop witness only — no prefs/settings UI, no 1:1 parity.** Build-proven on the generic iOS Simulator SDK (`xcodebuild ... BUILD SUCCEEDED`). Two existing Swift files only (no new file / `project.pbxproj`); the S4d-81 Kotlin bridge is unchanged. Future prefs UI should replace this witness with real state usage. |
3131
| **iOS watermark-config bridge (first off-Android editor consumer)** | **S4d-102..S4d-118 (accepted 2026-06-27)** — the iOS app edits + persists watermark text, degree, tileMode, alpha, textColor, textSize, h/v gaps, textTypeface, and textStyle through the common `WaterMarkRepository` + `WatermarkConfigEditor`. `shared/src/iosMain/.../data/datastore/CreateDataStore.ios.kt` adds `createWaterMarkDataStore(name = WaterMarkRepository.SP_NAME)` (NSDocumentDirectory, mirroring `createUserConfigDataStore`); `IosWatermarkConfigBridge` exposes only suspend value APIs to Swift (no `Flow`/`DataStore`), with writes routed through `WatermarkConfigEditor`. iOS repo edges are platform-neutral: default text `"EasyWatermark 水印"`, the **pure** `WatermarkTileMode.fromStorageId` (the Android SDK-gated legacy tile migration does **not** apply off-Android), `println` logging; **no new dependency**. S4d-107/109/110 aligned fresh iOS renders to shared defaults for amber `#FFB800`, text size `14`, and gaps `0/0`; S4d-112 made iOS Skiko text consume `TextTypeface`; S4d-113 wired `TextPaintStyle`; **S4d-123 fixed the commonMain drawStyle paint gap found in S4d-122**, so `Stroke` is raster-honored on Desktop/iOS Skiko while Android text production remains native. **Build/runtime proof of the editor state flow — NOT 1:1 parity**. **S4d-114 accepted the `iconUri`/`markMode` decision pack; S4d-115 landed the Kotlin-only iOS icon render foundation**: `IosWatermarkRenderer.renderIconCell` wraps commonMain `composeIconCell`, and `composeIconOverImage` decodes background+icon bytes via `IosImageDecoder` then composes through `composeOverBackground`. The icon cell is rendered opaque and alpha is applied once at composition; this is perceptual iOS/Skiko behavior, not Android native byte parity. **S4d-116 added durable iOS icon-byte persistence:** `IosIconPersistence` copies bytes to app-private `NSDocumentDirectory/watermark_icons/icon_<NSUUID>`, `IosWatermarkConfigBridge.setIconFromBytes` persists `MediaRef(path)` through `WatermarkConfigEditor.updateIcon` (flipping Image mode), and cleanup rejects traversal/nested suffixes before deletion. **S4d-117 wired the iOS render branch:** Text mode still uses the existing text bridge; Image mode reads icon bytes through `IosWatermarkConfigBridge.currentIconBytes()` and renders via `IosWatermarkRenderBridge.renderIconWatermarkedPng`; missing/unreadable icon fails visibly. **S4d-118 wired the minimal picker UI:** a separate SwiftUI icon `PhotosPicker` passes picked bytes to `setIconFromBytes`, shows Text/Image mode plus a transient thumbnail, and re-renders the current source image. Real PHPicker grid-cell automation remains S4d-57-limited; `enableBounds` remains deferred. |
3232
| **Desktop prefs/headless flow** | **S4d-80 (accepted 2026-06-27, commit `3daa7c4`)** was the first app-entry (non-test) use of the common prefs repo: `desktopApp` constructs `UserConfigRepository(createUserConfigDataStore(dir = File("build/s4d80-desktop-userconfig")))` and proves read → `updateFormat(PNG)`/`updateCompressLevel(60)` → read via `runBlocking`. **S4d-119** then mapped the Desktop product-flow gaps and selected a no-new-dependency headless save slice before any Compose Desktop window. **S4d-120 (accepted, commit `7583099`)** added desktop `createWaterMarkDataStore`, a `WaterMarkRepository`/`WatermarkConfigEditor` roundtrip test, and a `desktopApp` headless open → edit → render → save flow that persists a watermark config, re-reads it, feeds the honored fields into `DesktopWatermarkComposer.composeOverRealImage`, and writes `build/s4d120-desktop-headless/watermarked.png`. **S4d-121 (accepted, commit `6c4ffdd`)** added the minimal Compose Desktop `Window`: no-arg `:desktopApp:run` opens `EasyWatermark — Desktop (S4d-121)`, while `--headless` keeps the bounded console/save automation. **S4d-122 (accepted, commit `4e4d928`)** made Desktop text rendering / real-image composition consume persisted `WaterMark.textColor` + `TextTypeface` (raster-honored; color default now shared amber) and pass `TextPaintStyle` through. **S4d-123 (accepted, commit `2ec876d`)** fixed commonMain drawStyle painting, so `TextPaintStyle.Stroke` is raster-honored on Desktop/iOS Skiko. **S4d-125** added the first interactive file input: the Desktop window's **"Open image..."** button uses native JDK/AWT `FileDialog`, reads selected bytes on `Dispatchers.IO`, and routes them through `DesktopWatermarkFlow.runSaveFlow(inputBytes, inputLabel)`; cancel is a no-op and `--headless` remains unchanged. **S4d-126/S4d-130** split Desktop output-format safely: S4d-127 added `DesktopWatermarkTextRenderer.encode(bitmap, format, quality)` with PNG byte-identical passthrough and JPEG alpha-flatten + JDK `ImageIO` quality while keeping `composeOverRealImage(..., format = ImageFormat.PNG, quality = 100)` as the PNG-default composer/golden path. S4d-128 wired `DesktopWatermarkFlow.runSaveFlow` to `UserConfigRepository.userPreferences.first()`: fresh Desktop prefs now match shared/Android `(JPEG, 80)`, null output paths choose `watermarked.jpg`/`watermarked.png` by effective format, and PNG remains available when prefs select PNG. S4d-130 added a minimal Desktop window selector: `JPEG / 80` and `PNG / 100` preset buttons persist via the shared `OutputPrefsEditor` over the same `UserConfigRepository` the save flow reads. **Still not the full Compose 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 yet. |
33-
| **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. Remaining Desktop gates include template edit-in-place/default seeding, 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; default-template seeding remains deferred/owner-priority-gated. |
33+
| **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. Remaining Desktop gates include template edit-in-place/default seeding, 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; default-template seeding remains deferred/owner-priority-gated. **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. |
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. |
3636
| **Template** | Saved watermark text snippets (Room entity), prepopulated from locale-selected bundled DBs (`ewm-db-ch.db` / `ewm-db-eng.db`). |

progress.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
# Progress Log
22

3+
## 2026-06-28 — S4d-217 Desktop window saves to user output dir accepted (+S4d-217b docs)
4+
5+
- **S4d-217 (implementation, accepted; commit `ce677bb2 Route Desktop saves to user output dir`, `DesktopWindow.kt` only, +34/-1):** the interactive Desktop window's three REAL save actions — image **drop**, **Render & Save sample**, and **Open image…** — now write the watermarked output to a user output dir instead of the repo-local `build/s4d120-desktop-headless` default. A new same-file helper `resolveDesktopOutputDir()` returns `~/Pictures` when that directory exists, else `~/.easywatermark/output` (`File(resolveDesktopAppDataDir(), "output")`), created if missing; `launchDesktopWindow()` remembers it, and each save passes `outputFile = File(outputDir, DesktopSaveDecision.defaultOutputFileName(fmt))` with `fmt = userConfigRepo.userPreferences.first().outputFormat` (the same value `runSaveFlow` reads → extension matches).
6+
- **Intentionally unchanged (do NOT "fix"):** `Save as…` still writes the user-chosen dialog path; the preview temp stays `build/s4d147-desktop-preview/preview.img`; `DesktopWatermarkFlow` defaults + `Main.kt` headless/demo witness stay build-local (the `--headless` run confirmed it still wrote `build/s4d120-desktop-headless/watermarked.jpg`).
7+
- **Verification:** `:desktopApp:compileKotlin` + `:desktopApp:run --args='--headless'` green (`--max-workers=8`, daemon stopped). On this host `~/Pictures` exists → `resolveDesktopOutputDir()` resolves to `~/Pictures`. The GUI save **click was not automated**, so the file actually landing in the user dir is diff/branch-proven, NOT runtime-automated — a coordinator manual acceptance check. No stray output files were written.
8+
- **Boundary (no overclaim):** output filename is fixed by format (`watermarked.<ext>`), so repeated real saves overwrite — collision-avoiding names remain deferred. Release-grade Desktop save-location correctness, **not** Android v2.10.0 1:1 UI/UX parity; default-template seeding stays deferred. PR #358 Draft.
9+
- **S4d-217b (this entry):** docs-only closeout — recorded the above in `progress.md`, `task_plan.md`, and the `docs/CONTEXT.md` Desktop-window row.
10+
311
## 2026-06-28 — S4d-215 Desktop window stable persistence accepted (+S4d-215b docs)
412

513
- **S4d-215 (implementation, accepted; commit `873dbcbd Route Desktop window state to user data dir`, `DesktopWindow.kt` only, +31/-6):** the interactive Compose Desktop window now persists its user state — watermark config, output prefs, and the templates Room DB — under the stable per-user dir `~/.easywatermark` instead of repo-local `build/` dev paths. A new same-file helper `resolveDesktopAppDataDir()` returns `~/.easywatermark` (matching the `CreateDataStore.desktop.kt` convention) and is passed to `DesktopWatermarkFlow.buildRepository(dir = appDataDir)`, `buildUserConfigRepository(dir = appDataDir)`, and `buildTemplateDatabase(appDataDir)`. If `user.home` is null/blank it falls back to a repo-local `build/desktop-app-data` dir with a `System.err` warning (no crash).

0 commit comments

Comments
 (0)