Skip to content

Commit fb5e8ed

Browse files
dbdeveloperclaude
andcommitted
release: 2.0.1-beta — pseudo-merge mode
Version bump: 2.0.0-beta → 2.0.1-beta. Includes manual sync of manifest-beta.json (version-bump.mjs only touches manifest.json + versions.json). README.md: - New "What's new in 2.0.1-beta" section with a marketing-friendly feature list pointing at docs/PSEUDO-MERGE-MODE.md for full rationale: plain-file-ops resolution, edit-while-in-conflict, preserve-all-commits, auto-merge first, crash-tolerant atomic writes, three visibility surfaces, per-file resolution on multi-file sessions, *.unresolved-* rename on reset. - Explicit note that the side-by-side diff-edit GUI is planned for the next release; this release ships pseudo-merge mode using only the native Obsidian file operations users already know. - "Conflict resolution" section rewritten from the ground up to match 2.0.1-beta behaviour: auto-merge dispatch table, sibling file naming, three resolution moves (delete sibling / rename over base / hand-merge then delete sibling), multi-sibling rules, link to docs/PSEUDO-MERGE-MODE.md for depth. CLAUDE.md: full rewrite. - Header section pointing at README.md (user-facing) and docs/PSEUDO-MERGE-MODE.md (conflict-layer canonical spec) instead of duplicating their content. - Module layout updated to reflect current src/ tree (no commit-templates, conflict-merge-all, ConflictView, etc.). - Module layout annotated with the actual entry points each file exposes for Claude-Code navigation. - Constraints section consolidated; added Capacitor-rename-doesn't- overwrite pattern + sync-vs-conflict-layer event-driven split. - Removed the giant per-bucket integration-test table (it duplicated the article's scenarios). Kept the test layout + env-var reference. - Dropped the entire "Pseudo-merge planned rework" block — that was a development-process artefact; the article supersedes it. PSEUDO-MERGE-MODE-IMPLEMENTATION_PLAN.md deleted. Was the work-in-progress design document during the conflict-layer rewrite; its content is now superseded by docs/PSEUDO-MERGE-MODE.md (canonical article) + the implementation itself. Tests: 526/526 unit GREEN; 106/106 integration GREEN; build clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 81283af commit fb5e8ed

7 files changed

Lines changed: 237 additions & 2835 deletions

File tree

CLAUDE.md

Lines changed: 99 additions & 491 deletions
Large diffs are not rendered by default.

PSEUDO-MERGE-MODE-IMPLEMENTATION_PLAN.md

Lines changed: 0 additions & 2302 deletions
This file was deleted.

README.md

Lines changed: 133 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,74 @@
33
> Sync your Obsidian vault with a GitHub repository — no `git` binary,
44
> no `isomorphic-git`, identical behaviour on desktop and mobile.
55
6-
Version `2.0.0-beta` · AGPL-3.0 · Fork of
6+
Version `2.0.1-beta` · AGPL-3.0 · Fork of
77
[`github-gitless-sync`](https://github.com/silvanocerza/github-gitless-sync)
88

99
---
1010

11+
## What's new in 2.0.1-beta
12+
13+
Conflict resolution rebuilt from the ground up. Full design rationale
14+
in [docs/PSEUDO-MERGE-MODE.md](./docs/PSEUDO-MERGE-MODE.md); the
15+
practical highlights:
16+
17+
- **Resolve conflicts with plain file operations — no plugin UI
18+
required.** Every conflict becomes a pair of ordinary files in
19+
your vault: the original (your local version) and a sibling
20+
(`<note>.conflict-from-<device>-<timestamp>.md`) carrying the
21+
remote version. You resolve by managing those files the same way
22+
you manage any other file in Obsidian: delete the sibling to keep
23+
yours, rename it over the base to accept theirs, edit either or
24+
both to produce a hand-merged result. No modal dialogs, no
25+
conflict markers leaking into your reading view. *(A dedicated
26+
side-by-side diff-edit GUI is planned for the next release. For
27+
now, resolution uses only the native Obsidian file operations
28+
you already know — file explorer, editor, delete, rename.)*
29+
- **Keep typing even while a file is in conflict.** Your in-progress
30+
edits flow to a private GitHub branch that no other device sees
31+
until you finalise the resolution. The conflict doesn't block
32+
you, and other devices stay protected from your half-resolved
33+
state.
34+
- **Your full edit history is preserved on GitHub — forever.** Every
35+
commit the plugin ever produces — including every iteration made
36+
during a conflict-resolution session — remains reachable on the
37+
GitHub network graph. Nothing is silently squashed or discarded;
38+
months later you can find the merge-commit for any resolved
39+
conflict and walk the side-branch to see exactly how you got
40+
there.
41+
- **Auto-merge first.** Text files get a real three-way merge against
42+
the last-synced base; only overlapping line edits surface as
43+
conflicts. Plugin bundles auto-resolve by semantic version
44+
(`manifest.json`'s `version` field), so a plugin update on one
45+
device propagates without manual intervention. Binary files
46+
always produce a sibling so you can see both versions side by
47+
side — no silent overwrites of edited images.
48+
- **Crash-tolerant atomic writes.** Every disk operation that
49+
touches multiple files (writing a sibling, persisting a conflict
50+
record, replacing a pulled file) is built on a documented
51+
multi-step protocol with a recovery sweep on plugin load. An
52+
interruption — mobile OS suspending the app mid-write, laptop
53+
losing battery during a push — leaves the vault in either the
54+
pre-operation state or the post-operation state, never a
55+
half-applied state.
56+
- **Three visibility surfaces for pending conflicts.** Status bar
57+
shows `🔀 N`, the ribbon icon carries a count badge, and the next
58+
Sync click surfaces a confirmation modal listing exactly which
59+
files are still pending. Nothing about a conflict can hide from
60+
you.
61+
- **Multi-file conflict sessions resolve one file at a time.** When
62+
several files end up in conflict, each per-file resolution
63+
publishes its result to `main` as an ordinary commit; the
64+
conflict branch waits in the background and merges back only
65+
when the last file is settled.
66+
- **`Reset` cleanly relabels conflict siblings.** If you wipe the
67+
plugin state via the settings-tab `Reset` button, your
68+
`*.conflict-from-*` files are renamed to
69+
`<file>.unresolved-<original-ts>.<ext>` so they won't collide
70+
with a future re-enable.
71+
72+
---
73+
1174
## Why another GitHub sync plugin?
1275

1376
Most existing Obsidian-to-Git plugins either rely on the `git` CLI
@@ -419,46 +482,78 @@ credentials → one Sync click → reload → done.
419482

420483
## Conflict resolution
421484

422-
> **Status: works but not exhaustively tested.** The atomic paths
423-
> (binary, plugin files) and the simple 3-way text merge are
424-
> well-covered by integration tests; the diff-edit UI for unresolved
425-
> text conflicts works on desktop and Android but the UX is still
426-
> rough. Treat this section as a beta feature: it won't lose data
427-
> (sibling-file deferral guarantees that), but it may surprise you.
428-
429485
A conflict happens when the same file changed on both sides since
430486
the last sync — for example, you edited a note on your phone, then
431487
edited the same note on your laptop, then clicked Sync on the
432-
laptop. The plugin classifies the path and picks a resolver:
433-
434-
| What changed | Resolver |
435-
|---|---|
436-
| Binary (`.png`, `.pdf`, …) | **Atomic mtime** — the newer side wins. No merge attempt, no prompt. |
437-
| Plugin's `main.js` or `manifest.json` | **Atomic semver** — the higher version from `manifest.json` wins, with mtime as tie-break. Merging minified plugin code would crash Obsidian, so we don't try. |
438-
| Other text (`.md`, `.txt`, …) | **3-way merge** against the last-synced base. If the merge is clean, applied silently. If it produces conflict markers, the diff view opens. |
439-
| Local deleted + remote modified | **Surfaced as a conflict** — pick: keep delete / take remote / merge / defer. |
440-
441-
When the diff view opens, you get three resolution choices:
442-
443-
- **Resolve now** — edit the merged content directly in the diff
444-
view, then save. The resolved content lands in the next push.
445-
- **Merge into one** — concatenates both versions with clear
446-
separators. Useful when both edits should be preserved verbatim
447-
(you'll clean up the result later in your normal editor).
448-
- **Defer** — keep your local version as-is and write the remote
449-
version next to it as a sibling file named
450-
`<note>.conflict-from-<deviceLabel>-<timestamp>.md`. Sync
451-
resumes; you resolve at your own pace. Deleting the sibling
452-
closes the conflict.
453-
454-
<!-- SCREENSHOT: conflict view, both panes visible -->
455-
456-
Deferred conflicts show up as a 🔀 counter in the status bar
457-
(click it to open the list).
458-
459-
The `.conflict-from-…` sibling files are gitignored by default —
460-
they stay strictly local. If you want them to sync across devices,
461-
edit `<vault>/.gitignore` and remove the `*.conflict-from-*` line.
488+
laptop. **Full design rationale and worked examples in
489+
[docs/PSEUDO-MERGE-MODE.md](./docs/PSEUDO-MERGE-MODE.md).** The
490+
user-facing summary:
491+
492+
### Auto-merge happens first
493+
494+
Before any conflict surfaces, the plugin tries to reconcile the two
495+
sides on its own. The strategy depends on the file type:
496+
497+
| File type | Auto-merge strategy | Surfaces as a conflict only when… |
498+
|---|---|---|
499+
| Text (`.md`, `.txt`, …) | Three-way merge against the last-synced base. | Edits overlap on the same line. |
500+
| Plugin bundle (`<plugin>/main.js`, `<plugin>/manifest.json`) | Higher semantic version from `manifest.json` wins; mtime tie-break. | Identical version **and** identical mtime. |
501+
| Binary (`.png`, `.pdf`, attachments) | None — binary always surfaces. | Always (no silent overwrites of edited images). |
502+
| Local deleted, remote modified | Auto-resolves in favour of the modification (the more recent intent wins; the file resurrects on remote). | Never. |
503+
| Local modified, remote deleted | Surfaces as a conflict — your edit is preserved as a sibling. | Always. |
504+
505+
When auto-merge succeeds, the merged result goes straight to GitHub
506+
and you're never notified — there was nothing for you to decide.
507+
508+
### When a conflict surfaces
509+
510+
The plugin writes the remote version into the vault next to your
511+
file, named:
512+
513+
```
514+
<note>.conflict-from-<remote-device>-<isoTimestamp>.md
515+
```
516+
517+
The original file (your local version) is **not** touched. Both
518+
files are visible in the Obsidian file explorer, openable in the
519+
editor, indistinguishable from any other Markdown / binary file.
520+
The status bar shows `🔀 N` for the count; the ribbon icon carries
521+
a matching badge.
522+
523+
### Resolution — entirely through native Obsidian file operations
524+
525+
> **A dedicated side-by-side diff-edit GUI is planned for the next
526+
> release.** In this release, conflict resolution is done with the
527+
> native Obsidian operations you already use every day: open the
528+
> files in the editor, delete a file from the file explorer, rename
529+
> a file by long-press (mobile) or right-click → rename (desktop).
530+
531+
The three resolution moves and what they mean:
532+
533+
- **Delete the sibling** (`<note>.conflict-from-…md`) → keep your
534+
local version. On the next sync the local content is published to
535+
GitHub.
536+
- **Rename the sibling onto the base** (`<note>.conflict-from-…md`
537+
`<note>.md`, overwriting your local version) → accept the
538+
remote version.
539+
- **Edit the base file by hand**, copying the parts you want from
540+
the sibling, then **delete the sibling** → publish your
541+
hand-merged result.
542+
543+
A file may carry several siblings if multiple devices contributed
544+
conflicting versions — they're distinguished by the device-label
545+
segment in the filename. The conflict on that path is closed only
546+
when **every** sibling is gone (or matches the base byte-for-byte).
547+
548+
While a file is in conflict, **you can keep editing the base
549+
file**. Those edits flow to a private branch on GitHub that no
550+
other device sees until you finalise. The conflict doesn't block
551+
you; other devices stay protected from your half-resolved state.
552+
553+
The `*.conflict-from-*` filename pattern is gitignored by default —
554+
siblings stay strictly local. If you want them to sync across
555+
devices, edit `<vault>/.gitignore` and remove the
556+
`*.conflict-from-*` line.
462557

463558
---
464559

manifest-beta.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"id": "github-easy-sync",
33
"name": "GitHub Easy Sync",
4-
"version": "2.0.0-beta",
4+
"version": "2.0.1-beta",
55
"minAppVersion": "1.7.7",
66
"description": "Sync a GitHub repository with vaults on different platforms without requiring git installation",
77
"author": "Vladyslav Kozlovskyy",

manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"id": "github-easy-sync",
33
"name": "GitHub Easy Sync",
4-
"version": "2.0.0-beta",
4+
"version": "2.0.1-beta",
55
"minAppVersion": "1.7.7",
66
"description": "Sync a GitHub repository with vaults on different platforms without requiring git installation",
77
"author": "Vladyslav Kozlovskyy",

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "github-easy-sync",
3-
"version": "2.0.0-beta",
3+
"version": "2.0.1-beta",
44
"description": "Sync a GitHub repository with vaults on different platforms without requiring git installation",
55
"main": "main.js",
66
"scripts": {

versions.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,6 @@
1414
"1.0.5": "1.7.7",
1515
"1.0.6": "1.7.7",
1616
"1.0.7": "1.7.7",
17-
"2.0.0-beta": "1.7.7"
17+
"2.0.0-beta": "1.7.7",
18+
"2.0.1-beta": "1.7.7"
1819
}

0 commit comments

Comments
 (0)