Repository navigation
Concord: Tine alongside git, Syncthing, Dropbox and your other tools #534
Replies: 3 comments 1 reply
|
@martinkoutecky From Fable 5.1's description, Tine's implementation already looks quite comprehensive and reliable. At least from my own experience, I have not seen another note-taking application handle this in quite the same way, which makes Concord rather distinctive.
Professional diff tools such as Beyond Compare already provide very powerful A/B text comparison, but Tine's block-level conflict handling feels much more natural, intuitive, and convenient for an outliner. Users can understand differences and choose what to keep directly within the original outline structure, instead of treating the entire file as one large piece of text. At least in the specific context of resolving conflicts in an outliner, I think Tine's block-level workflow can even be more convenient than professional diff tools such as Beyond Compare. This also reflects a development philosophy that I strongly agree with: software should serve people, rather than requiring people to serve the software by constantly adapting themselves to it. I think this is more than just a matter of user experience. It also affects where development resources ultimately get spent. When development resources are limited, if the priority is always "how can the software serve the user better?", then development naturally tends to focus on problems users actually encounter, reducing unnecessary steps, cognitive load, and friction in real workflows. In that case, a relatively limited amount of development effort can still produce a very high amount of practical value. By contrast, if software more often requires users to adapt themselves to its own structures, rules, and workflows, then even with abundant development resources, a great deal of time and effort can end up being invested in things that look sophisticated, advanced, or ambitious, but do not necessarily provide the same level of value in real use. Over time, the total amount of development may be large, while the proportion that translates into meaningful user value can remain relatively low. I think this is also one reason Tine, despite starting later, may still have the opportunity to gradually move ahead: it has the chance to avoid a lot of vague, low-value, but very time-consuming development, and instead concentrate limited resources on things that genuinely affect usability, efficiency, and reliability. I also think the designs of Concord and the Base Ledger are very sensible. In particular, the Base Ledger allows Tine to compare both sides against "the last version both sides agreed on" and determine who actually changed what. This significantly reduces the amount of manual judgement required when resolving conflicts. At the same time, Tine can now handle conflict copies from Syncthing, Dropbox, and Seafile, as well as merge conflicts from Git and Fossil. That gives the conflict-handling system quite broad compatibility. My original idea in #337 was mainly that conflicts should be preserved and resolved directly inside the page. After further refinement by you and your AI tools, moving the conflict review to the top of the affected page seems clearly better. It keeps conflict resolution in the same context as the actual content and avoids repeatedly moving between Settings, Backups & recovery, and different locations inside a page. That reduces both friction and the number of steps required. Compared with both the earlier approach centered around Backups & recovery and my original in-page idea, I think the current design has become considerably more mature. I have one small additional suggestion. If many pages become conflicted at the same time, I think the current Conflict indicator at the bottom-left could be developed into the entry point for a dedicated Conflict Page. In other words, clicking This page could behave like a normal Tine page and simply show a list like:
This would make it very easy to see:
I think this would be particularly useful after a large synchronization, a Git branch switch, or any situation where many pages become conflicted at once. I also think the Conflict Page itself should remain open when the user selects a specific conflicted page. Clicking one of the pages in the list could open that page in a new page / pane, while keeping the Conflict Page available as an overview and navigation surface. The overall structure would then be quite clear:
This would preserve the simplicity of the current Conflict Queue while adding a clearer global overview for large-scale conflicts, without introducing another separate entry point. Of course, whether this is worth implementing, and what form it should ultimately take, is entirely up to your design judgement. |
|
What if Tine exposed the graph as a fuse filesystem? |
|
Hi! This is an interesting idea but AFAIK this would basically only work on
Linux, no? I don't think fuse is multiplatform in a meaningful sense, while
I want to ship Tine for at least the five platforms that I ship today.
Also, I'm not sure it would help very much - Tine would still have to
reconcile what the user wrote into the file vs what was currently in the
app. Put differently, this doesn't seem different in principle from the
current situation where Tine notices a file write via inotify and then acts
on it. Am I missing something?
Dne pá 18. 9. 2026 10:39 uživatel Dieter Plaetinck ***@***.***>
napsal:
… What if Tine exposed the graph as a fuse filesystem?
that way, other applications still just see a directory with markdown
files. but when they make any change (syscalls such as write(), unlink(),
rename(), mkdir()) tine can intercept, do whatever it wants to do, and only
return the syscall when tine's internal state is up-to-date as well.
This way, you can keep additional state that is guaranteed to be always
consistent with the files in the folder.
—
Reply to this email directly, view it on GitHub
<#534?email_source=notifications&email_token=AADMWVMCYCHPWWBLTD6DORD5PTYENA5CNFSNUABIM5UWIORPF5TWS5BNNB2WEL2ENFZWG5LTONUW63SDN5WW2ZLOOQXTCOBUHE3TENJRUZZGKYLTN5XKO3LFNZ2GS33OUVSXMZLOOSWGM33PORSXEX3DNRUWG2Y#discussioncomment-18497251>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AADMWVNQZUS4RYXHOZEYPBD5PTYENAVCNFSNUABJKJSXA33TNF2G64TZHMYTENZZGYZTOMJZGI5UI2LTMN2XG43JN5XDWMJQG44TMMZQHGQXMAQ>
.
You are receiving this because you were mentioned.Message ID:
***@***.***>
|

Uh oh!
There was an error while loading. Please reload this page.
Written by Fable 5.1, not by me (Martin). I have read it and stand by it.
Back in #342 I mentioned "an interesting design for #337, more on that when the release lands". It landed in v0.6.94 and has been refined in every release since, and I never wrote the "more". Here it is.
The problem
#337 (thanks @EllisMorrow, and @sulisu for the Fossil angle) was about Tine not reliably noticing that a Markdown file had changed under it, and in the worst case saving a stale version over an edit made elsewhere. That is the one thing an outliner over plain files must never do. But the fix I wanted was not "make Tine's file watcher a bit better". Direct Files graphs live in a folder that other software also owns: Logseq itself, a text editor, Syncthing, Dropbox, Seafile, git, Fossil, a USB stick. Tine does not bring its own sync for Direct Files, and it should not pretend to. So the question became: what does it mean for Tine to be a perfect citizen of a folder it does not own?
I called the answer Concord, and it is a contract with three promises:
.conflictfile you have to open in another app, and not an automatic merge you did not see.And one rule underneath all three: the files on disk stay plain, valid Logseq Markdown throughout. Tine invents no markers, no metadata, no sidecar properties to record any of this. Whatever Tine writes, Logseq (or
cat) reads.What shipped
Clean pages reload silently. If a file changes on disk and the page has no unsaved edits in Tine, the page is replaced, no flicker, nothing to click. The watcher is inotify-based by default (a 3-second poll is available for network mounts that lie), it ignores
.git,.stfolderand similar noise entirely, and Tine's own saves do not bounce back as external changes. If you would rather be asked even in the clean case, there is a switch in Settings → Backups & recovery.A page you are editing is never yanked. If the file changes while your caret is in that page, the reload is deferred and replayed the moment you click away. At replay time the safety checks run again from scratch; if you typed in the meantime, the reload is not applied and the conflict path takes over instead. Returning to the window also triggers a pass over the graph, which covers suspended apps and sync tools that deliver no filesystem event at all.
Bulk changes are one event, not hundreds. A
git checkout,fossil update, a branch switch or a first big sync used to be a storm of per-page reloads. Now anything touching more than 32 pages is treated as a single external revision: one reconcile pass, one toast ("128 pages updated externally · 2 conflicts to review"), visible pages refreshed through the same safety checks, everything else reloaded lazily.Sync conflict copies are recognized and kept out of your page list. Syncthing's
Page.sync-conflict-YYYYMMDD-HHMMSS-DEVICEID.md, Dropbox's(conflicted copy), Seafile's(SFConflict ...)are matched by their exact generated shapes. They never appear as pages, never hijack the real page's identity through atitle::property, and Tine never renames, rewrites or deletes them on its own. (OneDrive'sPage-COMPUTERNAME.mdand Google Drive'sPage (1).mdare deliberately not recognized: their names are indistinguishable from ordinary page names, and hiding a real page would be worse than showing a duplicate.)git and Fossil merge markers are quarantined, and resolvable in Tine. An outliner that re-saves a file containing
<<<<<<</=======/>>>>>>>destroys the merge: the markers are not bullets, so they get re-indented or dropped, git stops seeing the conflict, and one side is lost without anyone choosing it. Tine now renders such a file as-is with a banner, refuses every save to it (including force-save) while the markers are present, and lets you resolve the merge block by block on the page itself. Applying writes the merged page without any markers; a byte-exact copy of the pre-resolution file goes to the recoverable trash first. Or resolve it in git as you always did; the moment the markers are gone, the page is editable again with nothing to reset.One conflict queue. Everything needing your judgement, whether a diverged live edit, a sync copy or a marker-bearing file, is one quiet
N conflictsbadge at the bottom of the sidebar. Clicking it walks you to the next conflicted page. The queue is derived from disk on every scan, with no cache, so it cannot go stale and survives restarts by construction. No modal opens, nothing is blocked, and leaving a page with conflicts outstanding gets a one-line note rather than a dialog.Resolution happens on the page, block by block. The sides are named by what produced them (
HEADvsfeature/x, or the Syncthing device tag) and colored consistently. Each differing block gets keep-mine / keep-theirs / keep both, andN conflicts ↑↓walks between them. Keep-both writes two adjacent sibling blocks, ordinary outline Markdown any tool can read. Nothing is written until you press Apply resolution, and the write goes through the same guarded path as every other save (page lock, the file must still be byte-for-byte what you reviewed, losing side recoverable from trash).The base ledger turns a guess into a mostly-answered question. Tine remembers, per page, the last version it agreed on with the disk (it lives in Tine's app data, outside your graph; your sync tool never sees it and deleting it is always safe). With that common ancestor, a three-way comparison tells Tine who changed what: a block only you changed arrives with your version pre-selected and labeled suggested; a block only the other side changed arrives with theirs; a block both sides changed in provably different places gets a combined Merged row. The normal gesture becomes glance-and-confirm rather than hunt-and-compare. Nothing is ever merged without you clicking.
Tine does not touch bytes it has nothing to say about. Opening a graph, reading a page, or re-saving a page you did not edit leaves every file byte-identical: your indentation, your trailing newline or lack of one, your line endings. This matters most under git (a spurious rewrite is a diff you did not make) and under a sync tool (a wake for every device). The old behavior of silently renaming title-named journal files at every graph open is gone; that repair now runs only when you ask for it in Settings.
Slow or missed changes are diagnosable without setup. Tine keeps the last 64 external-change batches with per-stage timings (OS event → reconcile → UI).
await __tineWatcherLatency()in the devtools console prints them; they contain file counts and timings only, never note content. If you see a multi-second delay, paste that into an issue.What Concord is not
It is Direct Files only. Managed Storage has its own sync with its own conflict model (block-level, oplog-based), which is the subject of the 0.7.0 work.
It is not a merge tool for arbitrary files: only pages and journals, only the exact conflict-copy shapes listed above, and merge markers only at column 0 in Markdown/Org. A page that merely documents merge markers inside a code fence is left alone.
It is not multi-window on one graph. Two Tine windows on the same folder are still refused, so that particular conflict cannot happen locally. (Same-graph multiwindow is a separate, planned piece of work.)
Along the way one thing was removed: the block-by-block merge dialog that used to live inside Settings. Two surfaces over the same conflict opened with different pre-selections, which is precisely the kind of quiet disagreement Concord exists to prevent. Settings → Backups & recovery is now the inventory (list, "Review in page…", "Discard copy"); resolution has one home.
Where to read more, and what I would like from you
The full description is
docs/concord.mdin the repo; the user-facing version is the Guide page Files, external edits and backups. The two design records are ADR 0056 (the base ledger and three-way diff; it reverses an earlier decision to stay two-way) and ADR 0057 (conflict objects and in-page resolution).If you keep a graph in git or Fossil, or sync it with Syncthing / Dropbox / Seafile across devices, I would like to hear how it behaves for you: especially any conflict copy that did not get recognized, any merge marker layout your tool produces that Tine does not understand, and any external change that took more than a second to arrive (with the latency receipt above). Those are the reports that improve this.
All reactions