Skip to content

ENG-2217 Rewrite asset links in Obsidian-origin markdown for Roam - #1398

Open
maparent wants to merge 1 commit into
eng-2218-Copy-assets-from-supabase-into-Roam-storagefrom
eng-2217-rewrite-asset-links-in-obsidian-origin-markdown-for-roam
Open

ENG-2217 Rewrite asset links in Obsidian-origin markdown for Roam#1398
maparent wants to merge 1 commit into
eng-2218-Copy-assets-from-supabase-into-Roam-storagefrom
eng-2217-rewrite-asset-links-in-obsidian-origin-markdown-for-roam

Conversation

@maparent

@maparent maparent commented Sep 5, 2026

Copy link
Copy Markdown
Collaborator

Reviewer brief

Given a markdown and assets, rewrite the links to use the firebase links.
We have to look up the file type to make the right kind of Roam link; since Obsidian does not have distinct link shapes, we have to infer from mimetype or file extension.

Verification

Unit tests. Real tests in the next PR.

Loom video

https://www.loom.com/share/ac85dc0a4df9411c84858e965dadb86f

Note: I did push a refactor from 2219 to 2217 after the loom, but no behaviour change.

Scope check

  • Ran $scope-check against ENG-2217 and the final diff.
  • Scope beyond Done When: Adds mime-db (+ @types/mime-db) as an apps/roam dependency.
  • Required now: Done When requires correct Roam syntax per media type, and the type set is not enumerable by hand: the extension table is derived from all ~1000 mime-db types (223 extensions) so an imported asset renders as the same file would if uploaded natively. Cost: db.json is not tree-shakeable, so ~204 KB lands in extension.js (23KB compressed); a build-time codegen of the table would remove it later.
  • Anyone affected or consulted: No.
  • Decision: Rationale recorded in the EXTENSION_KINDS docstring; no Linear decision.
    • I thought that maintaining mimetype information ourselves was inviting drift, and wanted to rely on a library.
    • Obsidian relies on mime-types already, so there is symmetry. mime-types imports path, which would not work in a browser build.

Local delegated full review

  • Ran a comprehensive review of the entire final diff in a subagent with a fresh context. Use $dg-delegated-full-review when no other full-review workflow is available.

https://linear.app/discourse-graphs/issue/ENG-2217/rewrite-asset-links-in-obsidian-origin-markdown-for-roam

@linear-code

linear-code Bot commented Sep 5, 2026

Copy link
Copy Markdown

ENG-2217

@vercel

vercel Bot commented Sep 5, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
discourse-graph Ready Ready Preview Sep 10, 2026 1:01am UTC

Request Review

@supabase

supabase Bot commented Sep 5, 2026

Copy link
Copy Markdown

This pull request has been ignored for the connected project zytfjzqyijgagqxrzbmz because there are no changes detected in packages/database/supabase directory. You can change this behaviour in Project Integrations Settings ↗︎.


Preview Branches by Supabase.
Learn more about Supabase Branching ↗︎.

@maparent
maparent force-pushed the eng-2217-rewrite-asset-links-in-obsidian-origin-markdown-for-roam branch from 1b550fd to 4ce0832 Compare September 6, 2026 14:38
@maparent
maparent force-pushed the eng-2217-rewrite-asset-links-in-obsidian-origin-markdown-for-roam branch from 4ce0832 to 2950a2a Compare September 6, 2026 15:00
@maparent
maparent force-pushed the eng-2217-rewrite-asset-links-in-obsidian-origin-markdown-for-roam branch from 2950a2a to 28e9eaa Compare September 6, 2026 16:01
@maparent
maparent force-pushed the eng-2217-rewrite-asset-links-in-obsidian-origin-markdown-for-roam branch from 28e9eaa to 7bb3b3d Compare September 6, 2026 16:37
@maparent
maparent force-pushed the eng-2217-rewrite-asset-links-in-obsidian-origin-markdown-for-roam branch from 7bb3b3d to 1179bdb Compare September 6, 2026 17:05
@maparent
maparent force-pushed the eng-2217-rewrite-asset-links-in-obsidian-origin-markdown-for-roam branch from 1179bdb to e0588dd Compare September 6, 2026 18:09
@maparent
maparent marked this pull request as ready for review September 6, 2026 20:09
@graphite-app

graphite-app Bot commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

PR size/scope check

This PR is over our review-size guideline.

  • Recommended: ~200 lines changed
  • Acceptable limit: up to 400 lines when well-scoped/self-contained
  • Preferred file count: fewer than 5 files

Please split this into smaller PRs unless there is a clear reason the changes need to land together.

If keeping it as one PR, please add a brief justification covering:

  • What single problem this PR solves
  • Why the files/changes are coupled

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 4 potential issues.

2 flags not posted on this PR by your GitHub settings — view them in Devin Review. (Configure)

Devin Review

Comment on lines +260 to +266
export const rewriteAssetLinks = ({
markdown,
assets,
}: {
markdown: string;
assets: ResolvedAsset[];
}): string => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Asset rewriting never reaches imports

Imports never call rewriteAssetLinks. materializeSharedNode sends unchanged markdown to Roam, so imported vault assets remain broken.

Prompt for agents
Integrate asset materialization into apps/roam/src/utils/materializeSharedNode.ts before createImportedPage or updateImportedPage receives markdown. Fetch the source node's FileReference data, mirror each stored hash through mirrorAssetToRoamStorage sequentially as that utility requires, build ResolvedAsset entries from successful mirrors, and pass the fetched markdown through rewriteAssetLinks. Preserve unresolved tokens when a reference cannot be fetched or mirrored, and cover both initial imports and refreshes with integration tests.
Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +270 to +274
return markdown.replace(
LINK_PATTERN,
// One capture group per branch, in the order the pattern lists them. The trailing
// offset and input arguments the replacer also receives are simply not destructured.
(match: string, ...groups: (string | undefined)[]) => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Code examples become live assets

When markdown demonstrates a recorded asset inside code, replace rewrites that example too. The imported page no longer preserves its source text.

Prompt for agents
Make rewriteAssetLinks syntax-aware so replacements occur only in markdown link and embed nodes, not inside inline code, fenced code blocks, or other literal markdown regions. A markdown parser is preferable to expanding the global regular expression. Add cases where the same sourceRef appears once as an actual embed and once inside inline and fenced code.
Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

// and it is the only statement available for a storage uid with no extension.
String.raw`\{\{\[\[(pdf|audio|video)\]\]:\s*(${URL_TOKEN})\s*\}\}`, // {{[[pdf]]: url}}
String.raw`\{\{(pdf|audio|video):\s*(${URL_TOKEN})\s*\}\}`, // {{pdf: url}}
String.raw`!\[\[([^\]|]+)(?:\|[^\]]*)?\]\]`, // ![[token]] or ![[token|alt]]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Wikilink aliases disappear

For ![[file|label]], LINK_PATTERN discards label. Non-media attachments then display a filename instead of the author's label.

Prompt for agents
Capture the optional alias in both wikilink branches, update the replacement capture-group mapping, and pass that alias as linkText. Preserve the current sourcePath fallback only when the alias is absent or empty. Add tests for aliased non-media attachments and image alt text.
Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +211 to +216
String.raw`!\[([^\]]*)\]\(([^)\s]+)(?:\s+"[^"]*")?\)`, // ![alt](token)
// No `[` in the label, so `[![alt](image)](link)` cannot match here from the outer
// bracket: the branch fails, the scan advances one character, and the image branch
// takes the inner embed as it should. Alternation is tried per position, so ordering
// the image branch first is not enough on its own.
String.raw`\[([^\]\[]*)\]\(([^)\s]+)(?:\s+"[^"]*")?\)`, // [label](token)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Spaced Markdown destinations remain broken

Valid links like ![](<my folder/image.png>) never match LINK_PATTERN. Their recorded assets remain as unusable vault paths after import.

Prompt for agents
Extend standard Markdown destination parsing to support angle-bracket destinations, including spaces, while retaining support for optional titles and nested-image behavior. Normalize the captured destination by removing only its angle delimiters before asset lookup, and add image and ordinary-link tests using sourceRef values with spaces.
Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

@maparent
maparent force-pushed the eng-2217-rewrite-asset-links-in-obsidian-origin-markdown-for-roam branch from e0588dd to 0f5a93e Compare September 6, 2026 22:32
@maparent
maparent force-pushed the eng-2217-rewrite-asset-links-in-obsidian-origin-markdown-for-roam branch from 0f5a93e to c1d603a Compare September 6, 2026 22:41
@maparent
maparent force-pushed the eng-2217-rewrite-asset-links-in-obsidian-origin-markdown-for-roam branch from c20286f to ef15f98 Compare September 8, 2026 22:07
@maparent
maparent force-pushed the eng-2217-rewrite-asset-links-in-obsidian-origin-markdown-for-roam branch from ef15f98 to 349c0e0 Compare September 9, 2026 13:26
@maparent
maparent force-pushed the eng-2217-rewrite-asset-links-in-obsidian-origin-markdown-for-roam branch from 349c0e0 to 0f71a67 Compare September 9, 2026 15:58
@maparent
maparent force-pushed the eng-2217-rewrite-asset-links-in-obsidian-origin-markdown-for-roam branch from 0f71a67 to f225e53 Compare September 9, 2026 16:10
@maparent
maparent force-pushed the eng-2217-rewrite-asset-links-in-obsidian-origin-markdown-for-roam branch from f225e53 to d77dd2c Compare September 9, 2026 16:29
@maparent
maparent force-pushed the eng-2217-rewrite-asset-links-in-obsidian-origin-markdown-for-roam branch from d77dd2c to 0f834a4 Compare September 9, 2026 18:24
@maparent
maparent force-pushed the eng-2217-rewrite-asset-links-in-obsidian-origin-markdown-for-roam branch from 0f834a4 to e3c12d5 Compare September 9, 2026 18:37
@maparent
maparent force-pushed the eng-2217-rewrite-asset-links-in-obsidian-origin-markdown-for-roam branch from e3c12d5 to a37d0c0 Compare September 9, 2026 18:43
@maparent
maparent force-pushed the eng-2217-rewrite-asset-links-in-obsidian-origin-markdown-for-roam branch from a37d0c0 to b94dd69 Compare September 9, 2026 18:51
@maparent
maparent force-pushed the eng-2217-rewrite-asset-links-in-obsidian-origin-markdown-for-roam branch from b94dd69 to f2ce51a Compare September 9, 2026 19:13
};

// A wikilink embed carries no separate text, so its label comes from the asset.
const linkText = imageRef ? (imageAlt ?? "") : (linkLabel ?? "");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wikilink embeds with alt text (e.g., ![[image.png|alt text]]) will lose their alt text. The regex pattern on line 241 captures only the token in a capturing group while the alt text portion (?:\|[^\]]*)? is non-capturing. When embedRef is matched, neither imageRef nor linkLabel is defined, so linkText becomes "", discarding any alt text that was present after the | in the wikilink syntax.

// Current - loses alt text:
const linkText = imageRef ? (imageAlt ?? "") : (linkLabel ?? "");

// Should capture wikilink alt text:
// Modify regex on line 241 to:
!\[\[([^\]|]+)(?:\|([^\]]*))?\]\]
// Then handle it:
const linkText = imageRef ? (imageAlt ?? "") : (embedAlt ?? linkLabel ?? "");

Spotted by Graphite

Fix in Graphite


Is this helpful? React 👍 or 👎 to let us know.

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