Based on the recorded hands-on evidence linked below.
Two notes. The same title.
Reserve the filenames.
Then connect the notes.
Work/Research/Roadmap_ Q4.mdWork/Research/Roadmap_ Q4-1.md- 01Assign paths
- 02Copy resources
- 03Rewrite links
- 04Write notes
[Next](:/bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb)[Next](../../Work/Research/Roadmap_%20Q4-1.md)The first note can link to the second before its file exists. Shipped Joplin modules produced this output with synthetic notes and controlled adapters.
What happens to a link when a note leaves Joplin?
Inside Joplin, a note link can point to an internal ID such as :/bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb. A folder of exported Markdown needs an ordinary file path instead. Attachments also need to leave the application's resource storage and become files beside the export.
We investigated the installed Joplin Desktop 3.7.21 package with REA. The question was specific: how does Markdown export keep the right destination when two notes have the same title, or the source note is inside nested notebooks?
Three findings explain the mechanism: Joplin allocates filenames before writing notes, keeps separate maps for note and resource IDs, and processes resources before note bodies. The diagram above shows output from replaying the shipped modules with synthetic data. It is not a captured export through the desktop UI.
Follow the export command into the service
We began with REA's inspect_artifact on the application's resources/app.asar. This pins the input package and inventories its program files. Its package metadata identifies version 3.7.21.
Next, analyze_javascript_application selected three real files from that archive:
InteropServiceHelper.js
commands/exportNotes.js
commands/exportFolders.js
The command wrappers supply sourceNoteIds or sourceFolderIds, a destination and a format to InteropService.instance().export(...). The helper's export function assembles those options and calls the same service. REA's source ranges and semantic nodes give us the places to read; they do not by themselves establish everything the export will do.
This changes the next question: rather than following a button's appearance, we need the service's queue and the Markdown exporter it selects.
Find the implementation in the shipped bundle
The service import does not lead to a loose copy of the library in this installation. Its implementation is inside the 19,982,228-byte main-html.bundle.js. The accompanying source map lists InteropService_Exporter_Md.ts, and the bundled class retains the name InteropService_Exporter_Md in a static naming block.
We used those clues to select the unchanged AST regions for that exporter and InteropService. This was an analyst selection, not automatic whole-application recovery. Each region's byte offset, length and hash is recorded against the shipped bundle in the source manifest.
REA then analyzed both selected regions. Its function nodes locate prepareForProcessingItemType, processResource, replaceLinkedItemIdsByRelativePaths_ and processItem. These inputs retain the original code but omit surrounding module bindings, so imported aliases and some dynamic calls remain unresolved.
The first discovery: choose names before writing bodies
The service prepares every item type before processing the queue. In the Markdown exporter, the note preparation pass builds a notePaths map:
note ID → unique exported path
The candidate path combines the notebook hierarchy, a filename derived from the title and an extension. findUniqueFilename checks both files already on disk and names reserved by this export. The exporter requests the hyphen suffix style.
Our synthetic input contains two notes titled Roadmap: Q4 in Work / Research. Their reserved paths are:
Work/Research/Roadmap_ Q4.md
Work/Research/Roadmap_ Q4-1.md
This is why a link to the second note can be rewritten while the first is being written: the target's final filename is already known. Looking only at the current title would lose that distinction.
The same idea applies to notebooks. Two root notebooks titled Work become Work and Work-1 in this replay. The exporter maintains the folder-name reservations separately for each parent.
The second discovery: attachments have their own map
processResource chooses a resource filename, reserves a unique destination under _resources, copies the file and records the destination in destResourcePaths.
That map is different from notePaths. The service's processing order puts resources before notes, so resource destinations are available when note links are rewritten.
In the replay, two resources with the same supplied filename become:
_resources/diagram_(draft).svg
_resources/diagram_(draft)-1.svg
There is also a small naming distinction: a supplied filename uses safeFilename, while the fallback title uses friendlySafeFilename with extension preservation. Our title-only resource becomes _resources/Flow chart.svg.
The copies are real files in a new test directory. Their hashes match the three synthetic resource inputs.
The third discovery: links go back through the export root
From a note inside Work/Research, makeDirPath_(note, "..") produces a two-level prefix. Resource IDs become paths beneath _resources; note IDs use the destination from notePaths.
The resulting body includes:
[Next](../../Work/Research/Roadmap_%20Q4-1.md)



The note link goes up to the export root and back down, even though its target is in the same directory. It is valid without being the shortest possible relative path. Spaces and parentheses are escaped for Markdown link destinations.
REA's trace_javascript_semantics queries on notePaths and destResourcePaths retain ambiguous results because of dynamic property and method relationships. We use them to find the relevant reads and writes, then read the selected functions and test the mechanism. We do not present those traces as complete runtime data flow.
Check the mechanism with shipped code
The replay executes the original selected InteropService and Markdown exporter classes, their base class, and the original filename, uniqueness and link-escaping helpers. The original service supplies the preparation and processing order.
The controlled boundaries are explicit: synthetic model lookups replace the database; model serialization returns the fixture body; exporter selection returns the selected class; filesystem and path adapters support the test directory. Function-name annotations are inert. No Joplin profile, note database or account data is opened.
The checks confirm that:
- Duplicate note and notebook titles receive distinct paths.
- A forward link uses the reserved filename, including its collision suffix.
- Nested note and resource links resolve to the emitted files.
- Resource collisions and Markdown URL escaping preserve the expected destinations.
- All three copied resources match their input hashes.
- The original service copies resources before writing note bodies.
All five emitted note/image link destinations resolve inside the generated export. This checks the selected mechanism; it does not validate the desktop file picker, database serialization, every Markdown construct or a round-trip import.
Repeat it and inspect the evidence
Download the replay bundle, extract it and run:
node verify.mjs
The script needs Node.js and creates a new joplin-export-replay-* directory beside your working location. It verifies the selected source hashes, runs the controlled export and saves checks.json. It has no npm dependencies. The Joplin excerpts and replay are supplied under AGPL-3.0-or-later with attribution and license text.
To inspect those selected classes with REA, use:
rea analyze-javascript-application ./joplin-export \
--source-path exporter.js --source-path service.js \
--format json
The run record lists the actual archive analysis and semantic queries. The entry excerpt, exporter excerpt, note-path trace and replay results let you follow the investigation without the installed application.
Public analysis files contain selected fields or normalized local paths. They are readable publication copies, not untouched canonical re-import bundles. The full local REA results were retained during this investigation. The installed archive's identity is pinned locally; we do not claim an independent installer-signature verification.
The useful result is an explanation of how export stays connected: reserve identities as file paths, finish the resource map, then rewrite references using those maps. The binary checker case follows a different target through the same question → evidence → interpretation → verification method.
Recorded verification
Joplin Desktop 3.7.21 / Markdown export / 2026-10-23
Environment: Windows x64, REA 6.7.0, host Node.js 24.15.0 / installed Joplin Desktop 3.7.21 / @electron/asar 4.3.1, model Authoring assistant interpreted CLI evidence; no isolated model benchmark.
Sample SHA-256: 9f793336c679ea3214542587ca42abc77e82184327e3f7677f22d47d8ae23506
- Inspect the installed ASAR with REA and analyze three export entry files.
- Locate the service/exporter in the shipped bundle and select unchanged AST regions, retaining offsets and hashes.
- Analyze the selected classes with REA and trace notePaths, destResourcePaths and the preparation call.
- Run the original selected service/exporter/helpers with synthetic model, serializer, module-selection and FS/path adapters; verify emitted links, collision suffixes, resource hashes and operation order.
Expected: Explain how note/resource IDs become exported file paths, including collisions and nested notebooks.
Observed: Nine assertion groups pass. Same-title notes and notebooks get distinct paths; all five emitted note/image link destinations resolve; three resource hashes match inputs; resources are copied before note bodies. Two data-flow traces remain ambiguous and are not treated as complete runtime flow.
- Controlled module replay, not a desktop UI export or a read of any Joplin profile.
- The analyst selected original bundle regions. Imported aliases and omitted modules are not globally resolved by the bounded REA analysis.
- Database/model lookup, serialization, exporter selection, filesystem/path adapters and inert naming annotations are injected.
- Public evidence contains selected fields or normalized paths; it is not an untouched canonical re-import bundle.
Sources & further reading
- Joplin 3.7.21 release ↗
Release identity; detailed findings come from the locally inspected installed package and retained evidence · Checked Oct 23, 2026
- Joplin import/export documentation ↗
User-facing context for exporting notes and notebooks · Checked Oct 23, 2026
- REA JavaScript/Electron guide ↗
Application inspection and following feature evidence · Checked Oct 23, 2026
- Joplin license ↗
Joplin source attribution and licensing · Checked Oct 23, 2026
Tools in this guide
- REA — Agent-powered workflow