{"owner":"GargantuaX","repo":"gemini-watermark-remover","hasSkills":true,"hasMcp":false,"mcpConfig":null,"found":["AGENTS.md"],"skills":{"AGENTS.md":"# AGENTS.md\n\n## Best-Effort Product Principle\n\n- Treat best-effort processing as the minimum product guarantee: when the input is available and processing can run, return the best result the current system can produce.\n- A result with visible residuals or minor imperfections is preferable to returning the unchanged input or no result.\n- Low confidence, incomplete catalog coverage, or a quality warning should not by themselves block output. Use the most plausible validated/fallback strategy, produce the result, and surface the uncertainty as non-blocking metadata or UI copy.\n- Do not tell users to refresh or retry when repeating the same action is not expected to change the outcome. Retry guidance is allowed only when there is a concrete, recoverable state change and the user can take an actionable step.\n- Reserve hard failure for cases where processing truly cannot run or no usable input/output can be produced; do not use failure as a substitute for a best-effort result.\n\n## Debug Workflow\n\n### Allenk Upstream Reference\n\n- The local fork of allenk/GeminiWatermarkTool is at `${GWR_ALLENK_ROOT}`.\n- Local-only path variables are configured in `.env`; use `.env.example` as the public template.\n- When learning or comparing upstream watermark catalog specs, alpha maps, video rules, FDnCNN behavior, or CLI behavior, prefer this local fork over temporary clones or remote README-only assumptions.\n- Treat upstream specs as candidate priors until they are verified against this repo's sample scoring, crop sheets, and output residual gates.\n\n### Data-Driven Watermark Investigation\n\n- When user-provided samples show obvious watermarks being skipped or poorly removed, treat the first task as pattern discovery, not threshold tuning.\n- Derive the watermark geometry and rendering rules from the samples before changing removal heuristics:\n  - exact anchor position (`x/y`, right/bottom margins)\n  - watermark size and aspect\n  - subpixel offset / scale drift\n  - alpha map shape and alpha strength\n  - background-dependent compositing behavior\n- Build batch reports and visual artifacts from the sample set:\n  - full image list with dimensions and scores\n  - bottom-right crop sheets\n  - candidate-position overlays when debugging detection\n  - before/after crops for every changed strategy\n- Prefer improving candidate localization and alpha estimation over loosening safety/protection gates.\n- Safety gates are a final fallback. If a visible watermark is skipped, first ask whether the selected position/size/alpha candidate is wrong or incomplete.\n- Do not generalize from a single image when the user supplied a sample set. Cluster samples by size, anchor, background, and residual behavior, then make the smallest algorithm change supported by that cluster.\n- When residual artifacts remain after mathematically valid inverse alpha removal, do not assume stronger inpaint, preview-only priors, or subpixel sweeps are safe production fixes. First verify whether the real alpha edge profile / antialiasing model differs from the current template, and keep any visual cleanup evidence-gated.\n- Do not treat a restoration score measured only with the candidate's own selected alpha map and geometry as independent cleanliness or damage evidence. A wrong candidate can erase the pattern it scores against while leaving an obvious hole, shifted star, or larger residual outside that ROI. Use those self-consistent scores for candidate ranking only; promotion evidence should come from frozen cross-profile/decoy signals and blind visual review.\n- Preserve signed source polarity when a rescue assumes a white alpha template. A localized gradient or edge witness can be strong even when the spatial template polarity is inverted; the fresh exact-48 R96 case 77 passed the decoy/edge gates but inverse removal turned it into a dark star. Keep the exact-48 R96 white source-witness gate constrained to `spatialSignedTarget > 0`; do not replace this with an absolute/max score or generalize it to other polarity models without independent validation.\n- Issue #123 independently confirmed the same collision class at exact-96 R192: a near-zero negative spatial witness with highly localized unsigned edges can be structured dark content, and applying the white `20260520` rescue creates a visible black hole. Require positive signed spatial evidence for the normal white rescue. Only retain the established structured-content exception when a drifted dark-polarity selection independently exists and the exact anchor has sufficiently strong, localized spatial evidence; a gradient/edge witness alone is not enough.\n- Treat `texture.hardReject`, `visibleDarkHole`, and selection-stage damage warnings as noisy review signals, not independent cleanliness truth. Confirmed clean exact-48 rescues can trigger them, while visible dark residuals can still retain a top-level `clean` status. Do not directly map these signals to `qualityStatus`, candidate ranking, blocking, or retry behavior without frozen independent validation.\n- Background-endpoint consistency is not by itself a safe gate for row/patch inpainting. A discovery-only endpoint threshold improved one dark-hole sample but later triggered on a clean fresh exact-48 rescue and introduced visible horizontal banding. Keep such repair offline until a preregistered temporal holdout demonstrates both useful recall and no clean-output damage.\n\n## Deployment Note\n\n- The active local/debugging build surface is the generated `dist/` directory.\n- Keep deployment assumptions aligned with the current repo contents.\n\n### Website SDK Release Sync\n\n- `https://geminiwatermarkremover.io/` is deployed from the separate local `geminiwatermarkremover.io` repository; resolve its checkout from local configuration instead of committing a machine-specific absolute path.\n- Publishing this repository to npm, GitHub Releases, or the Chrome Web Store does **not** update the website's image-processing Worker. The website pins `@pilio/gemini-watermark-remover` in both `package.json` and `pnpm-lock.yaml` and must be upgraded, rebuilt, tested, and deployed separately.\n- After every core package release, compare the website's pinned SDK version with npm `latest` before treating the website as current. A current website userscript or extension download does not prove that the website Worker uses the current SDK.\n- Verify the deployed Worker itself with a real sample and inspect its selected candidate / strategy metadata or other version-specific behavior. Do not rely only on CDN asset timestamps, package release status, userscript `@version`, or successful HTTP responses.\n- Known failure mode from issue #115: the public site still bundled SDK `1.0.31` after `1.0.33` was published. Upgrading exposed a second path mismatch: the browser engine loaded the real `36-v2` template while the sync image-data SDK interpolated size 36, allowing local validation to select `48x48` while the deployed Worker skipped. Keep runtime alpha-map catalogs and candidate orchestration aligned across SDK entry points.\n- Treat the website as a separate release surface. If its main worktree contains unrelated user changes, prepare and validate the dependency upgrade in an isolated worktree so those changes are not overwritten or accidentally deployed.\n\n### Gemini Image Size / Watermark Catalog\n\nUse this catalog when checking watermark size and anchor regressions against Gemini image outputs.\n\nCurrent code expectation:\n\n- `0.5K` preview outputs use a `48x48` watermark with `32px` right/bottom margins.\n- `1K`, `2K`, and `4K` preview/full outputs use a `96x96` watermark with `64px` right/bottom margins.\n- New reported anchor type: exact `1K` / `2K` / `4K` catalog sizes also consider a secondary `96x96` candidate with `192px` right/bottom margins. Keep the canonical `64px` anchor first; the `192px` anchor must remain evidence-gated by candidate validation.\n- Known confirmed local exception: `2816x1536` uses `96x96` with `192px` right/bottom margins (`2k-new-margin` in `src/core/geminiSizeCatalog.js`).\n- The local catalog also keeps a confirmed `1408x768` 16:9 `1K` variant.\n\n#### Gemini 3.1 Flash Image Preview\n\n| Aspect ratio | 512 resolution | 0.5K tokens | 1K resolution | 1K tokens | 2K resolution | 2K tokens | 4K resolution | 4K tokens |\n|---|---|---:|---|---:|---|---:|---|---:|\n| **1:1** | 512x512 | 747 | 1024x1024 | 1120 | 2048x2048 | 1680 | 4096x4096 | 2520 |\n| **1:4** | 256x1024 | 747 | 512x2048 | 1120 | 1024x4096 | 1680 | 2048x8192 | 2520 |\n| **1:8** | 192x1536 | 747 | 384x3072 | 1120 | 768x6144 | 1680 | 1536x12288 | 2520 |\n| **2:3** | 424x632 | 747 | 848x1264 | 1120 | 1696x2528 | 1680 | 3392x5056 | 2520 |\n| **3:2** | 632x424 | 747 | 1264x848 | 1120 | 2528x1696 | 1680 | 5056x3392 | 2520 |\n| **3:4** | 448x600 | 747 | 896x1200 | 1120 | 1792x2400 | 1680 | 3584x4800 | 2520 |\n| **4:1** | 1024x256 | 747 | 2048x512 | 1120 | 4096x1024 | 1680 | 8192x2048 | 2520 |\n| **4:3** | 600x448 | 747 | 1200x896 | 1120 | 2400x1792 | 1680 | 4800x3584 | 2520 |\n| **4:5** | 464x576 | 747 | 928x1152 | 1120 | 1856x2304 | 1680 | 3712x4608 | 2520 |\n| **5:4** | 576x464 | 747 | 1152x928 | 1120 | 2304x1856 | 1680 | 4608x3712 | 2520 |\n| **8:1** | 1536x192 | 747 | 3072x384 | 1120 | 6144x768 | 1680 | 12288x1536 | 2520 |\n| **9:16** | 384x688 | 747 | 768x1376 | 1120 | 1536x2752 | 1680 | 3072x5504 | 2520 |\n| **16:9** | 688x384 | 747 | 1376x768 | 1120 | 2752x1536 | 1680 | 5504x3072 | 2520 |\n| **21:9** | 792x168 | 747 | 1584x672 | 1120 | 3168x1344 | 1680 | 6336x2688 | 2520 |\n\n#### Gemini 3 Pro Image Preview\n\n| Aspect ratio | 1K resolution | 1K tokens | 2K resolution | 2K tokens | 4K resolution | 4K tokens |\n|---|---|---:|---|---:|---|---:|\n| **1:1** | 1024x1024 | 1120 | 2048x2048 | 1120 | 4096x4096 | 2000 |\n| **2:3** | 848x1264 | 1120 | 1696x2528 | 1120 | 3392x5056 | 2000 |\n| **3:2** | 1264x848 | 1120 | 2528x1696 | 1120 | 5056x3392 | 2000 |\n| **3:4** | 896x1200 | 1120 | 1792x2400 | 1120 | 3584x4800 | 2000 |\n| **4:3** | 1200x896 | 1120 | 2400x1792 | 1120 | 4800x3584 | 2000 |\n| **4:5** | 928x1152 | 1120 | 1856x2304 | 1120 | 3712x4608 | 2000 |\n| **5:4** | 1152x928 | 1120 | 2304x1856 | 1120 | 4608x3712 | 2000 |\n| **9:16** | 768x1376 | 1120 | 1536x2752 | 1120 | 3072x5504 | 2000 |\n| **16:9** | 1376x768 | 1120 | 2752x1536 | 1120 | 5504x3072 | 2000 |\n| **21:9** | 1584x672 | 1120 | 3168x1344 | 1120 | 6336x2688 | 2000 |\n\n#### Gemini 2.5 Flash Image\n\n| Aspect ratio | Resolution | Tokens |\n|---|---|---:|\n| 1:1 | 1024x1024 | 1290 |\n| 2:3 | 832x1248 | 1290 |\n| 3:2 | 1248x832 | 1290 |\n| 3:4 | 864x1184 | 1290 |\n| 4:3 | 1184x864 | 1290 |\n| 4:5 | 896x1152 | 1290 |\n| 5:4 | 1152x896 | 1290 |\n| 9:16 | 768x1344 | 1290 |\n| 16:9 | 1344x768 | 1290 |\n| 21:9 | 1536x672 | 1290 |\n\n### Fixed Tampermonkey / Gemini Environment\n\n- Fixed Chrome profile: `.chrome-debug/tampermonkey-profile`\n- Fixed CDP port: `9226`\n- Default proxy: `http://127.0.0.1:7890`\n- Production userscript artifact: `dist/userscript/gemini-watermark-remover.user.js`\n\nPlatform notes:\n\n- macOS launcher uses `/usr/bin/open -na \"Google Chrome.app\" --args ...`\n- macOS default Chrome app lookup:\n  - `/Applications/Google Chrome.app`\n  - `~/Applications/Google Chrome.app`\n- Override Chrome location on any platform with `GWR_DEBUG_EXECUTABLE_PATH`\n\n### Open the Fixed Profile\n\n- Shell launcher: `./scripts/open-fixed-chrome-profile.sh`\n- Node launcher: `node scripts/open-tampermonkey-profile.js --cdp-port 9226`\n- CMD launcher: `.\\scripts\\open-fixed-chrome-profile.cmd`\n- PowerShell launcher: `.\\scripts\\open-fixed-chrome-profile.ps1`\n\nDefault behavior:\n\n- Reuse the fixed Chrome profile\n- Open remote debugging on port `9226`\n- Use the local proxy\n- The plain Node launcher opens the Tampermonkey Chrome Web Store page by default unless `--url` is passed\n- The shell wrapper opens the local probe page on `http://127.0.0.1:4173/tampermonkey-worker-probe.html` by default and still forwards extra args\n- The current CMD/PowerShell wrappers are fixed shortcuts to the same local probe page; they do not forward additional CLI args\n\nmacOS quick path:\n\n1. Run `pnpm build`\n2. Start the local artifact server, for example `pnpm dev`\n3. Open the fixed profile with `./scripts/open-fixed-chrome-profile.sh --url https://gemini.google.com/app`\n4. If you do not want the repo's default proxy, pass `--proxy off`\n\n### One-Time Manual Setup\n\nDo this only once in the fixed profile:\n\n1. Install Tampermonkey.\n2. Enable `Allow User Scripts` in Chrome extension details.\n3. Keep Developer Mode enabled.\n4. Install `public/tampermonkey-worker-probe.user.js` when local probe validation is needed.\n5. Install or reinstall the production userscript from the current local build server when validating the latest build.\n   - Use the active local build server URL printed by `pnpm dev`\n   - `pnpm dev` starts probing from `http://127.0.0.1:4173/` and will auto-increment if that port is occupied\n   - A previously confirmed request-layer debugging session used `http://127.0.0.1:4317/userscript/gemini-watermark-remover.user.js`, but do not assume that is still current\n\n### Local Build and Services\n\n- Production build: `pnpm build`\n- Local dist server: `pnpm dev`, `pnpm serve`, or the active local build server for this worktree\n- Default `pnpm dev` start port: `http://127.0.0.1:4173/`\n- Actual active dev server port may be higher; always trust the current `pnpm dev` console output\n- Probe smoke test: `pnpm probe:tm`\n- Installed userscript freshness check: `pnpm probe:tm:freshness`\n- Open fixed profile: `pnpm probe:tm:profile`\n\nCurrent `pnpm probe:tm` behavior:\n\n- in `run` mode it now attempts a Tampermonkey userscript freshness preflight first\n- if freshness returns `stale`, `probe:tm` must fail before running the worker/bridge smoke page\n- if the freshness preflight context itself is unavailable, for example:\n  - fixed `9226` profile is not open\n  - the CDP endpoint is unavailable\n  - the editor is mid-navigation\n  then the preflight is recorded as `skipped` and the smoke flow continues\n- this keeps stale installs fail-fast without making `probe:tm` hard-depend on a manually opened editor page\n\n### Installed Userscript Freshness Check\n\nWhen real-page behavior does not match the current worktree, verify the installed userscript body, not just the script name or `@version`.\n\n- Same `@version` does not guarantee the fixed profile is running the latest build.\n- A stale Tampermonkey script can still show:\n  - `[Gemini Watermark Remover] Initializing...`\n  - `[Gemini Watermark Remover] Ready`\n  - while silently missing newer request-layer fixes such as the download sticky intent window\n- Preferred check:\n  1. Open the Tampermonkey editor for `Gemini NanoBanana 图片水印移除`\n  2. Run `pnpm probe:tm:freshness`\n  3. Read `.artifacts/tampermonkey-freshness/latest.json`\n- If you just reinstalled the userscript and the check still reports `stale`, refresh the already-open Tampermonkey editor page once, then run `pnpm probe:tm:freshness` again.\n- Current command behavior:\n  - exits `0` when the installed userscript exactly matches the local `dist/userscript/gemini-watermark-remover.user.js`\n  - exits `1` when the installed userscript is stale or mismatched\n  - compares full normalized source hashes, not just `@version`\n  - also reports whether expected markers are missing\n- Current report path:\n  - `.artifacts/tampermonkey-freshness/latest.json`\n- Manual fallback if needed:\n  1. Compare the installed source against `dist/userscript/gemini-watermark-remover.user.js`\n  2. Confirm the installed source contains the expected newer markers before continuing real-page debugging\n     - `DEFAULT_DOWNLOAD_STICKY_WINDOW_MS`\n     - `downloadStickyUntil`\n     - `getActionContextFromIntentGate(intentGate = null, candidate = null)`\n  3. Refresh the real Gemini page after the fixed profile is updated\n\n### Real Gemini Page Validation\n\nTarget page:\n\n- `https://gemini.google.com/app`\n\nMinimum validation flow:\n\n1. Run `pnpm build`\n2. Reinstall the latest userscript in the fixed profile\n3. Open the real Gemini page\n4. Check that the console shows:\n   - `[Gemini Watermark Remover] Initializing...`\n   - `[Gemini Watermark Remover] Ready`\n5. If bridge validation is needed, trigger from page side:\n   - `gwr:userscript-process-request`\n   - Expect `gwr:userscript-process-response`\n\nCurrent confirmed request-layer behavior on the fixed profile:\n\n- `copy` can populate the strict original binding path through real `rd-gg` asset fetches and then place a processed `image/png` onto the clipboard\n- `download` stays on Gemini's native `c8o8Fe -> gg-dl -> rd-gg-dl` export flow; the userscript does not cancel the click\n- the userscript keeps explicit download intent alive for Gemini download asset URLs long enough to catch late `rd-gg-dl` requests on the native chain\n- If the original URL binding is unavailable when the required download/original request arrives, the action must fail closed with:\n  - `无法获取原图，请刷新页面后重试`\n- A successful real-page full-size download currently produces:\n  - a browser `download` event\n  - a blob-backed saved file such as `Gemini_Generated_Image_vusbaevusbaevusb.png`\n  - local detector result `skipReason=no-watermark-detected`\n\n### Real-Page Pixel Verification\n\n- Single image compare: `pnpm probe:real-page:compare`\n- All ready images on the current Gemini page: `pnpm probe:real-page:compare --all`\n- Latest batch summary:\n  - `.artifacts/real-page-pixel-compare/latest-summary.json`\n- Complex-figure validation checklist:\n  - `docs/complex-figure-verification-checklist.md`\n\nUse this when page-level screenshots are not enough and you need original blob pixel metrics for `before/after`.\n\nDo not rely on hardcoded sample counts here. Treat `.artifacts/real-page-pixel-compare/latest-summary.json` as the source of truth for the current worktree.\n\nCurrent checked local summary in this worktree:\n\n- artifact timestamp: `2026-04-06T07:25:13.120Z`\n- `total = 1`\n- the recorded ready image landed at:\n  - `afterSpatial ~= -0.2707`\n  - `afterGradient ~= 0.1075`\n\nHistorical multi-image baselines from earlier fixed-profile sessions should be treated as dated reference points, not as the current expected batch shape.\n\n### Current Preview Display Path\n\nCurrent production expectation for real Gemini preview display is:\n\n- keep request-layer preview interception enabled as an early processing source and observability point\n- do not assume Gemini's final displayed `blob:` image is fully controlled by the page `fetch` hook chain\n- keep `src/shared/pageImageReplacement.js` as the production display path for preview replacement\n- treat request-layer preview handling as supportive, but rely on page-level replacement to guarantee the user-visible preview is actually de-watermarked\n\nCurrent real-page success signal for preview display:\n\n- the displayed image reaches:\n  - `data-gwr-page-image-state=ready`\n  - `data-gwr-watermark-object-url=blob:...`\n- detector on that processed overlay blob reports:\n  - `skipReason=no-watermark-detected`\n\n### Confirmed Performance Pitfalls\n\nWhen the user reports \"this version became much slower\", check these first before touching the core algorithm:\n\n1. Page runtime / page bridge did not actually install into the real Gemini page.\n   - Symptom:\n     - Real page silently falls back to the userscript sandbox / slow main-thread path.\n     - Earlier bad runs showed `removeWatermarkMs` on the order of `11s ~ 13s` for a single preview image.\n   - Verify:\n     - Reinstall the latest userscript from the current active build server\n       - use the actual userscript URL from the active `pnpm dev` server\n       - the server starts probing from `http://127.0.0.1:4173/` and may auto-increment\n     - Refresh the real page\n     - Confirm console reaches `Initializing...` and `Ready`\n     - Confirm preview images continue to `page image process success`\n\n2. Preview queue blocked by a `blob:` image that is not renderable yet.\n   - Symptom:\n     - One image gets stuck at `state=processing`\n     - The element often has `complete=false`, `naturalWidth=0`, `naturalHeight=0`\n     - Later images stop progressing because the serial queue is effectively wedged\n   - Current fix:\n     - `src/shared/pageImageReplacement.js` now waits for renderability and retries instead of processing immediately\n   - If this regresses, inspect the waiting / retry path before changing watermark math\n\n3. Preview-anchor cleanup accidentally doing expensive work that is not adopted.\n   - Symptom:\n     - Main thread is busy, but output source does not include a successful `+subpixel`\n     - Earlier bad runs showed `subpixelRefinementMs ~= 80ms ~ 115ms` on strong preview samples with no accepted subpixel shift\n   - Current fix:\n     - preview-anchor cleanup no longer runs the expensive subpixel refinement path\n     - It relies on cheaper preview edge cleanup instead\n   - Rule:\n     - Do not re-enable preview-anchor subpixel search unless you have a real fixture that proves the accepted result is both safer and materially better\n\n### Confirmed Quality / Performance Tradeoff\n\nFor strong real-page preview samples, the current strategy is:\n\n- Skip expensive preview-anchor subpixel refinement\n- Use stronger preview edge cleanup only when:\n  - the image is a preview-anchor style match\n  - spatial residual is already low enough to be safe\n  - gradient residual is still strong enough to justify cleanup\n\nWhy this exists:\n\n- It lowers strong-sample real-page residual gradient from roughly `0.53` to roughly `0.30`\n- It keeps preview-anchor cleanup latency low by avoiding no-op subpixel sweeps\n- It accepts some spatial drift to stay within a safe residual envelope rather than overfitting and risking content damage\n\n### Confirmed Download / Copy Integration Constraint\n\nDo not re-enable the old active direct-download click hook in production.\n\nConfirmed real-page failure mode on `https://gemini.google.com/u/1/app/d3cd7d14852ecd3b?pageId=none`:\n\n- When the userscript intercepts `下载完整尺寸的图片` at capture time and calls `preventDefault()/stopImmediatePropagation()`, Gemini's own download flow is blocked before it can issue its native `c8o8Fe` / `rd-gg-dl` chain.\n- In that state, the userscript only has the earlier history bootstrap bindings from `hNvQHb`.\n- Current real `hNvQHb` bindings are mostly preview-style `gg/...=s0` URLs, not the final native download URL.\n- Falling back to those preview bindings makes the userscript attempt its own fetch path too early, which previously surfaced as:\n  - `Original image is unavailable for download processing`\n  - or `Failed to fetch image: 403`\n  - followed by the user-facing retry alert\n\nThere is a second real-page failure mode to keep in mind even after removing the active click hook:\n\n- Gemini's native full-size download chain can be much slower than the base intent window.\n- On the 2026-04-04 fixed-profile trace:\n  - `c8o8Fe` request started about `+50ms` after click\n  - `c8o8Fe` response returned about `+22.4s`\n  - final `rd-gg-dl ... image/png` arrived about `+23.9s`\n- A plain `5000ms` intent window expires far too early, so the passive request hook stops processing before the final full-size image request appears.\n\nCurrent correct production shape:\n\n- keep the intent gate for copy / download gestures\n- keep Gemini RPC discovery hooks (`hNvQHb`, `c8o8Fe`, related batchexecute responses)\n- keep generated-asset fetch interception for the native request flow\n- let Gemini continue its own click handling\n- do not block the button just to start a parallel userscript-only download path\n- keep a download-specific sticky intent window for Gemini download asset URLs\n  - current default: `30000ms`\n  - release it after terminal success/failure so it does not leak across actions\n\nCurrent confirmed real-page result with the passive native chain plus sticky download intent:\n\n- `下载完整尺寸的图片` produced a browser `download` event\n- the resulting download used a blob URL generated from the page flow\n- the saved file was `3136 x 1344`, about `5.4MB`, sha256 `4e945813779b58a5eda0f01f7973c924210477a84ae1d3826138f57b60eb691f`\n- local detector on that downloaded file reported:\n  - `skipReason = no-watermark-detected`\n  - `originalSpatialScore ~= -0.4096`\n  - `originalGradientScore ~= 0.0826`\n- no `无法获取原图，请刷新页面后重试` alert appeared\n- `复制图片` wrote an `image/png` item to the clipboard without a failure alert\n- the request-layer verification report path is session-specific; do not assume an older dated artifact still exists in the current worktree\n\n### Worker Debug Flow\n\nFor reproduction only. This is not the default production path.\n\n1. In the real page DevTools, run:\n   - `localStorage.setItem('__gwr_force_inline_worker__', '1')`\n2. Refresh `https://gemini.google.com/app/...`\n3. Inspect console logs\n\nCurrent confirmed result:\n\n- The real Gemini page can attempt to start the inline worker.\n- The worker crashes during startup because of CSP / runtime restrictions.\n- Production must stay on the main-thread path by default.\n- The force flag is for debugging only.\n\n### Worker Success / Failure Criteria\n\nDo not treat `new Worker(blobUrl)` returning without an immediate throw as proof that the worker is usable.\n\nCurrent correct criteria:\n\n- If `[Gemini Watermark Remover] Worker acceleration enabled` appears, that only means startup was attempted.\n- The worker is only considered usable if the startup handshake succeeds.\n- If `[Gemini Watermark Remover] Worker initialization failed, using main thread: ...` appears, safe fallback has happened.\n- After fallback, the page should still continue with:\n  - `page image process start`\n  - `page image process strategy`\n  - `page image process success`\n\n### Known Constraints\n\n- Direct `new Worker(blobUrl)` from Tampermonkey DOM sandbox is not reliable in the current environment.\n- The real Gemini page has CSP restrictions, so worker assumptions must not be based on probe-page success.\n- Runtime flags must be read across `unsafeWindow`; reading only the userscript sandbox `globalThis/localStorage` is insufficient.\n"},"files":{"AGENTS.md":"# AGENTS.md\n\n## Best-Effort Product Principle\n\n- Treat best-effort processing as the minimum product guarantee: when the input is available and processing can run, return the best result the current system can produce.\n- A result with visible residuals or minor imperfections is preferable to returning the unchanged input or no result.\n- Low confidence, incomplete catalog coverage, or a quality warning should not by themselves block output. Use the most plausible validated/fallback strategy, produce the result, and surface the uncertainty as non-blocking metadata or UI copy.\n- Do not tell users to refresh or retry when repeating the same action is not expected to change the outcome. Retry guidance is allowed only when there is a concrete, recoverable state change and the user can take an actionable step.\n- Reserve hard failure for cases where processing truly cannot run or no usable input/output can be produced; do not use failure as a substitute for a best-effort result.\n\n## Debug Workflow\n\n### Allenk Upstream Reference\n\n- The local fork of allenk/GeminiWatermarkTool is at `${GWR_ALLENK_ROOT}`.\n- Local-only path variables are configured in `.env`; use `.env.example` as the public template.\n- When learning or comparing upstream watermark catalog specs, alpha maps, video rules, FDnCNN behavior, or CLI behavior, prefer this local fork over temporary clones or remote README-only assumptions.\n- Treat upstream specs as candidate priors until they are verified against this repo's sample scoring, crop sheets, and output residual gates.\n\n### Data-Driven Watermark Investigation\n\n- When user-provided samples show obvious watermarks being skipped or poorly removed, treat the first task as pattern discovery, not threshold tuning.\n- Derive the watermark geometry and rendering rules from the samples before changing removal heuristics:\n  - exact anchor position (`x/y`, right/bottom margins)\n  - watermark size and aspect\n  - subpixel offset / scale drift\n  - alpha map shape and alpha strength\n  - background-dependent compositing behavior\n- Build batch reports and visual artifacts from the sample set:\n  - full image list with dimensions and scores\n  - bottom-right crop sheets\n  - candidate-position overlays when debugging detection\n  - before/after crops for every changed strategy\n- Prefer improving candidate localization and alpha estimation over loosening safety/protection gates.\n- Safety gates are a final fallback. If a visible watermark is skipped, first ask whether the selected position/size/alpha candidate is wrong or incomplete.\n- Do not generalize from a single image when the user supplied a sample set. Cluster samples by size, anchor, background, and residual behavior, then make the smallest algorithm change supported by that cluster.\n- When residual artifacts remain after mathematically valid inverse alpha removal, do not assume stronger inpaint, preview-only priors, or subpixel sweeps are safe production fixes. First verify whether the real alpha edge profile / antialiasing model differs from the current template, and keep any visual cleanup evidence-gated.\n- Do not treat a restoration score measured only with the candidate's own selected alpha map and geometry as independent cleanliness or damage evidence. A wrong candidate can erase the pattern it scores against while leaving an obvious hole, shifted star, or larger residual outside that ROI. Use those self-consistent scores for candidate ranking only; promotion evidence should come from frozen cross-profile/decoy signals and blind visual review.\n- Preserve signed source polarity when a rescue assumes a white alpha template. A localized gradient or edge witness can be strong even when the spatial template polarity is inverted; the fresh exact-48 R96 case 77 passed the decoy/edge gates but inverse removal turned it into a dark star. Keep the exact-48 R96 white source-witness gate constrained to `spatialSignedTarget > 0`; do not replace this with an absolute/max score or generalize it to other polarity models without independent validation.\n- Issue #123 independently confirmed the same collision class at exact-96 R192: a near-zero negative spatial witness with highly localized unsigned edges can be structured dark content, and applying the white `20260520` rescue creates a visible black hole. Require positive signed spatial evidence for the normal white rescue. Only retain the established structured-content exception when a drifted dark-polarity selection independently exists and the exact anchor has sufficiently strong, localized spatial evidence; a gradient/edge witness alone is not enough.\n- Treat `texture.hardReject`, `visibleDarkHole`, and selection-stage damage warnings as noisy review signals, not independent cleanliness truth. Confirmed clean exact-48 rescues can trigger them, while visible dark residuals can still retain a top-level `clean` status. Do not directly map these signals to `qualityStatus`, candidate ranking, blocking, or retry behavior without frozen independent validation.\n- Background-endpoint consistency is not by itself a safe gate for row/patch inpainting. A discovery-only endpoint threshold improved one dark-hole sample but later triggered on a clean fresh exact-48 rescue and introduced visible horizontal banding. Keep such repair offline until a preregistered temporal holdout demonstrates both useful recall and no clean-output damage.\n\n## Deployment Note\n\n- The active local/debugging build surface is the generated `dist/` directory.\n- Keep deployment assumptions aligned with the current repo contents.\n\n### Website SDK Release Sync\n\n- `https://geminiwatermarkremover.io/` is deployed from the separate local `geminiwatermarkremover.io` repository; resolve its checkout from local configuration instead of committing a machine-specific absolute path.\n- Publishing this repository to npm, GitHub Releases, or the Chrome Web Store does **not** update the website's image-processing Worker. The website pins `@pilio/gemini-watermark-remover` in both `package.json` and `pnpm-lock.yaml` and must be upgraded, rebuilt, tested, and deployed separately.\n- After every core package release, compare the website's pinned SDK version with npm `latest` before treating the website as current. A current website userscript or extension download does not prove that the website Worker uses the current SDK.\n- Verify the deployed Worker itself with a real sample and inspect its selected candidate / strategy metadata or other version-specific behavior. Do not rely only on CDN asset timestamps, package release status, userscript `@version`, or successful HTTP responses.\n- Known failure mode from issue #115: the public site still bundled SDK `1.0.31` after `1.0.33` was published. Upgrading exposed a second path mismatch: the browser engine loaded the real `36-v2` template while the sync image-data SDK interpolated size 36, allowing local validation to select `48x48` while the deployed Worker skipped. Keep runtime alpha-map catalogs and candidate orchestration aligned across SDK entry points.\n- Treat the website as a separate release surface. If its main worktree contains unrelated user changes, prepare and validate the dependency upgrade in an isolated worktree so those changes are not overwritten or accidentally deployed.\n\n### Gemini Image Size / Watermark Catalog\n\nUse this catalog when checking watermark size and anchor regressions against Gemini image outputs.\n\nCurrent code expectation:\n\n- `0.5K` preview outputs use a `48x48` watermark with `32px` right/bottom margins.\n- `1K`, `2K`, and `4K` preview/full outputs use a `96x96` watermark with `64px` right/bottom margins.\n- New reported anchor type: exact `1K` / `2K` / `4K` catalog sizes also consider a secondary `96x96` candidate with `192px` right/bottom margins. Keep the canonical `64px` anchor first; the `192px` anchor must remain evidence-gated by candidate validation.\n- Known confirmed local exception: `2816x1536` uses `96x96` with `192px` right/bottom margins (`2k-new-margin` in `src/core/geminiSizeCatalog.js`).\n- The local catalog also keeps a confirmed `1408x768` 16:9 `1K` variant.\n\n#### Gemini 3.1 Flash Image Preview\n\n| Aspect ratio | 512 resolution | 0.5K tokens | 1K resolution | 1K tokens | 2K resolution | 2K tokens | 4K resolution | 4K tokens |\n|---|---|---:|---|---:|---|---:|---|---:|\n| **1:1** | 512x512 | 747 | 1024x1024 | 1120 | 2048x2048 | 1680 | 4096x4096 | 2520 |\n| **1:4** | 256x1024 | 747 | 512x2048 | 1120 | 1024x4096 | 1680 | 2048x8192 | 2520 |\n| **1:8** | 192x1536 | 747 | 384x3072 | 1120 | 768x6144 | 1680 | 1536x12288 | 2520 |\n| **2:3** | 424x632 | 747 | 848x1264 | 1120 | 1696x2528 | 1680 | 3392x5056 | 2520 |\n| **3:2** | 632x424 | 747 | 1264x848 | 1120 | 2528x1696 | 1680 | 5056x3392 | 2520 |\n| **3:4** | 448x600 | 747 | 896x1200 | 1120 | 1792x2400 | 1680 | 3584x4800 | 2520 |\n| **4:1** | 1024x256 | 747 | 2048x512 | 1120 | 4096x1024 | 1680 | 8192x2048 | 2520 |\n| **4:3** | 600x448 | 747 | 1200x896 | 1120 | 2400x1792 | 1680 | 4800x3584 | 2520 |\n| **4:5** | 464x576 | 747 | 928x1152 | 1120 | 1856x2304 | 1680 | 3712x4608 | 2520 |\n| **5:4** | 576x464 | 747 | 1152x928 | 1120 | 2304x1856 | 1680 | 4608x3712 | 2520 |\n| **8:1** | 1536x192 | 747 | 3072x384 | 1120 | 6144x768 | 1680 | 12288x1536 | 2520 |\n| **9:16** | 384x688 | 747 | 768x1376 | 1120 | 1536x2752 | 1680 | 3072x5504 | 2520 |\n| **16:9** | 688x384 | 747 | 1376x768 | 1120 | 2752x1536 | 1680 | 5504x3072 | 2520 |\n| **21:9** | 792x168 | 747 | 1584x672 | 1120 | 3168x1344 | 1680 | 6336x2688 | 2520 |\n\n#### Gemini 3 Pro Image Preview\n\n| Aspect ratio | 1K resolution | 1K tokens | 2K resolution | 2K tokens | 4K resolution | 4K tokens |\n|---|---|---:|---|---:|---|---:|\n| **1:1** | 1024x1024 | 1120 | 2048x2048 | 1120 | 4096x4096 | 2000 |\n| **2:3** | 848x1264 | 1120 | 1696x2528 | 1120 | 3392x5056 | 2000 |\n| **3:2** | 1264x848 | 1120 | 2528x1696 | 1120 | 5056x3392 | 2000 |\n| **3:4** | 896x1200 | 1120 | 1792x2400 | 1120 | 3584x4800 | 2000 |\n| **4:3** | 1200x896 | 1120 | 2400x1792 | 1120 | 4800x3584 | 2000 |\n| **4:5** | 928x1152 | 1120 | 1856x2304 | 1120 | 3712x4608 | 2000 |\n| **5:4** | 1152x928 | 1120 | 2304x1856 | 1120 | 4608x3712 | 2000 |\n| **9:16** | 768x1376 | 1120 | 1536x2752 | 1120 | 3072x5504 | 2000 |\n| **16:9** | 1376x768 | 1120 | 2752x1536 | 1120 | 5504x3072 | 2000 |\n| **21:9** | 1584x672 | 1120 | 3168x1344 | 1120 | 6336x2688 | 2000 |\n\n#### Gemini 2.5 Flash Image\n\n| Aspect ratio | Resolution | Tokens |\n|---|---|---:|\n| 1:1 | 1024x1024 | 1290 |\n| 2:3 | 832x1248 | 1290 |\n| 3:2 | 1248x832 | 1290 |\n| 3:4 | 864x1184 | 1290 |\n| 4:3 | 1184x864 | 1290 |\n| 4:5 | 896x1152 | 1290 |\n| 5:4 | 1152x896 | 1290 |\n| 9:16 | 768x1344 | 1290 |\n| 16:9 | 1344x768 | 1290 |\n| 21:9 | 1536x672 | 1290 |\n\n### Fixed Tampermonkey / Gemini Environment\n\n- Fixed Chrome profile: `.chrome-debug/tampermonkey-profile`\n- Fixed CDP port: `9226`\n- Default proxy: `http://127.0.0.1:7890`\n- Production userscript artifact: `dist/userscript/gemini-watermark-remover.user.js`\n\nPlatform notes:\n\n- macOS launcher uses `/usr/bin/open -na \"Google Chrome.app\" --args ...`\n- macOS default Chrome app lookup:\n  - `/Applications/Google Chrome.app`\n  - `~/Applications/Google Chrome.app`\n- Override Chrome location on any platform with `GWR_DEBUG_EXECUTABLE_PATH`\n\n### Open the Fixed Profile\n\n- Shell launcher: `./scripts/open-fixed-chrome-profile.sh`\n- Node launcher: `node scripts/open-tampermonkey-profile.js --cdp-port 9226`\n- CMD launcher: `.\\scripts\\open-fixed-chrome-profile.cmd`\n- PowerShell launcher: `.\\scripts\\open-fixed-chrome-profile.ps1`\n\nDefault behavior:\n\n- Reuse the fixed Chrome profile\n- Open remote debugging on port `9226`\n- Use the local proxy\n- The plain Node launcher opens the Tampermonkey Chrome Web Store page by default unless `--url` is passed\n- The shell wrapper opens the local probe page on `http://127.0.0.1:4173/tampermonkey-worker-probe.html` by default and still forwards extra args\n- The current CMD/PowerShell wrappers are fixed shortcuts to the same local probe page; they do not forward additional CLI args\n\nmacOS quick path:\n\n1. Run `pnpm build`\n2. Start the local artifact server, for example `pnpm dev`\n3. Open the fixed profile with `./scripts/open-fixed-chrome-profile.sh --url https://gemini.google.com/app`\n4. If you do not want the repo's default proxy, pass `--proxy off`\n\n### One-Time Manual Setup\n\nDo this only once in the fixed profile:\n\n1. Install Tampermonkey.\n2. Enable `Allow User Scripts` in Chrome extension details.\n3. Keep Developer Mode enabled.\n4. Install `public/tampermonkey-worker-probe.user.js` when local probe validation is needed.\n5. Install or reinstall the production userscript from the current local build server when validating the latest build.\n   - Use the active local build server URL printed by `pnpm dev`\n   - `pnpm dev` starts probing from `http://127.0.0.1:4173/` and will auto-increment if that port is occupied\n   - A previously confirmed request-layer debugging session used `http://127.0.0.1:4317/userscript/gemini-watermark-remover.user.js`, but do not assume that is still current\n\n### Local Build and Services\n\n- Production build: `pnpm build`\n- Local dist server: `pnpm dev`, `pnpm serve`, or the active local build server for this worktree\n- Default `pnpm dev` start port: `http://127.0.0.1:4173/`\n- Actual active dev server port may be higher; always trust the current `pnpm dev` console output\n- Probe smoke test: `pnpm probe:tm`\n- Installed userscript freshness check: `pnpm probe:tm:freshness`\n- Open fixed profile: `pnpm probe:tm:profile`\n\nCurrent `pnpm probe:tm` behavior:\n\n- in `run` mode it now attempts a Tampermonkey userscript freshness preflight first\n- if freshness returns `stale`, `probe:tm` must fail before running the worker/bridge smoke page\n- if the freshness preflight context itself is unavailable, for example:\n  - fixed `9226` profile is not open\n  - the CDP endpoint is unavailable\n  - the editor is mid-navigation\n  then the preflight is recorded as `skipped` and the smoke flow continues\n- this keeps stale installs fail-fast without making `probe:tm` hard-depend on a manually opened editor page\n\n### Installed Userscript Freshness Check\n\nWhen real-page behavior does not match the current worktree, verify the installed userscript body, not just the script name or `@version`.\n\n- Same `@version` does not guarantee the fixed profile is running the latest build.\n- A stale Tampermonkey script can still show:\n  - `[Gemini Watermark Remover] Initializing...`\n  - `[Gemini Watermark Remover] Ready`\n  - while silently missing newer request-layer fixes such as the download sticky intent window\n- Preferred check:\n  1. Open the Tampermonkey editor for `Gemini NanoBanana 图片水印移除`\n  2. Run `pnpm probe:tm:freshness`\n  3. Read `.artifacts/tampermonkey-freshness/latest.json`\n- If you just reinstalled the userscript and the check still reports `stale`, refresh the already-open Tampermonkey editor page once, then run `pnpm probe:tm:freshness` again.\n- Current command behavior:\n  - exits `0` when the installed userscript exactly matches the local `dist/userscript/gemini-watermark-remover.user.js`\n  - exits `1` when the installed userscript is stale or mismatched\n  - compares full normalized source hashes, not just `@version`\n  - also reports whether expected markers are missing\n- Current report path:\n  - `.artifacts/tampermonkey-freshness/latest.json`\n- Manual fallback if needed:\n  1. Compare the installed source against `dist/userscript/gemini-watermark-remover.user.js`\n  2. Confirm the installed source contains the expected newer markers before continuing real-page debugging\n     - `DEFAULT_DOWNLOAD_STICKY_WINDOW_MS`\n     - `downloadStickyUntil`\n     - `getActionContextFromIntentGate(intentGate = null, candidate = null)`\n  3. Refresh the real Gemini page after the fixed profile is updated\n\n### Real Gemini Page Validation\n\nTarget page:\n\n- `https://gemini.google.com/app`\n\nMinimum validation flow:\n\n1. Run `pnpm build`\n2. Reinstall the latest userscript in the fixed profile\n3. Open the real Gemini page\n4. Check that the console shows:\n   - `[Gemini Watermark Remover] Initializing...`\n   - `[Gemini Watermark Remover] Ready`\n5. If bridge validation is needed, trigger from page side:\n   - `gwr:userscript-process-request`\n   - Expect `gwr:userscript-process-response`\n\nCurrent confirmed request-layer behavior on the fixed profile:\n\n- `copy` can populate the strict original binding path through real `rd-gg` asset fetches and then place a processed `image/png` onto the clipboard\n- `download` stays on Gemini's native `c8o8Fe -> gg-dl -> rd-gg-dl` export flow; the userscript does not cancel the click\n- the userscript keeps explicit download intent alive for Gemini download asset URLs long enough to catch late `rd-gg-dl` requests on the native chain\n- If the original URL binding is unavailable when the required download/original request arrives, the action must fail closed with:\n  - `无法获取原图，请刷新页面后重试`\n- A successful real-page full-size download currently produces:\n  - a browser `download` event\n  - a blob-backed saved file such as `Gemini_Generated_Image_vusbaevusbaevusb.png`\n  - local detector result `skipReason=no-watermark-detected`\n\n### Real-Page Pixel Verification\n\n- Single image compare: `pnpm probe:real-page:compare`\n- All ready images on the current Gemini page: `pnpm probe:real-page:compare --all`\n- Latest batch summary:\n  - `.artifacts/real-page-pixel-compare/latest-summary.json`\n- Complex-figure validation checklist:\n  - `docs/complex-figure-verification-checklist.md`\n\nUse this when page-level screenshots are not enough and you need original blob pixel metrics for `before/after`.\n\nDo not rely on hardcoded sample counts here. Treat `.artifacts/real-page-pixel-compare/latest-summary.json` as the source of truth for the current worktree.\n\nCurrent checked local summary in this worktree:\n\n- artifact timestamp: `2026-04-06T07:25:13.120Z`\n- `total = 1`\n- the recorded ready image landed at:\n  - `afterSpatial ~= -0.2707`\n  - `afterGradient ~= 0.1075`\n\nHistorical multi-image baselines from earlier fixed-profile sessions should be treated as dated reference points, not as the current expected batch shape.\n\n### Current Preview Display Path\n\nCurrent production expectation for real Gemini preview display is:\n\n- keep request-layer preview interception enabled as an early processing source and observability point\n- do not assume Gemini's final displayed `blob:` image is fully controlled by the page `fetch` hook chain\n- keep `src/shared/pageImageReplacement.js` as the production display path for preview replacement\n- treat request-layer preview handling as supportive, but rely on page-level replacement to guarantee the user-visible preview is actually de-watermarked\n\nCurrent real-page success signal for preview display:\n\n- the displayed image reaches:\n  - `data-gwr-page-image-state=ready`\n  - `data-gwr-watermark-object-url=blob:...`\n- detector on that processed overlay blob reports:\n  - `skipReason=no-watermark-detected`\n\n### Confirmed Performance Pitfalls\n\nWhen the user reports \"this version became much slower\", check these first before touching the core algorithm:\n\n1. Page runtime / page bridge did not actually install into the real Gemini page.\n   - Symptom:\n     - Real page silently falls back to the userscript sandbox / slow main-thread path.\n     - Earlier bad runs showed `removeWatermarkMs` on the order of `11s ~ 13s` for a single preview image.\n   - Verify:\n     - Reinstall the latest userscript from the current active build server\n       - use the actual userscript URL from the active `pnpm dev` server\n       - the server starts probing from `http://127.0.0.1:4173/` and may auto-increment\n     - Refresh the real page\n     - Confirm console reaches `Initializing...` and `Ready`\n     - Confirm preview images continue to `page image process success`\n\n2. Preview queue blocked by a `blob:` image that is not renderable yet.\n   - Symptom:\n     - One image gets stuck at `state=processing`\n     - The element often has `complete=false`, `naturalWidth=0`, `naturalHeight=0`\n     - Later images stop progressing because the serial queue is effectively wedged\n   - Current fix:\n     - `src/shared/pageImageReplacement.js` now waits for renderability and retries instead of processing immediately\n   - If this regresses, inspect the waiting / retry path before changing watermark math\n\n3. Preview-anchor cleanup accidentally doing expensive work that is not adopted.\n   - Symptom:\n     - Main thread is busy, but output source does not include a successful `+subpixel`\n     - Earlier bad runs showed `subpixelRefinementMs ~= 80ms ~ 115ms` on strong preview samples with no accepted subpixel shift\n   - Current fix:\n     - preview-anchor cleanup no longer runs the expensive subpixel refinement path\n     - It relies on cheaper preview edge cleanup instead\n   - Rule:\n     - Do not re-enable preview-anchor subpixel search unless you have a real fixture that proves the accepted result is both safer and materially better\n\n### Confirmed Quality / Performance Tradeoff\n\nFor strong real-page preview samples, the current strategy is:\n\n- Skip expensive preview-anchor subpixel refinement\n- Use stronger preview edge cleanup only when:\n  - the image is a preview-anchor style match\n  - spatial residual is already low enough to be safe\n  - gradient residual is still strong enough to justify cleanup\n\nWhy this exists:\n\n- It lowers strong-sample real-page residual gradient from roughly `0.53` to roughly `0.30`\n- It keeps preview-anchor cleanup latency low by avoiding no-op subpixel sweeps\n- It accepts some spatial drift to stay within a safe residual envelope rather than overfitting and risking content damage\n\n### Confirmed Download / Copy Integration Constraint\n\nDo not re-enable the old active direct-download click hook in production.\n\nConfirmed real-page failure mode on `https://gemini.google.com/u/1/app/d3cd7d14852ecd3b?pageId=none`:\n\n- When the userscript intercepts `下载完整尺寸的图片` at capture time and calls `preventDefault()/stopImmediatePropagation()`, Gemini's own download flow is blocked before it can issue its native `c8o8Fe` / `rd-gg-dl` chain.\n- In that state, the userscript only has the earlier history bootstrap bindings from `hNvQHb`.\n- Current real `hNvQHb` bindings are mostly preview-style `gg/...=s0` URLs, not the final native download URL.\n- Falling back to those preview bindings makes the userscript attempt its own fetch path too early, which previously surfaced as:\n  - `Original image is unavailable for download processing`\n  - or `Failed to fetch image: 403`\n  - followed by the user-facing retry alert\n\nThere is a second real-page failure mode to keep in mind even after removing the active click hook:\n\n- Gemini's native full-size download chain can be much slower than the base intent window.\n- On the 2026-04-04 fixed-profile trace:\n  - `c8o8Fe` request started about `+50ms` after click\n  - `c8o8Fe` response returned about `+22.4s`\n  - final `rd-gg-dl ... image/png` arrived about `+23.9s`\n- A plain `5000ms` intent window expires far too early, so the passive request hook stops processing before the final full-size image request appears.\n\nCurrent correct production shape:\n\n- keep the intent gate for copy / download gestures\n- keep Gemini RPC discovery hooks (`hNvQHb`, `c8o8Fe`, related batchexecute responses)\n- keep generated-asset fetch interception for the native request flow\n- let Gemini continue its own click handling\n- do not block the button just to start a parallel userscript-only download path\n- keep a download-specific sticky intent window for Gemini download asset URLs\n  - current default: `30000ms`\n  - release it after terminal success/failure so it does not leak across actions\n\nCurrent confirmed real-page result with the passive native chain plus sticky download intent:\n\n- `下载完整尺寸的图片` produced a browser `download` event\n- the resulting download used a blob URL generated from the page flow\n- the saved file was `3136 x 1344`, about `5.4MB`, sha256 `4e945813779b58a5eda0f01f7973c924210477a84ae1d3826138f57b60eb691f`\n- local detector on that downloaded file reported:\n  - `skipReason = no-watermark-detected`\n  - `originalSpatialScore ~= -0.4096`\n  - `originalGradientScore ~= 0.0826`\n- no `无法获取原图，请刷新页面后重试` alert appeared\n- `复制图片` wrote an `image/png` item to the clipboard without a failure alert\n- the request-layer verification report path is session-specific; do not assume an older dated artifact still exists in the current worktree\n\n### Worker Debug Flow\n\nFor reproduction only. This is not the default production path.\n\n1. In the real page DevTools, run:\n   - `localStorage.setItem('__gwr_force_inline_worker__', '1')`\n2. Refresh `https://gemini.google.com/app/...`\n3. Inspect console logs\n\nCurrent confirmed result:\n\n- The real Gemini page can attempt to start the inline worker.\n- The worker crashes during startup because of CSP / runtime restrictions.\n- Production must stay on the main-thread path by default.\n- The force flag is for debugging only.\n\n### Worker Success / Failure Criteria\n\nDo not treat `new Worker(blobUrl)` returning without an immediate throw as proof that the worker is usable.\n\nCurrent correct criteria:\n\n- If `[Gemini Watermark Remover] Worker acceleration enabled` appears, that only means startup was attempted.\n- The worker is only considered usable if the startup handshake succeeds.\n- If `[Gemini Watermark Remover] Worker initialization failed, using main thread: ...` appears, safe fallback has happened.\n- After fallback, the page should still continue with:\n  - `page image process start`\n  - `page image process strategy`\n  - `page image process success`\n\n### Known Constraints\n\n- Direct `new Worker(blobUrl)` from Tampermonkey DOM sandbox is not reliable in the current environment.\n- The real Gemini page has CSP restrictions, so worker assumptions must not be based on probe-page success.\n- Runtime flags must be read across `unsafeWindow`; reading only the userscript sandbox `globalThis/localStorage` is insufficient.\n"},"items":[{"name":"AGENTS.md","path":"AGENTS.md","title":"AGENTS.md","content":"# AGENTS.md\n\n## Best-Effort Product Principle\n\n- Treat best-effort processing as the minimum product guarantee: when the input is available and processing can run, return the best result the current system can produce.\n- A result with visible residuals or minor imperfections is preferable to returning the unchanged input or no result.\n- Low confidence, incomplete catalog coverage, or a quality warning should not by themselves block output. Use the most plausible validated/fallback strategy, produce the result, and surface the uncertainty as non-blocking metadata or UI copy.\n- Do not tell users to refresh or retry when repeating the same action is not expected to change the outcome. Retry guidance is allowed only when there is a concrete, recoverable state change and the user can take an actionable step.\n- Reserve hard failure for cases where processing truly cannot run or no usable input/output can be produced; do not use failure as a substitute for a best-effort result.\n\n## Debug Workflow\n\n### Allenk Upstream Reference\n\n- The local fork of allenk/GeminiWatermarkTool is at `${GWR_ALLENK_ROOT}`.\n- Local-only path variables are configured in `.env`; use `.env.example` as the public template.\n- When learning or comparing upstream watermark catalog specs, alpha maps, video rules, FDnCNN behavior, or CLI behavior, prefer this local fork over temporary clones or remote README-only assumptions.\n- Treat upstream specs as candidate priors until they are verified against this repo's sample scoring, crop sheets, and output residual gates.\n\n### Data-Driven Watermark Investigation\n\n- When user-provided samples show obvious watermarks being skipped or poorly removed, treat the first task as pattern discovery, not threshold tuning.\n- Derive the watermark geometry and rendering rules from the samples before changing removal heuristics:\n  - exact anchor position (`x/y`, right/bottom margins)\n  - watermark size and aspect\n  - subpixel offset / scale drift\n  - alpha map shape and alpha strength\n  - background-dependent compositing behavior\n- Build batch reports and visual artifacts from the sample set:\n  - full image list with dimensions and scores\n  - bottom-right crop sheets\n  - candidate-position overlays when debugging detection\n  - before/after crops for every changed strategy\n- Prefer improving candidate localization and alpha estimation over loosening safety/protection gates.\n- Safety gates are a final fallback. If a visible watermark is skipped, first ask whether the selected position/size/alpha candidate is wrong or incomplete.\n- Do not generalize from a single image when the user supplied a sample set. Cluster samples by size, anchor, background, and residual behavior, then make the smallest algorithm change supported by that cluster.\n- When residual artifacts remain after mathematically valid inverse alpha removal, do not assume stronger inpaint, preview-only priors, or subpixel sweeps are safe production fixes. First verify whether the real alpha edge profile / antialiasing model differs from the current template, and keep any visual cleanup evidence-gated.\n- Do not treat a restoration score measured only with the candidate's own selected alpha map and geometry as independent cleanliness or damage evidence. A wrong candidate can erase the pattern it scores against while leaving an obvious hole, shifted star, or larger residual outside that ROI. Use those self-consistent scores for candidate ranking only; promotion evidence should come from frozen cross-profile/decoy signals and blind visual review.\n- Preserve signed source polarity when a rescue assumes a white alpha template. A localized gradient or edge witness can be strong even when the spatial template polarity is inverted; the fresh exact-48 R96 case 77 passed the decoy/edge gates but inverse removal turned it into a dark star. Keep the exact-48 R96 white source-witness gate constrained to `spatialSignedTarget > 0`; do not replace this with an absolute/max score or generalize it to other polarity models without independent validation.\n- Issue #123 independently confirmed the same collision class at exact-96 R192: a near-zero negative spatial witness with highly localized unsigned edges can be structured dark content, and applying the white `20260520` rescue creates a visible black hole. Require positive signed spatial evidence for the normal white rescue. Only retain the established structured-content exception when a drifted dark-polarity selection independently exists and the exact anchor has sufficiently strong, localized spatial evidence; a gradient/edge witness alone is not enough.\n- Treat `texture.hardReject`, `visibleDarkHole`, and selection-stage damage warnings as noisy review signals, not independent cleanliness truth. Confirmed clean exact-48 rescues can trigger them, while visible dark residuals can still retain a top-level `clean` status. Do not directly map these signals to `qualityStatus`, candidate ranking, blocking, or retry behavior without frozen independent validation.\n- Background-endpoint consistency is not by itself a safe gate for row/patch inpainting. A discovery-only endpoint threshold improved one dark-hole sample but later triggered on a clean fresh exact-48 rescue and introduced visible horizontal banding. Keep such repair offline until a preregistered temporal holdout demonstrates both useful recall and no clean-output damage.\n\n## Deployment Note\n\n- The active local/debugging build surface is the generated `dist/` directory.\n- Keep deployment assumptions aligned with the current repo contents.\n\n### Website SDK Release Sync\n\n- `https://geminiwatermarkremover.io/` is deployed from the separate local `geminiwatermarkremover.io` repository; resolve its checkout from local configuration instead of committing a machine-specific absolute path.\n- Publishing this repository to npm, GitHub Releases, or the Chrome Web Store does **not** update the website's image-processing Worker. The website pins `@pilio/gemini-watermark-remover` in both `package.json` and `pnpm-lock.yaml` and must be upgraded, rebuilt, tested, and deployed separately.\n- After every core package release, compare the website's pinned SDK version with npm `latest` before treating the website as current. A current website userscript or extension download does not prove that the website Worker uses the current SDK.\n- Verify the deployed Worker itself with a real sample and inspect its selected candidate / strategy metadata or other version-specific behavior. Do not rely only on CDN asset timestamps, package release status, userscript `@version`, or successful HTTP responses.\n- Known failure mode from issue #115: the public site still bundled SDK `1.0.31` after `1.0.33` was published. Upgrading exposed a second path mismatch: the browser engine loaded the real `36-v2` template while the sync image-data SDK interpolated size 36, allowing local validation to select `48x48` while the deployed Worker skipped. Keep runtime alpha-map catalogs and candidate orchestration aligned across SDK entry points.\n- Treat the website as a separate release surface. If its main worktree contains unrelated user changes, prepare and validate the dependency upgrade in an isolated worktree so those changes are not overwritten or accidentally deployed.\n\n### Gemini Image Size / Watermark Catalog\n\nUse this catalog when checking watermark size and anchor regressions against Gemini image outputs.\n\nCurrent code expectation:\n\n- `0.5K` preview outputs use a `48x48` watermark with `32px` right/bottom margins.\n- `1K`, `2K`, and `4K` preview/full outputs use a `96x96` watermark with `64px` right/bottom margins.\n- New reported anchor type: exact `1K` / `2K` / `4K` catalog sizes also consider a secondary `96x96` candidate with `192px` right/bottom margins. Keep the canonical `64px` anchor first; the `192px` anchor must remain evidence-gated by candidate validation.\n- Known confirmed local exception: `2816x1536` uses `96x96` with `192px` right/bottom margins (`2k-new-margin` in `src/core/geminiSizeCatalog.js`).\n- The local catalog also keeps a confirmed `1408x768` 16:9 `1K` variant.\n\n#### Gemini 3.1 Flash Image Preview\n\n| Aspect ratio | 512 resolution | 0.5K tokens | 1K resolution | 1K tokens | 2K resolution | 2K tokens | 4K resolution | 4K tokens |\n|---|---|---:|---|---:|---|---:|---|---:|\n| **1:1** | 512x512 | 747 | 1024x1024 | 1120 | 2048x2048 | 1680 | 4096x4096 | 2520 |\n| **1:4** | 256x1024 | 747 | 512x2048 | 1120 | 1024x4096 | 1680 | 2048x8192 | 2520 |\n| **1:8** | 192x1536 | 747 | 384x3072 | 1120 | 768x6144 | 1680 | 1536x12288 | 2520 |\n| **2:3** | 424x632 | 747 | 848x1264 | 1120 | 1696x2528 | 1680 | 3392x5056 | 2520 |\n| **3:2** | 632x424 | 747 | 1264x848 | 1120 | 2528x1696 | 1680 | 5056x3392 | 2520 |\n| **3:4** | 448x600 | 747 | 896x1200 | 1120 | 1792x2400 | 1680 | 3584x4800 | 2520 |\n| **4:1** | 1024x256 | 747 | 2048x512 | 1120 | 4096x1024 | 1680 | 8192x2048 | 2520 |\n| **4:3** | 600x448 | 747 | 1200x896 | 1120 | 2400x1792 | 1680 | 4800x3584 | 2520 |\n| **4:5** | 464x576 | 747 | 928x1152 | 1120 | 1856x2304 | 1680 | 3712x4608 | 2520 |\n| **5:4** | 576x464 | 747 | 1152x928 | 1120 | 2304x1856 | 1680 | 4608x3712 | 2520 |\n| **8:1** | 1536x192 | 747 | 3072x384 | 1120 | 6144x768 | 1680 | 12288x1536 | 2520 |\n| **9:16** | 384x688 | 747 | 768x1376 | 1120 | 1536x2752 | 1680 | 3072x5504 | 2520 |\n| **16:9** | 688x384 | 747 | 1376x768 | 1120 | 2752x1536 | 1680 | 5504x3072 | 2520 |\n| **21:9** | 792x168 | 747 | 1584x672 | 1120 | 3168x1344 | 1680 | 6336x2688 | 2520 |\n\n#### Gemini 3 Pro Image Preview\n\n| Aspect ratio | 1K resolution | 1K tokens | 2K resolution | 2K tokens | 4K resolution | 4K tokens |\n|---|---|---:|---|---:|---|---:|\n| **1:1** | 1024x1024 | 1120 | 2048x2048 | 1120 | 4096x4096 | 2000 |\n| **2:3** | 848x1264 | 1120 | 1696x2528 | 1120 | 3392x5056 | 2000 |\n| **3:2** | 1264x848 | 1120 | 2528x1696 | 1120 | 5056x3392 | 2000 |\n| **3:4** | 896x1200 | 1120 | 1792x2400 | 1120 | 3584x4800 | 2000 |\n| **4:3** | 1200x896 | 1120 | 2400x1792 | 1120 | 4800x3584 | 2000 |\n| **4:5** | 928x1152 | 1120 | 1856x2304 | 1120 | 3712x4608 | 2000 |\n| **5:4** | 1152x928 | 1120 | 2304x1856 | 1120 | 4608x3712 | 2000 |\n| **9:16** | 768x1376 | 1120 | 1536x2752 | 1120 | 3072x5504 | 2000 |\n| **16:9** | 1376x768 | 1120 | 2752x1536 | 1120 | 5504x3072 | 2000 |\n| **21:9** | 1584x672 | 1120 | 3168x1344 | 1120 | 6336x2688 | 2000 |\n\n#### Gemini 2.5 Flash Image\n\n| Aspect ratio | Resolution | Tokens |\n|---|---|---:|\n| 1:1 | 1024x1024 | 1290 |\n| 2:3 | 832x1248 | 1290 |\n| 3:2 | 1248x832 | 1290 |\n| 3:4 | 864x1184 | 1290 |\n| 4:3 | 1184x864 | 1290 |\n| 4:5 | 896x1152 | 1290 |\n| 5:4 | 1152x896 | 1290 |\n| 9:16 | 768x1344 | 1290 |\n| 16:9 | 1344x768 | 1290 |\n| 21:9 | 1536x672 | 1290 |\n\n### Fixed Tampermonkey / Gemini Environment\n\n- Fixed Chrome profile: `.chrome-debug/tampermonkey-profile`\n- Fixed CDP port: `9226`\n- Default proxy: `http://127.0.0.1:7890`\n- Production userscript artifact: `dist/userscript/gemini-watermark-remover.user.js`\n\nPlatform notes:\n\n- macOS launcher uses `/usr/bin/open -na \"Google Chrome.app\" --args ...`\n- macOS default Chrome app lookup:\n  - `/Applications/Google Chrome.app`\n  - `~/Applications/Google Chrome.app`\n- Override Chrome location on any platform with `GWR_DEBUG_EXECUTABLE_PATH`\n\n### Open the Fixed Profile\n\n- Shell launcher: `./scripts/open-fixed-chrome-profile.sh`\n- Node launcher: `node scripts/open-tampermonkey-profile.js --cdp-port 9226`\n- CMD launcher: `.\\scripts\\open-fixed-chrome-profile.cmd`\n- PowerShell launcher: `.\\scripts\\open-fixed-chrome-profile.ps1`\n\nDefault behavior:\n\n- Reuse the fixed Chrome profile\n- Open remote debugging on port `9226`\n- Use the local proxy\n- The plain Node launcher opens the Tampermonkey Chrome Web Store page by default unless `--url` is passed\n- The shell wrapper opens the local probe page on `http://127.0.0.1:4173/tampermonkey-worker-probe.html` by default and still forwards extra args\n- The current CMD/PowerShell wrappers are fixed shortcuts to the same local probe page; they do not forward additional CLI args\n\nmacOS quick path:\n\n1. Run `pnpm build`\n2. Start the local artifact server, for example `pnpm dev`\n3. Open the fixed profile with `./scripts/open-fixed-chrome-profile.sh --url https://gemini.google.com/app`\n4. If you do not want the repo's default proxy, pass `--proxy off`\n\n### One-Time Manual Setup\n\nDo this only once in the fixed profile:\n\n1. Install Tampermonkey.\n2. Enable `Allow User Scripts` in Chrome extension details.\n3. Keep Developer Mode enabled.\n4. Install `public/tampermonkey-worker-probe.user.js` when local probe validation is needed.\n5. Install or reinstall the production userscript from the current local build server when validating the latest build.\n   - Use the active local build server URL printed by `pnpm dev`\n   - `pnpm dev` starts probing from `http://127.0.0.1:4173/` and will auto-increment if that port is occupied\n   - A previously confirmed request-layer debugging session used `http://127.0.0.1:4317/userscript/gemini-watermark-remover.user.js`, but do not assume that is still current\n\n### Local Build and Services\n\n- Production build: `pnpm build`\n- Local dist server: `pnpm dev`, `pnpm serve`, or the active local build server for this worktree\n- Default `pnpm dev` start port: `http://127.0.0.1:4173/`\n- Actual active dev server port may be higher; always trust the current `pnpm dev` console output\n- Probe smoke test: `pnpm probe:tm`\n- Installed userscript freshness check: `pnpm probe:tm:freshness`\n- Open fixed profile: `pnpm probe:tm:profile`\n\nCurrent `pnpm probe:tm` behavior:\n\n- in `run` mode it now attempts a Tampermonkey userscript freshness preflight first\n- if freshness returns `stale`, `probe:tm` must fail before running the worker/bridge smoke page\n- if the freshness preflight context itself is unavailable, for example:\n  - fixed `9226` profile is not open\n  - the CDP endpoint is unavailable\n  - the editor is mid-navigation\n  then the preflight is recorded as `skipped` and the smoke flow continues\n- this keeps stale installs fail-fast without making `probe:tm` hard-depend on a manually opened editor page\n\n### Installed Userscript Freshness Check\n\nWhen real-page behavior does not match the current worktree, verify the installed userscript body, not just the script name or `@version`.\n\n- Same `@version` does not guarantee the fixed profile is running the latest build.\n- A stale Tampermonkey script can still show:\n  - `[Gemini Watermark Remover] Initializing...`\n  - `[Gemini Watermark Remover] Ready`\n  - while silently missing newer request-layer fixes such as the download sticky intent window\n- Preferred check:\n  1. Open the Tampermonkey editor for `Gemini NanoBanana 图片水印移除`\n  2. Run `pnpm probe:tm:freshness`\n  3. Read `.artifacts/tampermonkey-freshness/latest.json`\n- If you just reinstalled the userscript and the check still reports `stale`, refresh the already-open Tampermonkey editor page once, then run `pnpm probe:tm:freshness` again.\n- Current command behavior:\n  - exits `0` when the installed userscript exactly matches the local `dist/userscript/gemini-watermark-remover.user.js`\n  - exits `1` when the installed userscript is stale or mismatched\n  - compares full normalized source hashes, not just `@version`\n  - also reports whether expected markers are missing\n- Current report path:\n  - `.artifacts/tampermonkey-freshness/latest.json`\n- Manual fallback if needed:\n  1. Compare the installed source against `dist/userscript/gemini-watermark-remover.user.js`\n  2. Confirm the installed source contains the expected newer markers before continuing real-page debugging\n     - `DEFAULT_DOWNLOAD_STICKY_WINDOW_MS`\n     - `downloadStickyUntil`\n     - `getActionContextFromIntentGate(intentGate = null, candidate = null)`\n  3. Refresh the real Gemini page after the fixed profile is updated\n\n### Real Gemini Page Validation\n\nTarget page:\n\n- `https://gemini.google.com/app`\n\nMinimum validation flow:\n\n1. Run `pnpm build`\n2. Reinstall the latest userscript in the fixed profile\n3. Open the real Gemini page\n4. Check that the console shows:\n   - `[Gemini Watermark Remover] Initializing...`\n   - `[Gemini Watermark Remover] Ready`\n5. If bridge validation is needed, trigger from page side:\n   - `gwr:userscript-process-request`\n   - Expect `gwr:userscript-process-response`\n\nCurrent confirmed request-layer behavior on the fixed profile:\n\n- `copy` can populate the strict original binding path through real `rd-gg` asset fetches and then place a processed `image/png` onto the clipboard\n- `download` stays on Gemini's native `c8o8Fe -> gg-dl -> rd-gg-dl` export flow; the userscript does not cancel the click\n- the userscript keeps explicit download intent alive for Gemini download asset URLs long enough to catch late `rd-gg-dl` requests on the native chain\n- If the original URL binding is unavailable when the required download/original request arrives, the action must fail closed with:\n  - `无法获取原图，请刷新页面后重试`\n- A successful real-page full-size download currently produces:\n  - a browser `download` event\n  - a blob-backed saved file such as `Gemini_Generated_Image_vusbaevusbaevusb.png`\n  - local detector result `skipReason=no-watermark-detected`\n\n### Real-Page Pixel Verification\n\n- Single image compare: `pnpm probe:real-page:compare`\n- All ready images on the current Gemini page: `pnpm probe:real-page:compare --all`\n- Latest batch summary:\n  - `.artifacts/real-page-pixel-compare/latest-summary.json`\n- Complex-figure validation checklist:\n  - `docs/complex-figure-verification-checklist.md`\n\nUse this when page-level screenshots are not enough and you need original blob pixel metrics for `before/after`.\n\nDo not rely on hardcoded sample counts here. Treat `.artifacts/real-page-pixel-compare/latest-summary.json` as the source of truth for the current worktree.\n\nCurrent checked local summary in this worktree:\n\n- artifact timestamp: `2026-04-06T07:25:13.120Z`\n- `total = 1`\n- the recorded ready image landed at:\n  - `afterSpatial ~= -0.2707`\n  - `afterGradient ~= 0.1075`\n\nHistorical multi-image baselines from earlier fixed-profile sessions should be treated as dated reference points, not as the current expected batch shape.\n\n### Current Preview Display Path\n\nCurrent production expectation for real Gemini preview display is:\n\n- keep request-layer preview interception enabled as an early processing source and observability point\n- do not assume Gemini's final displayed `blob:` image is fully controlled by the page `fetch` hook chain\n- keep `src/shared/pageImageReplacement.js` as the production display path for preview replacement\n- treat request-layer preview handling as supportive, but rely on page-level replacement to guarantee the user-visible preview is actually de-watermarked\n\nCurrent real-page success signal for preview display:\n\n- the displayed image reaches:\n  - `data-gwr-page-image-state=ready`\n  - `data-gwr-watermark-object-url=blob:...`\n- detector on that processed overlay blob reports:\n  - `skipReason=no-watermark-detected`\n\n### Confirmed Performance Pitfalls\n\nWhen the user reports \"this version became much slower\", check these first before touching the core algorithm:\n\n1. Page runtime / page bridge did not actually install into the real Gemini page.\n   - Symptom:\n     - Real page silently falls back to the userscript sandbox / slow main-thread path.\n     - Earlier bad runs showed `removeWatermarkMs` on the order of `11s ~ 13s` for a single preview image.\n   - Verify:\n     - Reinstall the latest userscript from the current active build server\n       - use the actual userscript URL from the active `pnpm dev` server\n       - the server starts probing from `http://127.0.0.1:4173/` and may auto-increment\n     - Refresh the real page\n     - Confirm console reaches `Initializing...` and `Ready`\n     - Confirm preview images continue to `page image process success`\n\n2. Preview queue blocked by a `blob:` image that is not renderable yet.\n   - Symptom:\n     - One image gets stuck at `state=processing`\n     - The element often has `complete=false`, `naturalWidth=0`, `naturalHeight=0`\n     - Later images stop progressing because the serial queue is effectively wedged\n   - Current fix:\n     - `src/shared/pageImageReplacement.js` now waits for renderability and retries instead of processing immediately\n   - If this regresses, inspect the waiting / retry path before changing watermark math\n\n3. Preview-anchor cleanup accidentally doing expensive work that is not adopted.\n   - Symptom:\n     - Main thread is busy, but output source does not include a successful `+subpixel`\n     - Earlier bad runs showed `subpixelRefinementMs ~= 80ms ~ 115ms` on strong preview samples with no accepted subpixel shift\n   - Current fix:\n     - preview-anchor cleanup no longer runs the expensive subpixel refinement path\n     - It relies on cheaper preview edge cleanup instead\n   - Rule:\n     - Do not re-enable preview-anchor subpixel search unless you have a real fixture that proves the accepted result is both safer and materially better\n\n### Confirmed Quality / Performance Tradeoff\n\nFor strong real-page preview samples, the current strategy is:\n\n- Skip expensive preview-anchor subpixel refinement\n- Use stronger preview edge cleanup only when:\n  - the image is a preview-anchor style match\n  - spatial residual is already low enough to be safe\n  - gradient residual is still strong enough to justify cleanup\n\nWhy this exists:\n\n- It lowers strong-sample real-page residual gradient from roughly `0.53` to roughly `0.30`\n- It keeps preview-anchor cleanup latency low by avoiding no-op subpixel sweeps\n- It accepts some spatial drift to stay within a safe residual envelope rather than overfitting and risking content damage\n\n### Confirmed Download / Copy Integration Constraint\n\nDo not re-enable the old active direct-download click hook in production.\n\nConfirmed real-page failure mode on `https://gemini.google.com/u/1/app/d3cd7d14852ecd3b?pageId=none`:\n\n- When the userscript intercepts `下载完整尺寸的图片` at capture time and calls `preventDefault()/stopImmediatePropagation()`, Gemini's own download flow is blocked before it can issue its native `c8o8Fe` / `rd-gg-dl` chain.\n- In that state, the userscript only has the earlier history bootstrap bindings from `hNvQHb`.\n- Current real `hNvQHb` bindings are mostly preview-style `gg/...=s0` URLs, not the final native download URL.\n- Falling back to those preview bindings makes the userscript attempt its own fetch path too early, which previously surfaced as:\n  - `Original image is unavailable for download processing`\n  - or `Failed to fetch image: 403`\n  - followed by the user-facing retry alert\n\nThere is a second real-page failure mode to keep in mind even after removing the active click hook:\n\n- Gemini's native full-size download chain can be much slower than the base intent window.\n- On the 2026-04-04 fixed-profile trace:\n  - `c8o8Fe` request started about `+50ms` after click\n  - `c8o8Fe` response returned about `+22.4s`\n  - final `rd-gg-dl ... image/png` arrived about `+23.9s`\n- A plain `5000ms` intent window expires far too early, so the passive request hook stops processing before the final full-size image request appears.\n\nCurrent correct production shape:\n\n- keep the intent gate for copy / download gestures\n- keep Gemini RPC discovery hooks (`hNvQHb`, `c8o8Fe`, related batchexecute responses)\n- keep generated-asset fetch interception for the native request flow\n- let Gemini continue its own click handling\n- do not block the button just to start a parallel userscript-only download path\n- keep a download-specific sticky intent window for Gemini download asset URLs\n  - current default: `30000ms`\n  - release it after terminal success/failure so it does not leak across actions\n\nCurrent confirmed real-page result with the passive native chain plus sticky download intent:\n\n- `下载完整尺寸的图片` produced a browser `download` event\n- the resulting download used a blob URL generated from the page flow\n- the saved file was `3136 x 1344`, about `5.4MB`, sha256 `4e945813779b58a5eda0f01f7973c924210477a84ae1d3826138f57b60eb691f`\n- local detector on that downloaded file reported:\n  - `skipReason = no-watermark-detected`\n  - `originalSpatialScore ~= -0.4096`\n  - `originalGradientScore ~= 0.0826`\n- no `无法获取原图，请刷新页面后重试` alert appeared\n- `复制图片` wrote an `image/png` item to the clipboard without a failure alert\n- the request-layer verification report path is session-specific; do not assume an older dated artifact still exists in the current worktree\n\n### Worker Debug Flow\n\nFor reproduction only. This is not the default production path.\n\n1. In the real page DevTools, run:\n   - `localStorage.setItem('__gwr_force_inline_worker__', '1')`\n2. Refresh `https://gemini.google.com/app/...`\n3. Inspect console logs\n\nCurrent confirmed result:\n\n- The real Gemini page can attempt to start the inline worker.\n- The worker crashes during startup because of CSP / runtime restrictions.\n- Production must stay on the main-thread path by default.\n- The force flag is for debugging only.\n\n### Worker Success / Failure Criteria\n\nDo not treat `new Worker(blobUrl)` returning without an immediate throw as proof that the worker is usable.\n\nCurrent correct criteria:\n\n- If `[Gemini Watermark Remover] Worker acceleration enabled` appears, that only means startup was attempted.\n- The worker is only considered usable if the startup handshake succeeds.\n- If `[Gemini Watermark Remover] Worker initialization failed, using main thread: ...` appears, safe fallback has happened.\n- After fallback, the page should still continue with:\n  - `page image process start`\n  - `page image process strategy`\n  - `page image process success`\n\n### Known Constraints\n\n- Direct `new Worker(blobUrl)` from Tampermonkey DOM sandbox is not reliable in the current environment.\n- The real Gemini page has CSP restrictions, so worker assumptions must not be based on probe-page success.\n- Runtime flags must be read across `unsafeWindow`; reading only the userscript sandbox `globalThis/localStorage` is insufficient.\n","category":"root","tokens":6506}]}