feat: automatic periodic bookmarks sync#247
Conversation
- Implement syncSiblings() to reorder bookmarks to match file explorer order and remove orphaned entries - Add configurable sync interval setting (0 = disabled, up to 3600s) - Start/stop sync timer on setting change and plugin lifecycle - Sync bookmarks immediately on new file/folder creation
|
Hi @andreas13xxx, looks great at glance! I didn't have time to do a full review yet, only a brief scan of the code changes. A fundamental question - did you take into account the race conditions? They can occur on slower desktops / synced vaults and especially on mobile devices. I remember they were tricky and I did many tests whenever I worked on code sensitive to race conditions. The plugin starts and initializes, yet the required data for sorting (metadata of vault files) is not yet available or fully populated. I don't remember the final logic now, it is probably tied to the rendering of File Explorer, so that until the File Explorer is actually rendered, custom sorting is not applied. Preventing race conditions for the bookmark sync introduced in this PR would essentially boil down to guaranteeing that bookmark syncing does not happen until it is absolutely certain that the custom sorting order has been correctly determined and can be safely applied to bookmarks - otherwise there is a risk that bookmarks could be overwritten with incorrect order (or partially correct). With the current philosophy of the code of the plugin, including bookmarks integration, race conditions do not matter because if the first attempt to sort results in a not fully correct order, the second (or subsequent) attempt will eventually work - the user may briefly see an incorrect order until everything settles down. But when the bookmarks sync happens (as introduced by this plugin), the race conditions can cause permanent damage to the sorting order stored in bookmarks. The internal logic of Obsidian startup sequence changed across versions, that's why testing was always required on the newest release. Let me know what you think |
|
Hello Sebastian, Obsidian’s architecture makes it hard to rule out race conditions. But maybe we can make the code more robust. Here are the concrete problems I currently see:
this.initialize(); // waits for onLayoutReady + delay + DOM watcher The timer calls runBookmarksSyncForVault(), which in turn calls orderedFolderItemsForBookmarking(). This method uses this.sortSpecCache – but the cache may still be null at this point, because readAndParseSortingSpec() is only called in switchPluginStateTo(true), which happens only after the entire startup dance (onLayoutReady → delay → possibly DOM watcher). If sortSpecCache is still null, determineSortSpecForFolder() returns undefined, and the sorting falls back to Obsidian’s default order. syncSiblings() then overwrites the bookmarks with this wrong order – permanently.
During startup (especially with sync vaults, where many create events are fired while new files are being downloaded), this handler can run before the sorting logic has been initialized. Same effect: wrong order is written into the bookmarks.
runBookmarksSyncForVault(): void { It is missing a check for this.customSortAppliedAtLeastOnce or whether this.sortSpecCache is populated at all. The existing flag customSortAppliedAtLeastOnce is exactly the indicator that sorting has been successfully applied at least once – i.e. that all data is ready. Proposed safeguard: Only start the timer after sorting has been successfully applied for the first time – that is, do not call restartBookmarksSyncTimer() in onload(), but instead at the point where customSortAppliedAtLeastOnce = true is set. Guard in runBookmarksSyncForVault() and in the create handler: runBookmarksSyncForVault(): void { Same guard in the vault.on("create") handler: |
|
hi @andreas13xxx, Yes, please go ahead and apply the changes. BTW did you use some AI tool? I was thinking about hiring Claude to review the code and support adding new features, especially generating unit tests. I will do a full review of the PR once the code is considered final — it requires a fresh head and a solid block of uninterrupted time. Somewhere in my notes (or comments) I collected observations and findings about inconsistent behavior in Obsidian bookmarks, including one or two bugs that were difficult to fully understand based on reverse-engineering the code. The root cause of the issues seemed to be the assumption that only Obsidian modifies bookmark data — it appears to keep some internal cache and additional state. One of the bugs manifested itself in such a way that dragging and dropping bookmarks caused duplicated entries: one entry remained at the top of the list, while another was copied to the correct destination (this issue still exists). Another problem was that changes made by the plugin could sometimes be overwritten. I'm not sure about the best way to approach this yet — let's continue step by step. |
- Remove restartBookmarksSyncTimer() from onload() to avoid syncing before sorting is initialized - Start bookmark sync timer only after custom sorting has been successfully applied (customSortAppliedAtLeastOnce = true) - Add guard in runBookmarksSyncForVault() to bail out if sorting was never applied yet - Add guard in vault 'create' handler to skip sync during startup This prevents bookmarks from being overwritten with incorrect order on slow desktops, synced vaults, and mobile devices where metadata may not be fully available at plugin startup time.
|
Hi @SebastianMC , And yes, I’m using AI — specifically KIRO. I hope that’s okay with you. KIRO can work with different LLMs. I mostly use the Claude models depending on the task (Haiku 4.5, Sonnet 4.6, or Opus 4.6). With Claude Code, you should be able to achieve very similar results. The advantage of KIRO is the very convenient spec-driven workflow. The downside is that some things work slightly differently (e.g. steering, etc.). In the end, it’s mostly a matter of personal preference which tool you use. AI is especially great for reverse engineering and for automatically building unit tests and similar tasks. Give it a try! If you have any questions, feel free to send me a DM. |
Summary
This PR adds automatic periodic synchronization of sorting bookmarks with the file explorer, addressing the feature requests in #108 and #192.
What it does
Full bookmark sync (
syncSiblings): A new method that enforces the correct file explorer order in bookmarks. Unlike the existingbookmarkSiblings(which only appends missing items), this method:Configurable auto-sync timer: A new setting
Automatic bookmarks sync interval(in seconds). Set to0to disable (default). When enabled, the plugin periodically runs a full vault-wide bookmark sync.Immediate sync on file creation: When auto-sync is enabled, newly created files/folders are immediately synced into the correct bookmark position via a
vault.on('create')handler.Manual recursive sync (context menu): A new context menu entry Sync bookmarks for sorting (recursive) that performs a one-shot full sync on a folder and all its descendants.
How to use
60for once per minute)For manual one-shot sync: right-click any folder in File Explorer → Custom sort → Sync bookmarks for sorting (recursive)
Addressing concerns from #108 and #192
Sebastian raised several valid concerns about automatic bookmark synchronization:
"Keeping bookmarks in sync is technically not feasible"
The periodic full-sync approach sidesteps the event-based synchronization problems. Rather than trying to catch every individual file system event in real-time, we periodically reconcile the entire state. Missed events (e.g. during plugin startup) are corrected at the next sync cycle. The
vault.on('create')handler provides near-instant sync for the most common case (new files)."Duplicate bookmark entries when rearranging items"
The
syncSiblingsmethod completely replaces the bookmark list for each folder with the correct ordered set. Any duplicates or orphans are eliminated on every sync cycle."High implementation/maintenance effort due to Obsidian breaking changes"
This implementation reuses the existing plugin infrastructure exclusively (
orderedFolderItemsForBookmarking,findGroupForItemPathInBookmarks, the existing bookmark data structures). No new Obsidian API dependencies are introduced. The maintenance surface is minimal: one new method inBookmarksPluginWrapper, a timer, and a create-event handler."Conflict with manual drag & drop ordering"
This is an inherent trade-off: when auto-sync is enabled, manually reordered bookmarks will be overwritten at the next sync cycle. This is by design — the setting is opt-in (disabled by default) and intended for users who want bookmarks to mirror the sorting spec automatically. Users who prefer manual bookmark ordering should leave the interval at
0.Technical details