Skip to content

feat(ui): an embed reads as its link, and an image resource shows the image - #223

Merged
samkeen merged 1 commit into
mainfrom
feat/image-resource-viewer
Aug 27, 2026
Merged

feat(ui): an embed reads as its link, and an image resource shows the image#223
samkeen merged 1 commit into
mainfrom
feat/image-resource-viewer

Conversation

@samkeen

@samkeen samkeen commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Two halves of the same complaint: a note that embeds a picture rendered the grammar instead of the picture, and selecting the picture itself offered only an OS handoff.

![[file]] reads as its link

The reading view's wikilink rule now spans the embed marker rather than starting after it, so the ! is consumed as grammar instead of surviving beside the anchor as prose.

The |-part changes meaning with the marker — a label on a plain wikilink, a display size on an embed (![[shot.png|400]]) — and B2 does not size images, so an embed labels itself with its target and drops the hint.

Authored Before After
![[__Attachments/Shot.png]] ! + link link
![[__Attachments/Shot.png|400]] ! + link labelled 400 link labelled with the filename
[[concepts/memory|how it works]] unchanged unchanged

The sized case is the sharper one: the unsupported half was eating the supported one.

An image resource shows the image

Vault::read_resource_bytes is read's non-note sibling, inventory-checked before the filesystem is touched — the posture Open in system default already took — so a link a note authored cannot turn it into "read any file this process can reach". The host encodes base64 for the JSON IPC (import_file's encoding, the other direction) and the card turns it into the data: URL the CSP already admits (img-src 'self' data:, unchanged).

The picture replaces the No viewer available line, not the card: metadata, backlinks and the OS handoff stay put. They are the whole card again in three cases:

  • a class with no viewer (pdf, binary, …) — unchanged behaviour
  • an image past IMAGE_VIEWER_MAX_BYTES (25 MB) — a memory bound, since the data: URL is held for as long as the card is open
  • a read that failed — deliberate: the card is the truth about the file whether or not its bytes can be read, so a failed read must not fail the navigation and strand the pane on the previous document

A transparent PNG sits on a checkerboard, so it is legible in either theme without the card guessing a background colour for it.

Tests

Each was written first and failed before its fix.

  • ui/src/embedlink.test.ts (15 checks) goes through renderMarkdown rather than the extension object, because the bug was the start hook's anchoring — the extension in isolation would still have passed. Covers the neighbours the grammar must not swallow: Markdown's own ![alt](path), a ! that isn't a marker, an unclosed [[.
  • ui/src/resourceview.test.ts (16 checks) pins the card's three states and the extension → MIME rule.
  • crates/b2-core/tests/resources.rs proves the inventory check rather than assuming it: it asks for a note, and for an uninventoried file that is genuinely on disk beside an inventoried one. A bare fs::read would pass both.

make ci green.

Known gap, deliberately left

In the editor, ![[file]] is claimed by CodeMirror's own image rule before B2's wikilink parser sees the [, so an embed stays raw text there — byte-honest, but not yet mod-clickable. Matching the editor to the reading view means moving the Lezer node's start over the marker, which touches droplink, wikicomplete and the decoration engine; out of scope here.

Also unchanged: the backlinks panel still shows "400" as an embed's caption, because the engine records the authored alias verbatim.

🤖 Generated with Claude Code

… image

Two halves of the same complaint: a note that embeds a picture rendered the
grammar instead of the picture, and selecting the picture itself offered only
an OS handoff.

**`![[file]]` reads as its link.** The reading view's wikilink rule now spans
the embed marker rather than starting after it, so the `!` is consumed as
grammar instead of surviving beside the anchor as prose. The `|`-part changes
meaning with the marker — a label on a plain wikilink, a display *size* on an
embed (`![[shot.png|400]]`) — and B2 does not size images, so an embed labels
itself with its target and drops the hint. Before, `![[shot.png|400]]` rendered
as `!400`: the unsupported half was eating the supported one.

**An image resource shows the image.** `Vault::read_resource_bytes` is
`read`'s non-note sibling, inventory-checked before the filesystem is touched
(the posture *Open in system default* already took), so a link a note authored
cannot turn it into "read any file this process can reach". The host encodes
base64 for the JSON IPC — `import_file`'s encoding, the other direction — and
the card turns it into the `data:` URL the CSP already admits.

The picture replaces the *No viewer available* line, not the card: metadata,
backlinks and the OS handoff stay, and they are the whole card again for a
class with no viewer, for an image past `IMAGE_VIEWER_MAX_BYTES` (a memory
bound — the URL is held while the card is open), and for a read that failed.
That last one is deliberate: the card is the truth about the file whether or
not its bytes can be read, so a failed read must not fail the navigation.

Tests first, each failing before its fix: `embedlink.test.ts` goes through
`renderMarkdown` rather than the extension object, because the bug was the
`start` hook's anchoring and the extension alone would still have passed;
`resourceview.test.ts` pins the card's three states and the `data:` rule; the
core test proves the inventory check by asking for a note and for an
uninventoried file that is really on disk.

Known gap, deliberately left: in the *editor*, `![[file]]` is claimed by
CodeMirror's own image rule before B2's wikilink parser sees the `[`, so an
embed stays raw text there — byte-honest, but not yet mod-clickable.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@kody-ai

kody-ai Bot commented Aug 27, 2026

Copy link
Copy Markdown

You've used all your free Kodus-paid PR reviews 🎁

Your trial is still active — this just means the PR reviews we cover during the trial are used up.

Connect your own AI key to keep Kody reviewing — unlimited reviews, on any plan (Free included).

Want more trial reviews to finish evaluating before adding a key? Talk to our founders. 😎

@coderabbitai

coderabbitai Bot commented Aug 27, 2026

Copy link
Copy Markdown

Warning

Review limit reached

Next included review available in 47 minutes.

View limit details

Limit details: You’ve used the included review currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: dfe9970e-00eb-411a-9671-eb1799a0d7d0

📥 Commits

Reviewing files that changed from the base of the PR and between f102bdf and ca8ea4f.

📒 Files selected for processing (12)
  • CLAUDE.md
  • crates/b2-core/src/vault.rs
  • crates/b2-core/tests/resources.rs
  • crates/b2-desktop/src/commands.rs
  • crates/b2-desktop/src/main.rs
  • ui/src/api.ts
  • ui/src/embedlink.test.ts
  • ui/src/main.ts
  • ui/src/render.ts
  • ui/src/resourceview.test.ts
  • ui/src/state.ts
  • ui/style.css

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@samkeen
samkeen merged commit c438992 into main Aug 27, 2026
3 checks passed
@samkeen
samkeen deleted the feat/image-resource-viewer branch August 27, 2026 15:35
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.

1 participant