Compare commits

...
15 Commits
Author SHA1 Message Date
vorotamorozandGitHub 0b31b84598 Merge pull request #1111 from vrtmrz/1_0_14
Releasing 1.0.14
2026-08-14 22:06:44 +09:00
github-actions[bot] 3a4f84443c Releasing 1.0.14 2026-08-14 12:21:36 +00:00
vorotamorozandGitHub a5e7ec6546 Merge pull request #1110 from vrtmrz/release-notes/1.0.14
Archive earlier 1.0 release notes
2026-08-14 21:13:55 +09:00
vorotamoroz 80fc493d0e docs: rotate 1.0 release notes 2026-08-14 11:21:45 +00:00
vorotamorozandGitHub 7cff820aa6 Merge pull request #1108 from vrtmrz/fix/finite-remote-connection-ownership
Close finite remote database connections owned by LiveSync
2026-08-14 19:55:38 +09:00
vorotamoroz 5fdac724cf Update Commonlib to 0.1.14 2026-08-14 10:40:31 +00:00
vorotamoroz 74fdec7f96 test: separate finite maintenance connection ownership 2026-08-14 10:29:29 +00:00
vorotamoroz de03534d2f fix: close finite remote database connections 2026-08-14 10:29:29 +00:00
vorotamorozandGitHub 16c7cc1b0f Merge pull request #1109 from vrtmrz/experiment/community-review-npm10-lockfile
Fix Community Review dependency installation under npm 10
2026-08-14 19:27:52 +09:00
vorotamoroz 0aec7b8cca Document Community Review install compatibility 2026-08-14 10:18:04 +00:00
vorotamoroz 040ce87b2f Make lockfile compatible with npm 10 2026-08-14 09:55:55 +00:00
vorotamorozandGitHub a4194a8978 Merge pull request #1107 from vrtmrz/update/commonlib-0.1.13
Update Commonlib to 0.1.13
2026-08-14 11:19:50 +09:00
vorotamoroz 1e9ab39adc Update Commonlib to 0.1.13 2026-08-13 15:43:40 +00:00
vorotamorozandGitHub 3973290485 Add note: Fix Fast Fetch tombstone handling for deletions
Fast Fetch now correctly handles deletion tombstones by writing them to the local database without decryption, allowing new devices to sync with vaults containing old deletions.
2026-08-13 14:38:18 +09:00
vorotamorozandGitHub 510db277cd Merge pull request #1105 from vrtmrz/1_0_13
Releasing 1.0.13
2026-08-13 13:01:17 +09:00
24 changed files with 513 additions and 352 deletions
+16
View File
@@ -27,6 +27,21 @@ npm ci
npm run build
```
#### Community Review dependency installation
Community Review installs dependencies independently before applying type-aware source rules. A successful installation with the npm version bundled with the repository's current Node.js CI does not prove that the lockfile is accepted by the scanner's npm version.
After changing `package.json`, a workspace manifest, or `package-lock.json`, verify both installation paths:
```bash
npm ci --ignore-scripts
npx --yes npm@10.9.2 ci --ignore-scripts
```
The npm 10.9.2 command is the current project-side compatibility check for the Community Review installation path. Update this check when the scanner runtime changes.
If Community Review reports widespread TypeScript `error` types across unrelated external packages, confirm that dependency installation completed successfully before changing source imports, declarations, or lint rules. An installation failure can make every unresolved external type appear as downstream unsafe-type findings.
### Commands
```bash
@@ -261,6 +276,7 @@ export class ModuleExample extends AbstractObsidianModule {
- Avoid listing purely internal refactors, maintenance chores, generated-file changes, and dependency updates unless they affect users; group and label them when they are included.
- When preparing a release, replace `## Unreleased` with the target version heading (for example, `## 0.25.81`) and add a fresh empty `## Unreleased` section above it for the next cycle.
- Review and polish the released section in the release PR before tagging, because the content is embedded into the plug-in and may be reused as the GitHub Release notes.
- Keep approximately the five most recent published plug-in versions in the embedded `updates.md`. Move older published sections unchanged into the appropriate release-line archive under `docs/releases/`, and update the history references when rotating them.
## Release Workflow
+1 -1
View File
@@ -1,6 +1,6 @@
# 1.0 preview release history
This document records the opt-in beta and release-candidate builds published before 1.0.0. Most users upgrading from 0.25.83 only need the consolidated [1.0.0 release notes](../../updates.md).
This document records the opt-in beta and release-candidate builds published before 1.0.0. Most users upgrading from 0.25.83 only need the consolidated [1.0.0 release notes](1.0.md#100).
The prepared `1.0.0-rc.0` tag was not published as a plug-in release and is therefore omitted.
+247
View File
@@ -0,0 +1,247 @@
# 1.0 release history
This document contains earlier published releases from the 1.0 line of the [current Self-hosted LiveSync release history](../../updates.md). Beta and release-candidate builds published before 1.0.0 are recorded in the [1.0 preview history](1.0-previews.md). Earlier release lines continue in the [0.25 history](0.25.md) and the [legacy history](legacy.md).
## 1.0.9
8th August, 2026
For the first time in a while, I published a release that could not be promoted to a stable release. Sorry about that! I am glad that we caught it while it was still a pre-release.
### Setup and compatibility
#### Fixed
- Multi-part settings QR codes now preserve special characters in passwords, passphrases, and other settings (PR #1083). Thank you to @calvinbui for the improvement!
- Fast Setup now sizes each finite CouchDB changes page from a one-row status probe, counts the returned result together with `pending`, and resumes from the page's opaque `last_seq` without comparing token representations. Each page uses a one-second idle timeout instead of a heartbeat, allowing CouchDB 3.2 to return its terminator after the currently available rows have been persisted.
## 1.0.8
8th August, 2026
This version was published for pre-release validation only and was not promoted to a stable release.
### Setup and compatibility
#### Fixed
- Fast Setup now sizes each finite CouchDB changes page from a one-row status probe, counts the returned result together with `pending`, and resumes from the page's opaque `last_seq` without comparing token representations. Heartbeat-enabled feeds no longer wait for future writes after the currently available rows have been persisted (#1065).
- Cancelling remote selection during a scheduled Fetch now removes the Fetch flag before restarting with file and database reflection paused, preventing the same selection dialogue from reopening on every start-up.
## 1.0.7
8th August, 2026
### Setup and compatibility
#### Fixed
- Fast Setup now completes only after the captured CouchDB changes target has been persisted. Decryption, protocol, and local write failures stop the operation without finalising an incomplete database, while transient interruptions resume from the last durable checkpoint (#1065).
## 1.0.6
6th August, 2026
I know that onboarding, and other parts which feel unclear or confusing, still need improvement. Please do report any such cases.
### Setup and compatibility
#### Fixed
- Initial setup now distinguishes an empty remote with no saved synchronisation settings from a failed remote read. New remotes can use this device's settings without an unnecessary retry; Fetch pauses on unreadable settings, while Rebuild can explicitly continue with this device's settings. Cancelling preserves the selected automatic synchronisation mode and restarts with Vault and database reflection paused (#1064). Thank you to @mateus2k2 for the follow-up report!
## 1.0.5
5th August, 2026
### Synchronisation and storage
#### Improved
- Added settings to control whether finite synchronisation operations keep the screen awake. Desktop devices now allow automatic sleep by default, while mobile devices retain screen-awake protection unless the general option is enabled (#1073).
### Interface and translation
#### Improved
- Korean translations now cover the complete current catalogue across Setup, P2P, remote configuration, diagnostics, and maintenance (PR #1075). Thank you to @motolies for the improvement!
#### Fixed
- The in-editor LiveSync status on iOS now remains below the view-header controls instead of overlapping them (PR #1067). Thank you to @Hsiii for the improvement!
## 1.0.4
5th August, 2026
### Synchronisation and storage
#### Fixed
- Testing or saving a fresh remote configuration no longer tries to access the local database while constructing a replicator, avoiding 'Local database is not ready yet' failures before local database initialisation (#1064).
### Command-line tool
#### Fixed
- Successful setup and remote-configuration commands now retain their settings changes. Other commands leave the settings file unchanged unless `--write-settings` is supplied, and temporary CLI suspension values are never written (#1070).
## 1.0.3
3rd August, 2026
### Synchronisation and storage
#### Fixed
- File consistency checks no longer read older revisions after the current Vault content matches known synchronised history, avoiding unnecessary 'Missing document content' warnings from obsolete unreadable revisions.
- Remote chunk fetching now retains chunks which were returned successfully when another chunk in the same request is unavailable, so the available content can still be processed (#771).
### Interface
#### Fixed
- The Remediation setting now displays its configured modification-time limit without raising a `HierarchyRequestError`.
## 1.0.2
31st July, 2026
I am aware that some of the Community Directory review checks have become a little more sensitive again. I will watch them for a little longer, then consider the most appropriate way to adapt.
### Synchronisation and storage
#### Improved
- Downloaded document batches retain best-effort screen-awake and lifecycle protection until every queued file has been applied to local storage, without extending the remote-activity indicator (#1031, PR #1032). Thank you to @apple-ouyang for the improvement!
#### Fixed
- Leading UTF-8 byte order marks are preserved during Vault ingestion, keeping stored content sizes consistent with file metadata and preventing persistent three-byte integrity mismatches (#1056, PR #1058).
### Interface and translation
#### Improved
- Document History now provides previous and next revision controls, reports the current revision position, and disables navigation at the oldest and newest boundaries without changing search-result navigation (#990, PR #1009). Thank you to @SeleiXi for the improvement!
- Remaining user-visible text in Setup, P2P, Customisation Sync, Global History, JSON conflict handling, and remote configuration now uses the translation catalogue (PR #1015). Thank you to @zeedif for the improvement!
- Korean translations have broader coverage and corrections for placeholders, punctuation, and established terminology (PR #1055). Thank you to @motolies for the improvement!
- Spanish translation coverage has been expanded across settings, Setup, P2P, maintenance, and newly catalogued interface text (PR #1059). Thank you to @zeedif for the improvement!
### Command-line tool
#### Fixed
- Large-buffer base64 encoding under Node.js now uses the published `octagonal-wheels` fallback when `FileReader` is unavailable, including correctly handling sliced binary views (#1036, PR #1060; [Fancy Kit PR #44](https://github.com/vrtmrz/fancy-kit/pull/44)).
## 1.0.1
29th July, 2026
I am taking this opportunity to update the experimental features as well.
This maintenance release mainly improves the robustness and maintainability of the experimental WebApp, WebPeer, and shared dialogue composition. Most plug-in users can skip it. I have reviewed the changes through CI and a real Obsidian instance, and I will validate the exact published build before merging the release commit.
### Interface
#### Improved
- Removed a custom positioning workaround from the onboarding Notice so that it follows Obsidian's standard placement and dismissal behaviour.
- WebApp now points users to **Scan local files** when automatic file observation is unavailable, instead of relying on a fixed browser-version recommendation.
## 1.0.0
27th July, 2026
The work towards 1.0 has become so substantial that I have written [an article about it](https://fancy-syncing.vrtmrz.net/blog/0036-livesync-1_0_0-en.html) (linked again here).
### Setup and compatibility
#### Improved
- An unconfigured Vault now waits for the user to start setup. Onboarding is offered through a persistent Notice and remains available from **Self-hosted LiveSync settings****Setup**.
- Setup now creates named CouchDB, Object Storage, and P2P connections. Setup URIs preserve their connection names and selections, and reserve Fetch or Rebuild before the ordinary start-up scan begins.
- Manual CouchDB setup distinguishes creating the first database from connecting another device. Onboarding requires a successful connection, while Settings can explicitly save an unverified connection and offers each server-setting correction separately.
- Compatible differences limited to the chunk hash algorithm, chunk size, or splitter version are aligned automatically by default. Existing chunks remain readable, an explicit opt-out remains available, and differences involving incompatible settings still require review.
#### Fixed
- Existing Vaults retain their effective legacy settings, including the case-insensitive file-name fallback used when an older release had no explicit case setting.
#### Security
- Fly.io setup generates CouchDB and Vault encryption secrets with cryptographically secure randomness.
- Dependency updates address excessive CPU use from crafted path patterns and `mailto:` links.
### Conflict handling and recovery
#### Improved
- **Not now** postpones repeated automatic merge dialogues while retaining the unresolved-conflict warning. Three or more live revisions are reviewed one reproducible pair at a time, completed pairs remain resolved across restart, and explicit commands can reopen a postponed conflict.
- **Inspect conflicts and file/database differences** compares the Vault with the database winner and every live conflict revision. Compact indicators show missing chunks, `Δsize`, `Δtime`, whether the Vault matches the winner, and whether conflicts remain.
- Each reported file and live revision has a compact wrench menu for comparison, applying an exact readable revision, recording an exact byte match, storing the Vault content as a child of a selected branch, retrying missing chunks without changing the tree, or explicitly discarding one selected live branch.
#### Fixed
- Automatic text and structured-data merge now uses the nearest revision actually shared by both branches. A resolution received from another device no longer recreates the same conflict merely because the Vault still contains the exact content of the removed branch.
- Edits, logical deletions, and renames made while a file remains conflicted extend the revision displayed on that device. When the relationship cannot be proved, LiveSync preserves the branches for review.
- Unreadable live revisions are preserved during automatic handling. An absent Vault file and a winning logical deletion are treated as agreement unless another live branch still requires attention.
- Garbage Collection V3 is limited to CouchDB and now protects every live conflict branch, required shared ancestry, and shared chunks. It stops when device progress cannot be verified and reports compaction failure without a contradictory success message.
### P2P and optional synchronisation features
#### Improved
- P2P and Hidden File Sync remain supported opt-in features. Customisation Sync remains a supported Advanced workflow, while Data Compression remains available but disabled by default.
- P2P controls remain outside the ordinary CouchDB experience until P2P is configured. The current status pane distinguishes announcing changes, following a peer, and persistent per-device actions.
- P2P setup and guidance now distinguish the required signalling relay from optional TURN and describe the replaceable public relay's privacy and availability limits.
- Enabling Hidden File Sync opens one progress Notice before saving the setting and reuses it until the initial scan has finished instead of stacking phase, reload, and restart messages.
#### Fixed
- First-device P2P setup can complete its signalling test without another peer online. Fetch on an additional device still requires an available source peer and a completed P2P Rebuild.
- P2P relay connections now close and are recreated reliably after settings changes and database resets.
### Interface, translation, and operations
#### Improved
- Command-palette actions now use clearer names and appear only when their feature and current context make them usable. Renamed commands retain their identifiers so that existing hotkeys continue to work.
- Setup and review dialogue text can be selected for copying or translation.
- Remote-size warnings use persistent clickable Notices. Initial uploads and Rebuild no longer ask to send every chunk in advance; ordinary replication completes the transfer.
- Obsolete controls for the plug-in trash setting and fixed chunk revisions were removed. The Change Log remains available but no longer opens automatically or tracks an unread count.
- Self-hosted LiveSync now owns its translation catalogue. Commonlib supplies canonical English to other consumers, while translation contributions can be made in the main Self-hosted LiveSync repository.
#### Fixed
- Applying an available interface translation no longer holds start-up behind an unsolicited dialogue; a persistent Notice opens the existing details on demand.
- Action buttons are arranged for narrow mobile screens, long dialogues keep their controls reachable, and persistent Notices no longer cover close controls.
### Storage and file selection
#### Fixed
- The optional Custom HTTP Handler used by Object Storage sends the correct byte range from binary request bodies and reports unsupported body types instead of silently sending an empty request.
- Broadening selectors, ignore rules, size or modification-time limits, or file-name case handling now rechecks previously received files without requiring another remote update.
- Start-up and full-inspection scans omit built-in legacy LiveSync log files and recovery flag files before comparing Vault and local-database state. Existing ignored database records remain untouched, and user-configured ignore behaviour is unchanged.
### Command-line tool
#### Fixed
- CLI Setup URI validation now uses the supported Commonlib ESM package interface.
- The non-root Docker image no longer depends on permissions inherited from the source checkout.
#### Security
- The CLI rejects detected path traversal and symbolic-link components before Vault operations.
### Validation
#### Testing
- Expanded automated Real Obsidian coverage for upgrades, two-device synchronisation, CouchDB, Object Storage, P2P, Hidden File Sync, mobile dialogues, conflict and revision recovery, failure diagnostics, and strict clean-up.
- Real CouchDB integration coverage verifies logical deletion, shared and conflict chunk retention, compaction, downstream replication, and recreation of content-addressed chunks.
- An encrypted Real Obsidian reconnect scenario replaces the remote Security Seed while one client retains the previous value, verifies that synchronisation adopts the replacement without restoring the old value, and proves a bidirectional encrypted round-trip.
- The plug-in code in this release was installed through BRAT and validated on macOS, iOS, and Android, including upgrade from 0.25.83, bidirectional synchronisation, P2P setup, conflict handling, recovery controls, mobile layouts, and start-up with existing configurations.
- Native and non-root Docker CLI scenarios cover setup, write, read, list, information, deletion, conflict resolution, and revision retrieval with the packaged Commonlib dependency.
+1 -1
View File
@@ -1,6 +1,6 @@
# Legacy release history
This history covers releases before 0.25. Later releases are recorded in the [0.25 history](0.25.md) and the [current release history](../../updates.md).
This history covers releases before 0.25. Later releases are recorded in the [0.25 history](0.25.md), the [earlier 1.0 history](1.0.md), and the [current release history](../../updates.md).
## 0.24
+1 -1
View File
@@ -1,7 +1,7 @@
{
"id": "obsidian-livesync",
"name": "Self-hosted LiveSync",
"version": "1.0.13",
"version": "1.0.14",
"minAppVersion": "1.7.2",
"description": "Community implementation of self-hosted livesync. Reflect your vault changes to some other devices immediately. Please make sure to disable other synchronize solutions to avoid content corruption or duplication.",
"author": "vorotamoroz",
+40 -41
View File
@@ -1,12 +1,12 @@
{
"name": "obsidian-livesync",
"version": "1.0.13",
"version": "1.0.14",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "obsidian-livesync",
"version": "1.0.13",
"version": "1.0.14",
"license": "MIT",
"workspaces": [
"src/apps/cli",
@@ -23,7 +23,7 @@
"@smithy/types": "^4.14.3",
"@smithy/util-retry": "^4.4.5",
"@vrtmrz/browser-ui-kit": "0.1.0",
"@vrtmrz/livesync-commonlib": "0.1.12",
"@vrtmrz/livesync-commonlib": "0.1.14",
"@vrtmrz/obsidian-plugin-kit": "0.1.3",
"@vrtmrz/ui-interactions": "0.1.2",
"diff-match-patch": "^1.0.5",
@@ -32,7 +32,7 @@
"markdown-it": "^14.2.0",
"minimatch": "^10.2.5",
"obsidian": "^1.13.1",
"octagonal-wheels": "^0.1.52",
"octagonal-wheels": "^0.1.53",
"qrcode-generator": "^1.4.4",
"xxhash-wasm-102": "npm:xxhash-wasm@^1.0.2"
},
@@ -988,7 +988,6 @@
"integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/code-frame": "^7.29.7",
"@babel/generator": "^7.29.7",
@@ -1353,7 +1352,6 @@
"integrity": "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@emnapi/wasi-threads": "1.2.2",
"tslib": "^2.4.0"
@@ -1365,7 +1363,6 @@
"integrity": "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"tslib": "^2.4.0"
}
@@ -2273,7 +2270,8 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@marijn/find-cluster-break/-/find-cluster-break-1.0.2.tgz",
"integrity": "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==",
"license": "MIT"
"license": "MIT",
"peer": true
},
"node_modules/@microsoft/eslint-plugin-sdl": {
"version": "1.1.0",
@@ -4048,7 +4046,6 @@
"integrity": "sha512-fRa09kZTgu8o71KFcDjUFuc7F+dEbZYZmkI0mg5YBTRs0yMKjYHsq/c0urDKeDb+D5qVgXOdFcuu+DZPKOITwA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"undici-types": "~7.18.0"
}
@@ -4626,7 +4623,6 @@
"integrity": "sha512-lt3kovsyHwYe00wq4D1ti0Z974fWj4NLp6siqiyEufUpyFwK9Yhi7rBhac9JL5aA0zoMrJqc4vYPZRUnI7l7nw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@bcoe/v8-coverage": "^1.0.2",
"@vitest/utils": "4.1.8",
@@ -4775,9 +4771,9 @@
}
},
"node_modules/@vrtmrz/livesync-commonlib": {
"version": "0.1.12",
"resolved": "https://registry.npmjs.org/@vrtmrz/livesync-commonlib/-/livesync-commonlib-0.1.12.tgz",
"integrity": "sha512-SYqUsMcz9241qEUqmnIwVKOPXJB4/FXjXQh8nHPmX004KkV5PUnAm4ClhNwlQzvYfpTI5IHYKej+JIndiQYXmw==",
"version": "0.1.14",
"resolved": "https://registry.npmjs.org/@vrtmrz/livesync-commonlib/-/livesync-commonlib-0.1.14.tgz",
"integrity": "sha512-ndHo1cKyEf4n9An5uovlTGbf56cpHjGARjOjSwMmzizC+qVJVEkRg7Ym94Eoa+KoheWTY614BweEtyAl9rKCfw==",
"license": "MIT",
"dependencies": {
"@aws-sdk/client-s3": "^3.808.0",
@@ -4793,7 +4789,7 @@
"idb": "^8.0.3",
"markdown-it": "^14.2.0",
"minimatch": "^10.2.5",
"octagonal-wheels": "^0.1.51",
"octagonal-wheels": "^0.1.53",
"pouchdb-adapter-http": "^9.0.0",
"pouchdb-adapter-idb": "^9.0.0",
"pouchdb-adapter-indexeddb": "^9.0.0",
@@ -5138,7 +5134,6 @@
"integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
"devOptional": true,
"license": "MIT",
"peer": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -6030,7 +6025,6 @@
}
],
"license": "MIT",
"peer": true,
"dependencies": {
"baseline-browser-mapping": "^2.10.12",
"caniuse-lite": "^1.0.30001782",
@@ -6502,7 +6496,8 @@
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz",
"integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==",
"license": "MIT"
"license": "MIT",
"peer": true
},
"node_modules/cross-spawn": {
"version": "7.0.6",
@@ -7344,7 +7339,6 @@
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"peer": true,
"bin": {
"esbuild": "bin/esbuild"
},
@@ -7476,7 +7470,6 @@
"integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.8.0",
"@eslint-community/regexpp": "^4.12.1",
@@ -10036,7 +10029,6 @@
"integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==",
"dev": true,
"license": "MIT",
"peer": true,
"bin": {
"jiti": "lib/jiti-cli.mjs"
}
@@ -11510,7 +11502,6 @@
"resolved": "https://registry.npmjs.org/obsidian/-/obsidian-1.13.1.tgz",
"integrity": "sha512-qtTEA2pmhJzhuhJqzbBFRYhpIOqvW+krDYjtFynv66KbxBbumHBlsJfWw3I4jtnK/6fZwbQhCrmmDdRwXmX56w==",
"license": "MIT",
"peer": true,
"dependencies": {
"@types/codemirror": "5.60.8",
"moment": "2.29.4"
@@ -11532,9 +11523,9 @@
"license": "MIT"
},
"node_modules/octagonal-wheels": {
"version": "0.1.52",
"resolved": "https://registry.npmjs.org/octagonal-wheels/-/octagonal-wheels-0.1.52.tgz",
"integrity": "sha512-9WJN2UveNh90Op1S07cIso1WyNrQbO/unibDLfUGnpomIcU4g6F+p8reZHW0Ed8sGzKF5qGnQp991Y9MB1TwNg==",
"version": "0.1.53",
"resolved": "https://registry.npmjs.org/octagonal-wheels/-/octagonal-wheels-0.1.53.tgz",
"integrity": "sha512-4NJsb96Sk6rJXhrTyjAY5GRIoWMFJsFp56b5ba9fV8/87ys2HCjMo0hior4F8k4ma72pLTJT7i6DvuihHxSsMA==",
"license": "MIT",
"dependencies": {
"idb": "^8.0.3"
@@ -12001,7 +11992,6 @@
"integrity": "sha512-Z+7BeeqQPRRzklHsVFP4KTGIyMxKUmfeRA4WisM6G3/XW6nwGeX6fX9qYaDa+CiUqpOkb2f6X3nar05R3kSuJQ==",
"dev": true,
"license": "Apache-2.0",
"peer": true,
"dependencies": {
"playwright-core": "1.61.0"
},
@@ -12058,7 +12048,6 @@
}
],
"license": "MIT",
"peer": true,
"dependencies": {
"nanoid": "^3.3.12",
"picocolors": "^1.1.1",
@@ -13752,7 +13741,8 @@
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.3.tgz",
"integrity": "sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ==",
"license": "MIT"
"license": "MIT",
"peer": true
},
"node_modules/sublevel-pouchdb": {
"version": "9.0.0",
@@ -13821,7 +13811,6 @@
"integrity": "sha512-w7JvrM5IFl5cmfbY0TLik9o7mjRUJmRMhOR51tBPu708Gr/MjbGs7VnJnr/B0CaXeI4vtnOh7RKxDr0cwhMdDA==",
"devOptional": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@jridgewell/remapping": "^2.3.4",
"@jridgewell/sourcemap-codec": "^1.5.0",
@@ -13887,6 +13876,21 @@
}
}
},
"node_modules/svelte-check/node_modules/picomatch": {
"version": "4.0.5",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz",
"integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true,
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/svelte-eslint-parser": {
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/svelte-eslint-parser/-/svelte-eslint-parser-1.8.0.tgz",
@@ -14060,7 +14064,6 @@
"integrity": "sha512-vzCjQO/rgUuK9sf8VJZvjqiqiHFaZLnOiimmUuOKODxWL8mm/xua7viT7aqX7dgPY60otQjUotzFMmCB4VdmqQ==",
"dev": true,
"license": "BSD-2-Clause",
"peer": true,
"dependencies": {
"@jridgewell/source-map": "^0.3.3",
"acorn": "^8.15.0",
@@ -14158,7 +14161,6 @@
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">=12"
},
@@ -14457,7 +14459,6 @@
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"dev": true,
"license": "Apache-2.0",
"peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@@ -14525,7 +14526,6 @@
"integrity": "sha512-PJ5vePq5/ognBbrIcoC5+SHO5dfpeLPzP9FpLkzWrguoYQEeeSjlJpVwOpo1JRSTEi7dRcwNy4h4dzV70PqHcg==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@typescript-eslint/scope-manager": "8.61.1",
"@typescript-eslint/types": "8.61.1",
@@ -14855,7 +14855,6 @@
"integrity": "sha512-h9bXPmJichP5fLmVQo3PyaGSDE2n3aPuomeAlVRm0JLmt4rY6zmPKd59HYI4LNW8oTK7tlTsuC7l/m7awx9Jcw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"lightningcss": "^1.32.0",
"picomatch": "^4.0.4",
@@ -14982,7 +14981,6 @@
"integrity": "sha512-flY6ScbCIt9HThs+C5HS7jvGOB560DJtk/Z15IQROTA6zEy49Nh8T/dofWTQL+n3vswqn87sbJNiuqw1SDp5Ig==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@vitest/expect": "4.1.8",
"@vitest/mocker": "4.1.8",
@@ -15097,7 +15095,8 @@
"version": "2.2.8",
"resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz",
"integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==",
"license": "MIT"
"license": "MIT",
"peer": true
},
"node_modules/wait-port": {
"version": "1.1.0",
@@ -15924,11 +15923,11 @@
},
"src/apps/cli": {
"name": "self-hosted-livesync-cli",
"version": "1.0.13-cli",
"version": "1.0.14-cli",
"dependencies": {
"chokidar": "^4.0.0",
"minimatch": "^10.2.5",
"octagonal-wheels": "^0.1.52",
"octagonal-wheels": "^0.1.53",
"pouchdb-adapter-http": "^9.0.0",
"pouchdb-adapter-leveldb": "^9.0.0",
"pouchdb-core": "^9.0.0",
@@ -15949,9 +15948,9 @@
},
"src/apps/webapp": {
"name": "livesync-webapp",
"version": "1.0.13-webapp",
"version": "1.0.14-webapp",
"dependencies": {
"octagonal-wheels": "^0.1.52"
"octagonal-wheels": "^0.1.53"
},
"devDependencies": {
"@sveltejs/vite-plugin-svelte": "^7.1.2",
@@ -15961,9 +15960,9 @@
}
},
"src/apps/webpeer": {
"version": "1.0.13-webpeer",
"version": "1.0.14-webpeer",
"dependencies": {
"octagonal-wheels": "^0.1.52"
"octagonal-wheels": "^0.1.53"
},
"devDependencies": {
"@sveltejs/vite-plugin-svelte": "^7.1.2",
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "obsidian-livesync",
"version": "1.0.13",
"version": "1.0.14",
"description": "Reflect your vault changes to some other devices immediately. Please make sure to disable other synchronize solutions to avoid content corruption or duplication.",
"main": "main.js",
"type": "module",
@@ -177,7 +177,7 @@
"@smithy/types": "^4.14.3",
"@smithy/util-retry": "^4.4.5",
"@vrtmrz/browser-ui-kit": "0.1.0",
"@vrtmrz/livesync-commonlib": "0.1.12",
"@vrtmrz/livesync-commonlib": "0.1.14",
"@vrtmrz/obsidian-plugin-kit": "0.1.3",
"@vrtmrz/ui-interactions": "0.1.2",
"diff-match-patch": "^1.0.5",
@@ -186,7 +186,7 @@
"markdown-it": "^14.2.0",
"minimatch": "^10.2.5",
"obsidian": "^1.13.1",
"octagonal-wheels": "^0.1.52",
"octagonal-wheels": "^0.1.53",
"qrcode-generator": "^1.4.4",
"xxhash-wasm-102": "npm:xxhash-wasm@^1.0.2"
},
+5 -1
View File
@@ -58,7 +58,11 @@ async function verifyRemoteState(
standardIo.writeStderr(`[Verification] Failed to connect to remote CouchDB: ${dbRet}\n`);
return false;
}
milestone = await dbRet.db.get(MILESTONE_DOCID);
try {
milestone = await dbRet.db.get(MILESTONE_DOCID);
} finally {
await dbRet.db.close();
}
} else if (settings.remoteType === REMOTE_MINIO) {
milestone = await (replicator as LiveSyncJournalReplicator).client.downloadJson("_00000000-milestone.json");
}
@@ -708,6 +708,18 @@ describe("runCommand abnormal cases", () => {
describe("mark-resolved and unlock-remote commands", () => {
it("mark-resolved without args runs on active database", async () => {
const core = createCoreMock();
const remoteDatabase = {
close: vi.fn(async () => undefined),
get: vi.fn(async () => ({
locked: false,
accepted_nodes: ["test-node-id"],
})),
};
core.services.replicator.getActiveReplicator.mockReturnValueOnce({
nodeid: "test-node-id",
initializeDatabaseForReplication: vi.fn(async () => undefined),
connectRemoteCouchDBWithSetting: vi.fn(async () => ({ db: remoteDatabase })),
});
const result = await runCommand(makeOptions("mark-resolved", []), {
...context,
core,
@@ -715,6 +727,7 @@ describe("runCommand abnormal cases", () => {
expect(result).toBe(true);
expect(core.services.replication.markResolved).toHaveBeenCalledTimes(1);
expect(core.services.control.applySettings).not.toHaveBeenCalled();
expect(remoteDatabase.close).toHaveBeenCalledOnce();
});
it("mark-resolved with remote-id temporarily activates it and runs markResolved", async () => {
+2 -2
View File
@@ -1,7 +1,7 @@
{
"name": "self-hosted-livesync-cli",
"private": true,
"version": "1.0.13-cli",
"version": "1.0.14-cli",
"main": "dist/index.cjs",
"type": "module",
"scripts": {
@@ -37,7 +37,7 @@
"dependencies": {
"chokidar": "^4.0.0",
"minimatch": "^10.2.5",
"octagonal-wheels": "^0.1.52",
"octagonal-wheels": "^0.1.53",
"pouchdb-adapter-http": "^9.0.0",
"pouchdb-adapter-leveldb": "^9.0.0",
"pouchdb-core": "^9.0.0",
+2 -2
View File
@@ -1,7 +1,7 @@
{
"name": "livesync-webapp",
"private": true,
"version": "1.0.13-webapp",
"version": "1.0.14-webapp",
"type": "module",
"description": "Browser-based Self-hosted LiveSync using FileSystem API",
"scripts": {
@@ -15,7 +15,7 @@
"test:browser": "deno test -A --no-check --frozen --config ../../../test/browser-apps/deno.json --lock ../../../test/browser-apps/deno.lock ../../../test/browser-apps/webapp/browser-smoke.test.ts"
},
"dependencies": {
"octagonal-wheels": "^0.1.52"
"octagonal-wheels": "^0.1.53"
},
"devDependencies": {
"@sveltejs/vite-plugin-svelte": "^7.1.2",
+2 -2
View File
@@ -1,7 +1,7 @@
{
"name": "webpeer",
"private": true,
"version": "1.0.13-webpeer",
"version": "1.0.14-webpeer",
"type": "module",
"scripts": {
"dev": "vite",
@@ -15,7 +15,7 @@
"test:browser": "deno test -A --no-check --frozen --config ../../../test/browser-apps/deno.json --lock ../../../test/browser-apps/deno.lock ../../../test/browser-apps/webpeer/browser-smoke.test.ts"
},
"dependencies": {
"octagonal-wheels": "^0.1.52"
"octagonal-wheels": "^0.1.53"
},
"devDependencies": {
"eslint-plugin-svelte": "^3.19.0",
@@ -95,18 +95,19 @@ function requiredEnvironment(name: "hostname" | "username" | "password"): string
describe("LocalDatabaseMaintenance Garbage Collection V3 with CouchDB", () => {
it("propagates collection safely, completes compaction, and permits content-addressed chunk recreation", async () => {
const databaseName = `livesync-gcv3-${crypto.randomUUID()}`;
const local = new PouchDB<FixtureContent>(`${databaseName}-source`, { adapter: "memory" });
const replica = new PouchDB<FixtureContent>(`${databaseName}-replica`, { adapter: "memory" });
const remote = new PouchDB<FixtureContent>(
`${requiredEnvironment("hostname").replace(/\/+$/u, "")}/${databaseName}`,
{
const createRemote = () =>
new PouchDB<FixtureContent>(`${requiredEnvironment("hostname").replace(/\/+$/u, "")}/${databaseName}`, {
adapter: "http",
auth: {
username: requiredEnvironment("username"),
password: requiredEnvironment("password"),
},
}
);
});
const local = new PouchDB<FixtureContent>(`${databaseName}-source`, { adapter: "memory" });
const replica = new PouchDB<FixtureContent>(`${databaseName}-replica`, { adapter: "memory" });
const remote = createRemote();
const maintenanceRemote = createRemote();
const closeMaintenanceRemote = vi.spyOn(maintenanceRemote, "close");
try {
await remote.info();
@@ -178,7 +179,7 @@ describe("LocalDatabaseMaintenance Garbage Collection V3 with CouchDB", () => {
},
})
),
connectRemoteCouchDBWithSetting: vi.fn(() => Promise.resolve({ db: remote })),
connectRemoteCouchDBWithSetting: vi.fn(() => Promise.resolve({ db: maintenanceRemote })),
};
const notice = vi.fn();
const maintenance = Object.create(LocalDatabaseMaintenance.prototype) as LocalDatabaseMaintenance;
@@ -201,6 +202,7 @@ describe("LocalDatabaseMaintenance Garbage Collection V3 with CouchDB", () => {
await maintenance.gcv3();
expect(closeMaintenanceRemote).toHaveBeenCalledOnce();
expect(replicationModes).toEqual(["sync", "pushOnly"]);
expect(clearHash).toHaveBeenCalledOnce();
expect(notice).toHaveBeenCalledWith("Compaction on remote database completed successfully.", "gc-compact");
@@ -251,6 +253,9 @@ describe("LocalDatabaseMaintenance Garbage Collection V3 with CouchDB", () => {
data: "recreated",
});
} finally {
if (closeMaintenanceRemote.mock.calls.length === 0) {
await maintenanceRemote.close();
}
await Promise.all([local.destroy(), replica.destroy(), remote.destroy()]);
}
}, 30_000);
@@ -747,29 +747,33 @@ Success: ${successCount}, Errored: ${errored}`;
this._notice(`Failed to connect to remote for compaction. ${remote}`, "gc-compact");
return;
}
const compactResult = await remote.db.compact({
interval: 1000,
});
// Probably no need to wait, but just in case.
let timeout = 2 * 60 * 1000; // 2 minutes
for (;;) {
const status = await remote.db.info();
if ("compact_running" in status && status?.compact_running) {
this._notice("Compaction in progress on remote database...", "gc-compact");
await delay(2000);
timeout -= 2000;
if (timeout <= 0) {
this._notice("Compaction on remote database timed out.", "gc-compact");
return;
try {
const compactResult = await remote.db.compact({
interval: 1000,
});
// Probably no need to wait, but just in case.
let timeout = 2 * 60 * 1000; // 2 minutes
for (;;) {
const status = await remote.db.info();
if ("compact_running" in status && status?.compact_running) {
this._notice("Compaction in progress on remote database...", "gc-compact");
await delay(2000);
timeout -= 2000;
if (timeout <= 0) {
this._notice("Compaction on remote database timed out.", "gc-compact");
return;
}
} else {
break;
}
} else {
break;
}
}
if (compactResult && "ok" in compactResult) {
this._notice("Compaction on remote database completed successfully.", "gc-compact");
} else {
this._notice("Compaction on remote database failed.", "gc-compact");
if (compactResult && "ok" in compactResult) {
this._notice("Compaction on remote database completed successfully.", "gc-compact");
} else {
this._notice("Compaction on remote database failed.", "gc-compact");
}
} finally {
await remote.db.close();
}
}
@@ -222,6 +222,7 @@ describe("LocalDatabaseMaintenance Garbage Collection V3", () => {
const remoteDatabase = {
compact: vi.fn(async () => ({ ok: true })),
info: vi.fn(async () => ({ compact_running: true })),
close: vi.fn(async () => undefined),
};
Object.assign(maintenance, {
core: {
@@ -243,6 +244,7 @@ describe("LocalDatabaseMaintenance Garbage Collection V3", () => {
"Compaction on remote database completed successfully.",
"gc-compact"
);
expect(remoteDatabase.close).toHaveBeenCalledOnce();
});
it.each([
+22 -18
View File
@@ -187,27 +187,31 @@ Even if you choose to clean up, you will see this option again if you exit Obsid
return false;
}
await purgeUnreferencedChunks(this.localDatabase.localDatabase, false);
this.localDatabase.clearCaches();
// Perform the synchronisation once.
const replicated = await this.services.replicator.runFiniteReplicationActivity(
() => this.core.replicator.openReplication(this.settings, false, showMessage, true),
{ label: "replication" }
);
if (replicated) {
await balanceChunkPurgedDBs(this.localDatabase.localDatabase, remoteDB.db);
try {
await purgeUnreferencedChunks(this.localDatabase.localDatabase, false);
this.localDatabase.clearCaches();
await this.services.replicator.getActiveReplicator()?.markRemoteResolved(this.settings);
Logger(
"The local database has been cleaned up.",
showMessage ? LOG_LEVEL_NOTICE : LOG_LEVEL_INFO
);
} else {
Logger(
"Replication has been cancelled. Please try it again.",
showMessage ? LOG_LEVEL_NOTICE : LOG_LEVEL_INFO
// Perform the synchronisation once.
const replicated = await this.services.replicator.runFiniteReplicationActivity(
() => this.core.replicator.openReplication(this.settings, false, showMessage, true),
{ label: "replication" }
);
if (replicated) {
await balanceChunkPurgedDBs(this.localDatabase.localDatabase, remoteDB.db);
await purgeUnreferencedChunks(this.localDatabase.localDatabase, false);
this.localDatabase.clearCaches();
await this.services.replicator.getActiveReplicator()?.markRemoteResolved(this.settings);
Logger(
"The local database has been cleaned up.",
showMessage ? LOG_LEVEL_NOTICE : LOG_LEVEL_INFO
);
} else {
Logger(
"Replication has been cancelled. Please try it again.",
showMessage ? LOG_LEVEL_NOTICE : LOG_LEVEL_INFO
);
}
} finally {
await remoteDB.db.close();
}
},
{ label: "database-cleanup" }
@@ -128,8 +128,11 @@ describe("compatibility: cleaned-remote reconciliation for IndexedDB clients", (
});
const runFiniteReplicationActivity = vi.fn(async (task: () => unknown) => await task());
const openReplication = vi.fn(async () => true);
const remoteDatabase = {
close: vi.fn(async () => undefined),
};
const activeReplicator = Object.assign(new LiveSyncCouchDBReplicator({} as any), {
connectRemoteCouchDBWithSetting: vi.fn(async () => ({ db: {} })),
connectRemoteCouchDBWithSetting: vi.fn(async () => ({ db: remoteDatabase })),
markRemoteResolved: vi.fn(async () => undefined),
});
const services = {
@@ -177,5 +180,9 @@ describe("compatibility: cleaned-remote reconciliation for IndexedDB clients", (
expect(openReplication).toHaveBeenCalledOnce();
expect(openReplication.mock.invocationCallOrder[0]).toBeLessThan(activityFinished.mock.invocationCallOrder[0]);
expect(chunkMocks.balanceChunkPurgedDBs).toHaveBeenCalledOnce();
expect(remoteDatabase.close).toHaveBeenCalledOnce();
expect(remoteDatabase.close.mock.invocationCallOrder[0]).toBeLessThan(
activityFinished.mock.invocationCallOrder[0]
);
});
});
@@ -547,7 +547,8 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
if (typeof db === "string") {
Logger($msg("obsidianLiveSyncSettingTab.logCheckPassphraseFailed", { db }), LOG_LEVEL_NOTICE);
return false;
} else {
}
try {
if (await checkSyncInfo(db.db)) {
// Logger($msg("obsidianLiveSyncSettingTab.logDatabaseConnected"), LOG_LEVEL_NOTICE);
return true;
@@ -555,6 +556,8 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
Logger($msg("obsidianLiveSyncSettingTab.logPassphraseNotCompatible"), LOG_LEVEL_NOTICE);
return false;
}
} finally {
await db.db.close();
}
};
isPassphraseValid = async () => {
@@ -0,0 +1,75 @@
import { describe, expect, it, vi } from "vitest";
import { DEFAULT_SETTINGS, REMOTE_COUCHDB } from "@vrtmrz/livesync-commonlib/compat/common/types";
const negotiationMocks = vi.hoisted(() => ({
checkSyncInfo: vi.fn(async () => true),
}));
vi.mock("@/deps.ts", () => ({
App: class {},
Component: class {},
PluginSettingTab: class {},
}));
vi.mock("@/main.ts", () => ({ default: class {} }));
vi.mock("@/common/events.ts", () => ({
EVENT_REQUEST_RELOAD_SETTING_TAB: "request-reload-setting-tab",
eventHub: { onEvent: vi.fn() },
}));
vi.mock("@vrtmrz/livesync-commonlib/compat/pouchdb/negotiation", () => negotiationMocks);
vi.mock("@vrtmrz/livesync-commonlib/compat/replication/couchdb/LiveSyncReplicator", () => ({
LiveSyncCouchDBReplicator: class {},
}));
vi.mock("./LiveSyncSetting.ts", () => ({ LiveSyncSetting: class {} }));
vi.mock("./SettingPane.ts", () => ({
enableOnly: vi.fn(() => vi.fn()),
setLevelClass: vi.fn(),
setStyle: vi.fn(),
visibleOnly: vi.fn(() => vi.fn()),
}));
vi.mock("./PaneChangeLog.ts", () => ({ paneChangeLog: vi.fn() }));
vi.mock("./PaneSetup.ts", () => ({ paneSetup: vi.fn() }));
vi.mock("./PaneGeneral.ts", () => ({ paneGeneral: vi.fn() }));
vi.mock("./PaneRemoteConfig.ts", () => ({ paneRemoteConfig: vi.fn() }));
vi.mock("./PaneSelector.ts", () => ({ paneSelector: vi.fn() }));
vi.mock("./PaneSyncSettings.ts", () => ({ paneSyncSettings: vi.fn() }));
vi.mock("./PaneCustomisationSync.ts", () => ({ paneCustomisationSync: vi.fn() }));
vi.mock("./PaneHatch.ts", () => ({ paneHatch: vi.fn() }));
vi.mock("./PaneAdvanced.ts", () => ({ paneAdvanced: vi.fn() }));
vi.mock("./PanePowerUsers.ts", () => ({ panePowerUsers: vi.fn() }));
vi.mock("./PanePatches.ts", () => ({ panePatches: vi.fn() }));
vi.mock("./PaneMaintenance.ts", () => ({ paneMaintenance: vi.fn() }));
import { LiveSyncCouchDBReplicator } from "@vrtmrz/livesync-commonlib/compat/replication/couchdb/LiveSyncReplicator";
import { ObsidianLiveSyncSettingTab } from "./ObsidianLiveSyncSettingTab";
describe("ObsidianLiveSyncSettingTab passphrase verification", () => {
it("closes the finite remote connection after checking synchronisation information", async () => {
const remoteDatabase = {
close: vi.fn(async () => undefined),
};
const replicator = Object.assign(new LiveSyncCouchDBReplicator({} as never), {
connectRemoteCouchDBWithSetting: vi.fn(async () => ({ db: remoteDatabase })),
});
const plugin = {
app: {},
core: {
services: {
API: { isMobile: vi.fn(() => false) },
replicator: { getNewReplicator: vi.fn(() => replicator) },
},
},
};
const tab = new ObsidianLiveSyncSettingTab({} as never, plugin as never);
Object.assign(tab, {
_editingSettings: {
...DEFAULT_SETTINGS,
remoteType: REMOTE_COUCHDB,
},
});
await expect(tab.checkWorkingPassphrase()).resolves.toBe(true);
expect(negotiationMocks.checkSyncInfo).toHaveBeenCalledWith(remoteDatabase);
expect(remoteDatabase.close).toHaveBeenCalledOnce();
});
});
@@ -8,7 +8,7 @@ export type CouchDBConnectionProbeResult = { ok: true } | { ok: false; reason: s
type CouchDBConnectionResult =
| string
| {
db: unknown;
db: { close(): Promise<void> };
info: unknown;
};
@@ -50,7 +50,11 @@ export async function probeCouchDBConnection(
if (typeof result === "string") {
return { ok: false, reason: result };
}
return { ok: true };
try {
return { ok: true };
} finally {
await result.db.close();
}
}
export function isValidCouchDBServerURL(value: string): boolean {
@@ -14,8 +14,9 @@ describe("CouchDB setup connection policy", () => {
] as const)(
"%s can %s without changing the Commonlib connection contract",
async (createIfMissing, _description) => {
const close = vi.fn(async () => undefined);
const connectRemoteCouchDBWithSetting = vi.fn(async () => ({
db: {},
db: { close },
info: { db_name: "notes" },
}));
const replicator = {
@@ -27,6 +28,7 @@ describe("CouchDB setup connection policy", () => {
await expect(probeCouchDBConnection(replicator, settings, createIfMissing)).resolves.toEqual({ ok: true });
expect(connectRemoteCouchDBWithSetting).toHaveBeenCalledWith(settings, false, createIfMissing, false);
expect(replicator.tryConnectRemote).not.toHaveBeenCalled();
expect(close).toHaveBeenCalledOnce();
}
);
+19 -245
View File
@@ -8,10 +8,24 @@ None of this would have been possible without your issue reports, pull requests,
This will call for your help once again. I would be very grateful for your co-operation as we build a sounder foundation for the project and its future development.
Earlier releases remain available in the 0.25 release history and the legacy release history.
Earlier releases remain available in the 1.0 release history, the 1.0 preview history, the 0.25 release history, and the legacy release history.
## Unreleased
## 1.0.14
14th August, 2026
Thank you for your patience. At last, it looks as though we can clear some of the Community Review warnings.
### Synchronisation and storage
#### Fixed
- CouchDB operations which run to completion now close their temporary remote database connections after use across both Commonlib and LiveSync, including Setup Wizard and settings probes, command-line milestone verification, database maintenance, Security Seed refreshes, status queries, and retry and error paths (Commonlib PR #112).
- This covers every temporary connection currently identified as a possible contributor to the long-running resource growth tracked in #1034. Validation over extended sessions is continuing.
- Thank you to @apple-ouyang for the contribution!
## 1.0.13
13th August, 2026
@@ -22,6 +36,10 @@ Earlier releases remain available in the 0.25 release history and the legacy rel
- **Inspect conflicts and file/database differences** now reports local Metadata whose stored document ID does not match the ID derived from its recorded path. Ordinary scans leave unresolved entries and their corresponding Vault paths unchanged, while allowing consistently addressed Metadata for the same logical path to proceed normally.
- When one live, unconflicted entry has an unambiguous target, its wrench menu can repair that one local Metadata document after separate confirmation. The target is written and verified before the mismatched source ID is removed; ambiguous or otherwise unsafe entries remain read-only.
#### Fixed
- Fast Fetch now writes deletion tombstones to the local database without attempting to decrypt them. A tombstone has no encrypted payload, and decryption previously aborted the whole fetch at the first deleted document. New devices could not complete their initial sync on vaults that contain old deletions (Commonlib PR #108).
- Thank you to @KennethLloyd for the contribution!
## 1.0.12
@@ -62,247 +80,3 @@ Earlier releases remain available in the 0.25 release history and the legacy rel
#### Fixed
- Fast Setup now sends configured CouchDB custom headers with every changes-feed request, allowing reverse proxies such as Cloudflare Access to authenticate initial setup in the same way as ordinary synchronisation ([Commonlib PR #82](https://github.com/vrtmrz/livesync-commonlib/pull/82)). Thank you to @nimula for the contribution!
## 1.0.9
8th August, 2026
For the first time in a while, I published a release that could not be promoted to a stable release. Sorry about that! I am glad that we caught it while it was still a pre-release.
### Setup and compatibility
#### Fixed
- Multi-part settings QR codes now preserve special characters in passwords, passphrases, and other settings (PR #1083). Thank you to @calvinbui for the improvement!
- Fast Setup now sizes each finite CouchDB changes page from a one-row status probe, counts the returned result together with `pending`, and resumes from the page's opaque `last_seq` without comparing token representations. Each page uses a one-second idle timeout instead of a heartbeat, allowing CouchDB 3.2 to return its terminator after the currently available rows have been persisted.
## 1.0.8
8th August, 2026
This version was published for pre-release validation only and was not promoted to a stable release.
### Setup and compatibility
#### Fixed
- Fast Setup now sizes each finite CouchDB changes page from a one-row status probe, counts the returned result together with `pending`, and resumes from the page's opaque `last_seq` without comparing token representations. Heartbeat-enabled feeds no longer wait for future writes after the currently available rows have been persisted (#1065).
- Cancelling remote selection during a scheduled Fetch now removes the Fetch flag before restarting with file and database reflection paused, preventing the same selection dialogue from reopening on every start-up.
## 1.0.7
8th August, 2026
### Setup and compatibility
#### Fixed
- Fast Setup now completes only after the captured CouchDB changes target has been persisted. Decryption, protocol, and local write failures stop the operation without finalising an incomplete database, while transient interruptions resume from the last durable checkpoint (#1065).
## 1.0.6
6th August, 2026
I know that onboarding, and other parts which feel unclear or confusing, still need improvement. Please do report any such cases.
### Setup and compatibility
#### Fixed
- Initial setup now distinguishes an empty remote with no saved synchronisation settings from a failed remote read. New remotes can use this device's settings without an unnecessary retry; Fetch pauses on unreadable settings, while Rebuild can explicitly continue with this device's settings. Cancelling preserves the selected automatic synchronisation mode and restarts with Vault and database reflection paused (#1064). Thank you to @mateus2k2 for the follow-up report!
## 1.0.5
5th August, 2026
### Synchronisation and storage
#### Improved
- Added settings to control whether finite synchronisation operations keep the screen awake. Desktop devices now allow automatic sleep by default, while mobile devices retain screen-awake protection unless the general option is enabled (#1073).
### Interface and translation
#### Improved
- Korean translations now cover the complete current catalogue across Setup, P2P, remote configuration, diagnostics, and maintenance (PR #1075). Thank you to @motolies for the improvement!
#### Fixed
- The in-editor LiveSync status on iOS now remains below the view-header controls instead of overlapping them (PR #1067). Thank you to @Hsiii for the improvement!
## 1.0.4
5th August, 2026
### Synchronisation and storage
#### Fixed
- Testing or saving a fresh remote configuration no longer tries to access the local database while constructing a replicator, avoiding 'Local database is not ready yet' failures before local database initialisation (#1064).
### Command-line tool
#### Fixed
- Successful setup and remote-configuration commands now retain their settings changes. Other commands leave the settings file unchanged unless `--write-settings` is supplied, and temporary CLI suspension values are never written (#1070).
## 1.0.3
3rd August, 2026
### Synchronisation and storage
#### Fixed
- File consistency checks no longer read older revisions after the current Vault content matches known synchronised history, avoiding unnecessary 'Missing document content' warnings from obsolete unreadable revisions.
- Remote chunk fetching now retains chunks which were returned successfully when another chunk in the same request is unavailable, so the available content can still be processed (#771).
### Interface
#### Fixed
- The Remediation setting now displays its configured modification-time limit without raising a `HierarchyRequestError`.
## 1.0.2
31st July, 2026
I am aware that some of the Community Directory review checks have become a little more sensitive again. I will watch them for a little longer, then consider the most appropriate way to adapt.
### Synchronisation and storage
#### Improved
- Downloaded document batches retain best-effort screen-awake and lifecycle protection until every queued file has been applied to local storage, without extending the remote-activity indicator (#1031, PR #1032). Thank you to @apple-ouyang for the improvement!
#### Fixed
- Leading UTF-8 byte order marks are preserved during Vault ingestion, keeping stored content sizes consistent with file metadata and preventing persistent three-byte integrity mismatches (#1056, PR #1058).
### Interface and translation
#### Improved
- Document History now provides previous and next revision controls, reports the current revision position, and disables navigation at the oldest and newest boundaries without changing search-result navigation (#990, PR #1009). Thank you to @SeleiXi for the improvement!
- Remaining user-visible text in Setup, P2P, Customisation Sync, Global History, JSON conflict handling, and remote configuration now uses the translation catalogue (PR #1015). Thank you to @zeedif for the improvement!
- Korean translations have broader coverage and corrections for placeholders, punctuation, and established terminology (PR #1055). Thank you to @motolies for the improvement!
- Spanish translation coverage has been expanded across settings, Setup, P2P, maintenance, and newly catalogued interface text (PR #1059). Thank you to @zeedif for the improvement!
### Command-line tool
#### Fixed
- Large-buffer base64 encoding under Node.js now uses the published `octagonal-wheels` fallback when `FileReader` is unavailable, including correctly handling sliced binary views (#1036, PR #1060; [Fancy Kit PR #44](https://github.com/vrtmrz/fancy-kit/pull/44)).
## 1.0.1
29th July, 2026
I am taking this opportunity to update the experimental features as well.
This maintenance release mainly improves the robustness and maintainability of the experimental WebApp, WebPeer, and shared dialogue composition. Most plug-in users can skip it. I have reviewed the changes through CI and a real Obsidian instance, and I will validate the exact published build before merging the release commit.
### Interface
#### Improved
- Removed a custom positioning workaround from the onboarding Notice so that it follows Obsidian's standard placement and dismissal behaviour.
- WebApp now points users to **Scan local files** when automatic file observation is unavailable, instead of relying on a fixed browser-version recommendation.
## 1.0.0
27th July, 2026
The work towards 1.0 has become so substantial that I have written [an article about it](https://fancy-syncing.vrtmrz.net/blog/0036-livesync-1_0_0-en.html) (linked again here).
### Setup and compatibility
#### Improved
- An unconfigured Vault now waits for the user to start setup. Onboarding is offered through a persistent Notice and remains available from **Self-hosted LiveSync settings****Setup**.
- Setup now creates named CouchDB, Object Storage, and P2P connections. Setup URIs preserve their connection names and selections, and reserve Fetch or Rebuild before the ordinary start-up scan begins.
- Manual CouchDB setup distinguishes creating the first database from connecting another device. Onboarding requires a successful connection, while Settings can explicitly save an unverified connection and offers each server-setting correction separately.
- Compatible differences limited to the chunk hash algorithm, chunk size, or splitter version are aligned automatically by default. Existing chunks remain readable, an explicit opt-out remains available, and differences involving incompatible settings still require review.
#### Fixed
- Existing Vaults retain their effective legacy settings, including the case-insensitive file-name fallback used when an older release had no explicit case setting.
#### Security
- Fly.io setup generates CouchDB and Vault encryption secrets with cryptographically secure randomness.
- Dependency updates address excessive CPU use from crafted path patterns and `mailto:` links.
### Conflict handling and recovery
#### Improved
- **Not now** postpones repeated automatic merge dialogues while retaining the unresolved-conflict warning. Three or more live revisions are reviewed one reproducible pair at a time, completed pairs remain resolved across restart, and explicit commands can reopen a postponed conflict.
- **Inspect conflicts and file/database differences** compares the Vault with the database winner and every live conflict revision. Compact indicators show missing chunks, `Δsize`, `Δtime`, whether the Vault matches the winner, and whether conflicts remain.
- Each reported file and live revision has a compact wrench menu for comparison, applying an exact readable revision, recording an exact byte match, storing the Vault content as a child of a selected branch, retrying missing chunks without changing the tree, or explicitly discarding one selected live branch.
#### Fixed
- Automatic text and structured-data merge now uses the nearest revision actually shared by both branches. A resolution received from another device no longer recreates the same conflict merely because the Vault still contains the exact content of the removed branch.
- Edits, logical deletions, and renames made while a file remains conflicted extend the revision displayed on that device. When the relationship cannot be proved, LiveSync preserves the branches for review.
- Unreadable live revisions are preserved during automatic handling. An absent Vault file and a winning logical deletion are treated as agreement unless another live branch still requires attention.
- Garbage Collection V3 is limited to CouchDB and now protects every live conflict branch, required shared ancestry, and shared chunks. It stops when device progress cannot be verified and reports compaction failure without a contradictory success message.
### P2P and optional synchronisation features
#### Improved
- P2P and Hidden File Sync remain supported opt-in features. Customisation Sync remains a supported Advanced workflow, while Data Compression remains available but disabled by default.
- P2P controls remain outside the ordinary CouchDB experience until P2P is configured. The current status pane distinguishes announcing changes, following a peer, and persistent per-device actions.
- P2P setup and guidance now distinguish the required signalling relay from optional TURN and describe the replaceable public relay's privacy and availability limits.
- Enabling Hidden File Sync opens one progress Notice before saving the setting and reuses it until the initial scan has finished instead of stacking phase, reload, and restart messages.
#### Fixed
- First-device P2P setup can complete its signalling test without another peer online. Fetch on an additional device still requires an available source peer and a completed P2P Rebuild.
- P2P relay connections now close and are recreated reliably after settings changes and database resets.
### Interface, translation, and operations
#### Improved
- Command-palette actions now use clearer names and appear only when their feature and current context make them usable. Renamed commands retain their identifiers so that existing hotkeys continue to work.
- Setup and review dialogue text can be selected for copying or translation.
- Remote-size warnings use persistent clickable Notices. Initial uploads and Rebuild no longer ask to send every chunk in advance; ordinary replication completes the transfer.
- Obsolete controls for the plug-in trash setting and fixed chunk revisions were removed. The Change Log remains available but no longer opens automatically or tracks an unread count.
- Self-hosted LiveSync now owns its translation catalogue. Commonlib supplies canonical English to other consumers, while translation contributions can be made in the main Self-hosted LiveSync repository.
#### Fixed
- Applying an available interface translation no longer holds start-up behind an unsolicited dialogue; a persistent Notice opens the existing details on demand.
- Action buttons are arranged for narrow mobile screens, long dialogues keep their controls reachable, and persistent Notices no longer cover close controls.
### Storage and file selection
#### Fixed
- The optional Custom HTTP Handler used by Object Storage sends the correct byte range from binary request bodies and reports unsupported body types instead of silently sending an empty request.
- Broadening selectors, ignore rules, size or modification-time limits, or file-name case handling now rechecks previously received files without requiring another remote update.
- Start-up and full-inspection scans omit built-in legacy LiveSync log files and recovery flag files before comparing Vault and local-database state. Existing ignored database records remain untouched, and user-configured ignore behaviour is unchanged.
### Command-line tool
#### Fixed
- CLI Setup URI validation now uses the supported Commonlib ESM package interface.
- The non-root Docker image no longer depends on permissions inherited from the source checkout.
#### Security
- The CLI rejects detected path traversal and symbolic-link components before Vault operations.
### Validation
#### Testing
- Expanded automated Real Obsidian coverage for upgrades, two-device synchronisation, CouchDB, Object Storage, P2P, Hidden File Sync, mobile dialogues, conflict and revision recovery, failure diagnostics, and strict clean-up.
- Real CouchDB integration coverage verifies logical deletion, shared and conflict chunk retention, compaction, downstream replication, and recreation of content-addressed chunks.
- An encrypted Real Obsidian reconnect scenario replaces the remote Security Seed while one client retains the previous value, verifies that synchronisation adopts the replacement without restoring the old value, and proves a bidirectional encrypted round-trip.
- The plug-in code in this release was installed through BRAT and validated on macOS, iOS, and Android, including upgrade from 0.25.83, bidirectional synchronisation, P2P setup, conflict handling, recovery controls, mobile layouts, and start-up with existing configurations.
- Native and non-root Docker CLI scenarios cover setup, write, read, list, information, deletion, conflict resolution, and revision retrieval with the packaged Commonlib dependency.
+1
View File
@@ -3,6 +3,7 @@
The release history is now kept as one chronological sequence across smaller files:
- [Current 1.x releases](updates.md)
- [Earlier 1.0 releases](docs/releases/1.0.md)
- [1.0 beta and release-candidate history](docs/releases/1.0-previews.md)
- [0.25 releases](docs/releases/0.25.md)
- [Releases before 0.25](docs/releases/legacy.md)
+2 -1
View File
@@ -25,5 +25,6 @@
"1.0.10": "1.7.2",
"1.0.11": "1.7.2",
"1.0.12": "1.7.2",
"1.0.13": "1.7.2"
"1.0.13": "1.7.2",
"1.0.14": "1.7.2"
}