Skip to content

Fix(assets): attached files land in assets/, and uploads are no longer corrupted - #41

Merged
adibhanna merged 2 commits into
mainfrom
fix/attached-files-assets-and-upload-encoding
Aug 31, 2026
Merged

Fix(assets): attached files land in assets/, and uploads are no longer corrupted#41
adibhanna merged 2 commits into
mainfrom
fix/attached-files-assets-and-upload-encoding

Conversation

@adibhanna

@adibhanna adibhanna commented Aug 29, 2026

Copy link
Copy Markdown
Contributor

Two bugs in the same Add image / attach button, one per vault kind. Closes #40.

Local vault: the file went to the vault root

importDroppedFile asked for a unique name in '', the vault root, wrote the file there, and hand-built the link as '../'.repeat(depth) + filename, so a note in a folder got ![name](<../name.jpg>).

Paste already wrote to assets/, and so did the cloud vault, which is why it looked like paste and attach were different features. Reported on Discord by xenin, whose shape_image 18.jpg was the seventeenth name collision in their vault root.

Desktop had this exact bug and fixed it in ZenNotes#377; the port here was missed.

Now: assets/, linked by vault-relative path, an image as ![[assets/pic.jpg]], matching paste and the cloud vault.

Self-hosted vault: every upload arrived corrupt (#40)

The upload hand-built its multipart body as one string and marked the file part Content-Transfer-Encoding: base64. That header is a MIME construct: RFC 7578 dropped it from multipart/form-data, and Go's mime/multipart ignores it. The server wrote the base64 text to disk as the file's bytes, so the attachment landed in assets/ with the right name, about a third larger than the original, and unreadable by anything, which matches the report exactly, including that it opened as nothing straight from the Assets folder.

The bytes must stay base64 to cross the native bridge, since a CapacitorHttp string body is written out as UTF-8 and mangles anything above 0x7F. dataType: 'formData' is the supported route: both native layers decode a base64File entry and write the raw bytes into the part (Android CapacitorHttpUrlConnection.writeFormDataRequestBody, iOS CapacitorUrlRequest.getRequestDataFromFormData). The sibling direct-object upload already used the same mechanism with dataType: 'file'.

Proven against the real Go server

Both body shapes posted to a running server with the same PNG:

body stored begins matches original
old 638,352 bytes iVBORw0K (base64 text) no
new 478,762 bytes \x89PNG yes, identical sha256

The old file is exactly 4/3 the original, the signature of base64 inflation.

Verification

Files already sitting in a vault root do not move themselves, and attachments already uploaded corrupt stay corrupt and need re-adding.

…r corrupted

Two bugs in the same button, one per vault kind.

On a LOCAL vault, attaching a file wrote it to the vault ROOT, next to
the notes, and linked it with a hand-built `'../'.repeat(depth)` path, so
a note in a folder got `![name](<../name.jpg>)`. Paste already wrote to
`assets/`, and so did the cloud vault, which is why it looked like paste
and attach were different features (Discord, reported by xenin, whose
`shape_image 18.jpg` was the seventeenth collision in their vault root).
Desktop had the same bug and fixed it in ZenNotes#377; the port here was
missed. Attach now writes to `assets/` and links by vault-relative path,
an image as `![[assets/pic.jpg]]`, matching paste and the cloud vault.

On a SELF-HOSTED vault, every uploaded attachment arrived corrupt (#40).
The upload hand-built its multipart body as one string and marked the
file part `Content-Transfer-Encoding: base64`. That header is a MIME
construct: RFC 7578 dropped it from multipart/form-data and Go's
mime/multipart ignores it, so the server wrote the base64 TEXT to disk as
the file's bytes. The file arrived in `assets/` with the right name, a
third larger than the original, and unreadable by anything.

The bytes must stay base64 to cross the native bridge at all, since a
CapacitorHttp string body is written out as UTF-8 and mangles anything
above 0x7F. `dataType: 'formData'` is the supported way through: both
native layers decode a `base64File` entry and write the raw bytes into
the part. The sibling direct-object upload already used the same
mechanism with `dataType: 'file'`.

Verified against the real Go server by posting both body shapes: the old
one stored 638352 bytes beginning `iVBORw0K`, the base64 text of the PNG,
and the new one stored 478762 bytes with the same sha256 as the original.

The pure rules move into `imported-assets.ts` so they can be unit-tested;
`vault-core` cannot be loaded by `node --test` because it reaches
`@shared/*` through a Vite alias, and it re-exports them so every existing
importer is untouched.

Claude-Session: https://claude.ai/code/session_01AYTRixg5TJmxn2j6FCqfUD
@adibhanna
adibhanna merged commit 982c228 into main Aug 31, 2026
2 checks passed
@adibhanna
adibhanna deleted the fix/attached-files-assets-and-upload-encoding branch August 31, 2026 13:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

New "add image to note" feature in Android, not working for me.

1 participant