fix(export): inline local images on Windows - #363
Merged
PathGao merged 1 commit intoAug 2, 2026
Conversation
`convertFileSrc` does not return the same shape on every platform: Windows (and Android) get `http://asset.localhost/<encoded path>`, everywhere else gets `asset://localhost/<encoded path>`. The app's own CSP lists both forms, but the export only recognised the second. Worse, the remote-scheme bail-out ran first. Because the Windows asset URL *is* an `http:` URL, it was discarded before it could be recognised — so the image was neither inlined nor counted in `missingImages`. A Windows export silently shipped `http://asset.localhost/...` links that are dead the moment the file leaves the app, with no warning toast. Recognise both shapes and check for them before the remote bail-out. The host match is anchored so `asset.localhost.evil.test` and `asset.localhostx` stay remote. The existing tests only covered `asset://localhost/C:/…`, a shape `convertFileSrc` never actually produces; the new ones use the real encoded output for both platforms. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This was referenced Aug 2, 2026
Merged
PathGao
pushed a commit
that referenced
this pull request
Aug 2, 2026
`[data](./data.csv)` was handed to `openUrl(anchor.href)`, and `anchor.href` is what the DOM resolved against the webview origin, not a path on disk. The two platforms then failed differently: on macOS and Linux the origin is `tauri://localhost`, which the opener scope refuses, so the click did nothing and left an uncaught promise rejection; on Windows it is `http://tauri.localhost`, which matches `http://*`, so the browser really opened onto a dead link. `resolveLocalFileLinkPath` reuses `resolveExportImagePath` from #363 for the whole scheme, drive-letter, UNC and query-suffix decision table, and adds two rules a link needs that an image does not: `//host/path` is a web address, matching what `getMarkdownLinkTarget` already assumes, and a relative link in an unsaved buffer resolves to nothing rather than to something relative to the process working directory. Both OS calls are now inside try/catch with a toast, which removes the uncaught rejection and makes the macOS failure visible. The file still will not open until the opener path scope is decided (#399, #403); everything above is an improvement regardless. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
PathGao
pushed a commit
that referenced
this pull request
Aug 2, 2026
`[data](./data.csv)` was handed to `openUrl(anchor.href)`, and `anchor.href` is what the DOM resolved against the webview origin, not a path on disk. The two platforms then failed differently: on macOS and Linux the origin is `tauri://localhost`, which the opener scope refuses, so the click did nothing and left an uncaught promise rejection; on Windows it is `http://tauri.localhost`, which matches `http://*`, so the browser really opened onto a dead link. `resolveLocalFileLinkPath` reuses `resolveExportImagePath` from #363 for the whole scheme, drive-letter, UNC and query-suffix decision table, and adds two rules a link needs that an image does not: `//host/path` is a web address, matching what `getMarkdownLinkTarget` already assumes, and a relative link in an unsaved buffer resolves to nothing rather than to something relative to the process working directory. Both OS calls are now inside try/catch with a toast, which removes the uncaught rejection and makes the macOS failure visible. The file still will not open until the opener path scope is decided (#399, #403); everything above is an improvement regardless. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
PathGao
pushed a commit
that referenced
this pull request
Aug 2, 2026
`[data](./data.csv)` was handed to `openUrl(anchor.href)`, and `anchor.href` is what the DOM resolved against the webview origin, not a path on disk. The two platforms then failed differently: on macOS and Linux the origin is `tauri://localhost`, which the opener scope refuses, so the click did nothing and left an uncaught promise rejection; on Windows it is `http://tauri.localhost`, which matches `http://*`, so the browser really opened onto a dead link. `resolveLocalFileLinkPath` reuses `resolveExportImagePath` from #363 for the whole scheme, drive-letter, UNC and query-suffix decision table, and adds two rules a link needs that an image does not: `//host/path` is a web address, matching what `getMarkdownLinkTarget` already assumes, and a relative link in an unsaved buffer resolves to nothing rather than to something relative to the process working directory. Both OS calls are now inside try/catch with a toast, which removes the uncaught rejection and makes the macOS failure visible. The file still will not open until the opener path scope is decided (#399, #403); everything above is an improvement regardless. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
PathGao
added a commit
that referenced
this pull request
Aug 3, 2026
…nestly for remote ones (#408) The remote branch fetched the image bytes with `fetch(src)`, which `connect-src 'self'` refuses - so it was unreachable on every platform, not merely failing. Windows fell into that same unreachable branch for *local* images too, because the local test was `src.startsWith('asset:')` while Windows asset URLs are `http://asset.localhost/...`. Reuses `normalizeAssetPath` from #363 rather than another hand-rolled prefix test. It handles both URL shapes and rejects `http://asset.localhost.evil.test/...`, which a `startsWith` check accepts and would have handed to `copy_file`. The dead fetch path is deleted. The remote case now bails before opening a save dialog it cannot honour, and says why, instead of leaving code that reads like a feature. Supporting it needs a download command in Rust. Co-authored-by: PathGao <gaoyanbo@gaoyanbodeMacBook-Air.local> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
PathGao
added a commit
that referenced
this pull request
Aug 3, 2026
…ent (#409) `[data](./data.csv)` was handed to `openUrl(anchor.href)`, and `anchor.href` is what the DOM resolved against the webview origin, not a path on disk. The two platforms then failed differently: on macOS and Linux the origin is `tauri://localhost`, which the opener scope refuses, so the click did nothing and left an uncaught promise rejection; on Windows it is `http://tauri.localhost`, which matches `http://*`, so the browser really opened onto a dead link. `resolveLocalFileLinkPath` reuses `resolveExportImagePath` from #363 for the whole scheme, drive-letter, UNC and query-suffix decision table, and adds two rules a link needs that an image does not: `//host/path` is a web address, matching what `getMarkdownLinkTarget` already assumes, and a relative link in an unsaved buffer resolves to nothing rather than to something relative to the process working directory. Both OS calls are now inside try/catch with a toast, which removes the uncaught rejection and makes the macOS failure visible. The file still will not open until the opener path scope is decided (#399, #403); everything above is an improvement regardless. Co-authored-by: PathGao <gaoyanbo@gaoyanbodeMacBook-Air.local> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This was referenced Aug 3, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
convertFileSrcdoes not return the same shape on every platform: Windows (and Android) gethttp://asset.localhost/<encoded path>, everywhere else getsasset://localhost/<encoded path>. The app's own CSP lists both forms, butnormalizeAssetPathonly recognised the second.Worse, the remote-scheme bail-out ran first. Because the Windows asset URL is an
http:URL, it was discarded before it could be recognised — so the image was neither inlined nor counted inmissingImages. A Windows export silently shippedhttp://asset.localhost/...references that are dead the moment the file leaves the app, and the "some images could not be inlined" toast never fired.This recognises both shapes and checks for them before the remote bail-out. The host match is anchored, so
asset.localhost.evil.testandasset.localhostxstay remote.Reviewer note
The existing tests passed only because they used
asset://localhost/C:/…— a shapeconvertFileSrcnever actually produces (it percent-encodes the path). The new cases use the real encoded output for both platforms, plus look-alike hosts as negatives.Validation
npm run check— 0 errors, 0 warningsnpm test— 156/156