Merge pull request #1310 from vrtmrz/1_0_44

Releasing 1.0.44
This commit is contained in:
vorotamoroz authored and GitHub committed 2026-11-02 00:11:20 +09:00
commit 03263a3888
21 files changed
+115 -95

No files matched your search

+1 -1
View File
@@ -22,7 +22,7 @@ Do not switch transport, enable P2P, or run Garbage Collection as a substitute f
This procedure addresses [issue #1303](https://github.com/vrtmrz/obsidian-livesync/issues/1303): an older Journal receiver can truncate a Chunk at a raw unit separator (`U+001F`) within its data. This can damage a received text file, including a plug-in's `main.js`, while the original batch remains intact in the bucket.
The receiver correction is available in Commonlib 0.2.3 and is included in the unreleased Self-hosted LiveSync build. Use a build containing that correction on every participating client before recovery; versions 1.0.42 and 1.0.43 still contain the defect. Updating alone does not repair Chunks already stored locally. Resetting only the Journal received history is also insufficient because ordinary reception skips existing Chunk IDs.
The receiver correction is available in Commonlib 0.2.3 and is included in Self-hosted LiveSync 1.0.44. Use a build containing that correction on every participating client before recovery; versions 1.0.42 and 1.0.43 still contain the defect. Updating alone does not repair Chunks already stored locally. Resetting only the Journal received history is also insufficient because ordinary reception skips existing Chunk IDs.
### Preserve the intact source
+25
View File
@@ -2,6 +2,31 @@
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.38
17th October, 2026
### Synchronisation
#### Added
- We can opt into NFC path identity for CouchDB synchronisation between the CLI and Obsidian. This prevents decomposed or mixed Unicode file names from becoming inaccessible through a receiving device's normalised path. (#1259)
- Existing Vaults retain their current identity rules. Migration requires updated clients, a Rebuild from a complete authoritative Vault, and a Fetch on the other devices.
- Reconstruction stops when the supported host file listing or local Metadata exposes ambiguous files or parent folders under NFC and the configured case handling. The CLI retains existing physical file names. P2P and Object Storage do not support the new mode.
#### Fixed
- We can synchronise file deletions and renames when **Do not keep metadata of deleted files.** is enabled.
- A file restored during the final deletion check remains in the receiving Vault.
- We can resolve configuration mismatches when **Encrypt internal file Properties** is enabled but its encryption prerequisites are inactive. (#1180)
- Compatible chunk settings can be aligned automatically, or reviewed through `Sync now`. Applying those settings preserves our inactive encryption preference.
- We can receive synchronised updates to unchanged files even after the local database has discarded their recorded revision, avoiding unnecessary conflicts. (#1207)
- Saved content evidence survives revision-history pruning. Genuine local edits remain protected, and older records are upgraded only after their content is verified.
### Maintenance
- The Review Harness can compare SHA-256 and xxHash64 file-content hashing on the current device, with fixed inputs from 4 KiB to 50 MiB and separate initialisation and warm-up measurements.
## 1.0.37
16th October, 2026
+1 -1
View File
@@ -1,7 +1,7 @@
{
"id": "obsidian-livesync",
"name": "Self-hosted LiveSync",
"version": "1.0.43",
"version": "1.0.44",
"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",
+5 -5
View File
@@ -1,12 +1,12 @@
{
"name": "obsidian-livesync",
"version": "1.0.43",
"version": "1.0.44",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "obsidian-livesync",
"version": "1.0.43",
"version": "1.0.44",
"license": "MIT",
"workspaces": [
"src/apps/cli",
@@ -11763,7 +11763,7 @@
},
"src/apps/cli": {
"name": "self-hosted-livesync-cli",
"version": "1.0.43-cli",
"version": "1.0.44-cli",
"dependencies": {
"@aws-crypto/sha256-js": "5.2.0",
"chokidar": "^4.0.0",
@@ -11789,7 +11789,7 @@
},
"src/apps/webapp": {
"name": "livesync-webapp",
"version": "1.0.43-webapp",
"version": "1.0.44-webapp",
"dependencies": {
"octagonal-wheels": "0.1.56"
},
@@ -11801,7 +11801,7 @@
}
},
"src/apps/webpeer": {
"version": "1.0.43-webpeer",
"version": "1.0.44-webpeer",
"dependencies": {
"octagonal-wheels": "0.1.56"
},
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "obsidian-livesync",
"version": "1.0.43",
"version": "1.0.44",
"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",
+2 -2
View File
@@ -3,14 +3,14 @@ import type {
KeyValueDatabaseFactory,
} from "@vrtmrz/livesync-commonlib/compat/interfaces/KeyValueDatabase";
import { deleteDB, openDB, type IDBPDatabase } from "idb";
import { serialized } from "octagonal-wheels/concurrency/lock";
import { withLock } from "octagonal-wheels/concurrency/lock";
/** Creates an application-owned IndexedDB key-value factory for browser runtimes. */
export function createBrowserKeyValueDatabaseFactory(): KeyValueDatabaseFactory {
const cache = new Map<string, BrowserKeyValueDatabase>();
return async (databaseKey) =>
await serialized(`OpenBrowserKeyValueDatabase-${databaseKey}`, async () => {
await withLock(`OpenBrowserKeyValueDatabase-${databaseKey}`, async () => {
const cached = cache.get(databaseKey);
if (cached && !cached.isDestroyed) {
return cached;
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "self-hosted-livesync-cli",
"private": true,
"version": "1.0.43-cli",
"version": "1.0.44-cli",
"main": "dist/index.cjs",
"type": "module",
"scripts": {
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "livesync-webapp",
"private": true,
"version": "1.0.43-webapp",
"version": "1.0.44-webapp",
"type": "module",
"description": "Browser-based Self-hosted LiveSync using FileSystem API",
"scripts": {
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "webpeer",
"private": true,
"version": "1.0.43-webpeer",
"version": "1.0.44-webpeer",
"type": "module",
"scripts": {
"dev": "vite",
+2 -2
View File
@@ -1,6 +1,6 @@
import { deleteDB, type IDBPDatabase, openDB } from "idb";
import type { KeyValueDatabase } from "@vrtmrz/livesync-commonlib/compat/interfaces/KeyValueDatabase";
import { serialized } from "octagonal-wheels/concurrency/lock";
import { withLock } from "octagonal-wheels/concurrency/lock";
import { Logger } from "octagonal-wheels/common/logger";
const databaseCache: { [key: string]: IDBPDatabase<unknown> } = {};
export { OpenKeyValueDatabase } from "./KeyValueDBv2.ts";
@@ -13,7 +13,7 @@ export const _OpenKeyValueDatabase = async (dbKey: string): Promise<KeyValueData
const storeKey = dbKey;
let db: IDBPDatabase<unknown> | null = null;
const _openDB = () => {
return serialized("keyvaluedb-" + dbKey, async () => {
return withLock("keyvaluedb-" + dbKey, async () => {
const dbInstance = await openDB(dbKey, 1, {
upgrade(db, _oldVersion, _newVersion, _transaction, _event) {
return db.createObjectStore(storeKey);
+2 -2
View File
@@ -1,12 +1,12 @@
import { LOG_LEVEL_VERBOSE, Logger } from "@vrtmrz/livesync-commonlib/compat/common/logger";
import type { KeyValueDatabase } from "@vrtmrz/livesync-commonlib/compat/interfaces/KeyValueDatabase";
import { deleteDB, openDB, type IDBPDatabase } from "idb";
import { serialized } from "octagonal-wheels/concurrency/lock";
import { withLock } from "octagonal-wheels/concurrency/lock";
const databaseCache = new Map<string, IDBKeyValueDatabase>();
export async function OpenKeyValueDatabase(dbKey: string): Promise<KeyValueDatabase> {
return await serialized(`OpenKeyValueDatabase-${dbKey}`, async () => {
return await withLock(`OpenKeyValueDatabase-${dbKey}`, async () => {
const cachedDB = databaseCache.get(dbKey);
if (cachedDB) {
if (!cachedDB.isDestroyed) {
+6 -6
View File
@@ -49,7 +49,7 @@ import {
decodeBinary,
readString,
} from "@vrtmrz/livesync-commonlib/compat/string_and_binary/convert";
import { serialized, shareRunningResult } from "octagonal-wheels/concurrency/lock";
import { withLock, shareRunningResult } from "octagonal-wheels/concurrency/lock";
import { LiveSyncCommands } from "@/features/LiveSyncCommands.ts";
import { stripAllPrefixes } from "@vrtmrz/livesync-commonlib/compat/string_and_binary/path";
import { cancelTask, EVEN, isCustomisationSyncMetadata, isPluginMetadata, scheduleTask } from "@/common/utils.ts";
@@ -752,7 +752,7 @@ export class ConfigSync extends LiveSyncCommands {
// const unifiedFilenameWithKey = this.filenameWithUnifiedKey(updatedDocumentPath);
const { pathV1 } = this.parseUnifiedPath(unifiedFilenameWithKey);
await serialized(`config:catalogue:${pathV1}`, async () => {
await withLock(`config:catalogue:${pathV1}`, async () => {
if (!isCurrent()) return;
const oldEntry = this.pluginList.find((e) => e.documentPath == pathV1);
let entry: PluginDataExDisplayV2 | undefined = undefined;
@@ -925,7 +925,7 @@ export class ConfigSync extends LiveSyncCommands {
path = path.split("%")[1] as FilePath;
}
if (fileA.path.endsWith(".json")) {
return serialized(
return withLock(
"config:merge-data",
() =>
new Promise<boolean>((res) => {
@@ -1276,7 +1276,7 @@ export class ConfigSync extends LiveSyncCommands {
requireCompletion: boolean
) {
const vf = this.filenameWithUnifiedKey(path, term);
return await serialized(`plugin-${vf}`, async () => {
return await withLock(`plugin-${vf}`, async () => {
const prefixedFileName = vf;
const id = await this.path2id(prefixedFileName);
@@ -1379,7 +1379,7 @@ export class ConfigSync extends LiveSyncCommands {
const vf = this.filenameToUnifiedKey(path, term);
// console.warn(`Storing ${path} to ${bareVF} :--> ${keyedVF}`);
return await serialized(`plugin-${vf}`, async () => {
return await withLock(`plugin-${vf}`, async () => {
const category = this.getFileCategory(path);
let mtime = 0;
let fileTargets = [] as FilePath[];
@@ -1683,7 +1683,7 @@ export class ConfigSync extends LiveSyncCommands {
) {
// const id = await this.path2id(prefixedFileName);
const mtime = new Date().getTime();
return await serialized("file-x-" + prefixedFileName, async () => {
return await withLock("file-x-" + prefixedFileName, async () => {
try {
const old = (await this.localDatabase.getDBEntryMeta(prefixedFileName, undefined, false)) as
| InternalFileEntry
@@ -40,7 +40,7 @@ import {
displayRev,
} from "@/common/utils.ts";
import { PeriodicProcessor } from "@/common/PeriodicProcessor.ts";
import { serialized, skipIfDuplicated } from "octagonal-wheels/concurrency/lock";
import { withLock, skipIfDuplicated } from "octagonal-wheels/concurrency/lock";
import { JsonResolveModal } from "@/features/HiddenFileCommon/JsonResolveModal.ts";
import { LiveSyncCommands } from "@/features/LiveSyncCommands.ts";
import { addPrefix, stripAllPrefixes } from "@vrtmrz/livesync-commonlib/compat/string_and_binary/path";
@@ -547,7 +547,7 @@ export class HiddenFileSync extends LiveSyncCommands {
hiddenFilesEventCount.value++;
const rel = await this.semaphore.acquire();
try {
return await serialized(`hidden-file-event:${file}`, async () => {
return await withLock(`hidden-file-event:${file}`, async () => {
hiddenFilesProcessingCount.value++;
try {
return await fn();
@@ -1182,7 +1182,7 @@ Offline Changed files: ${processFiles.length}`;
async applyOfflineChanges(showNotice: boolean) {
const logLevel = getLogLevel(showNotice);
return await serialized("applyOfflineChanges", async () => {
return await withLock("applyOfflineChanges", async () => {
const p = this._progress("[⚙ Apply untracked changes ]\n", logLevel);
this._log(`Track changes.`, logLevel);
p.log("Enumerating local files...");
@@ -1703,7 +1703,7 @@ Offline Changed files: ${files.length}`;
}
const prefixedFileName = addPrefix(storeFilePath, ICHeader);
return await serialized("file-" + prefixedFileName, async () => {
return await withLock("file-" + prefixedFileName, async () => {
try {
const fileInfo = "stat" in file && "body" in file ? file : await this.loadFileWithInfo(storeFilePath);
if (fileInfo.deleted) {
@@ -1764,7 +1764,7 @@ Offline Changed files: ${files.length}`;
}
const prefixedFileName = addPrefix(storeFilePath, ICHeader);
return await serialized("file-" + prefixedFileName, async () => {
return await withLock("file-" + prefixedFileName, async () => {
try {
const baseData = await this.getLiveInternalRevision(prefixedFileName, baseRevision);
if (baseData === false) {
@@ -1842,7 +1842,7 @@ Offline Changed files: ${files.length}`;
if (await this.services.vault.isIgnoredByIgnoreFile(storageFilePath)) {
return undefined;
}
return await serialized("file-" + prefixedFileName, async () => {
return await withLock("file-" + prefixedFileName, async () => {
try {
const baseData = strict
? await this.loadBaseSaveDataStrict(storeFilePath, false)
@@ -1913,7 +1913,7 @@ Offline Changed files: ${files.length}`;
if (await this.services.vault.isIgnoredByIgnoreFile(storageFilePath)) {
return undefined;
}
return await serialized("file-" + prefixedFileName, async () => {
return await withLock("file-" + prefixedFileName, async () => {
try {
// Check conflicted status
const metaOnDB = requiredLiveRevision
@@ -12,7 +12,7 @@ import {
} from "@vrtmrz/livesync-commonlib/compat/common/types";
import { getNoFromRev } from "@vrtmrz/livesync-commonlib/compat/pouchdb/LiveSyncLocalDB";
import { LiveSyncCommands } from "@/features/LiveSyncCommands";
import { serialized } from "octagonal-wheels/concurrency/lock_v2";
import { withLock } from "octagonal-wheels/concurrency/lock_v2";
import { arrayToChunkedArray } from "octagonal-wheels/collection";
import { EVENT_REQUEST_PERFORM_GC_V3, eventHub } from "@/common/events";
import type { LiveSyncCouchDBReplicator } from "@vrtmrz/livesync-commonlib/compat/replication/couchdb/LiveSyncReplicator";
@@ -435,7 +435,7 @@ Note: **Make sure to synchronise all devices before deletion.**
chunkSet.add(doc._id);
} else if ("children" in doc) {
await processDoc(doc, isDeleted);
await serialized("x-process-doc", async () => await processDocRevisions(doc));
await withLock("x-process-doc", async () => await processDocRevisions(doc));
}
};
// Track changes
@@ -4,7 +4,7 @@ vi.mock("octagonal-wheels/number", () => ({
sizeToHumanReadable: vi.fn((value: number) => `${value} B`),
}));
vi.mock("octagonal-wheels/concurrency/lock_v2", () => ({
serialized: vi.fn((_key: string, task: () => unknown) => task()),
withLock: vi.fn((_key: string, task: () => unknown) => task()),
}));
vi.mock("octagonal-wheels/collection", () => ({
arrayToChunkedArray: vi.fn((values: unknown[]) => [values]),
+2 -2
View File
@@ -34,7 +34,7 @@ import { AbstractObsidianModule } from "@/modules/AbstractObsidianModule.ts";
import { addIcon, debounce, normalizePath, Notice, stringifyYaml, type WorkspaceLeaf } from "@/deps.ts";
import { LOG_LEVEL_NOTICE, setGlobalLogFunction } from "octagonal-wheels/common/logger";
import { LogPaneView, VIEW_TYPE_LOG } from "./Log/LogPaneView.ts";
import { serialized } from "octagonal-wheels/concurrency/lock";
import { withLock } from "octagonal-wheels/concurrency/lock";
import { $msg } from "@/common/translation";
import { P2PLogCollector } from "@vrtmrz/livesync-commonlib/compat/replication/trystero/P2PLogCollector";
import { REMOTE_REQUEST_ACTIVITY_MINIMUM_VISIBLE_MS, getTrackedRequestCount } from "./RemoteActivityStatus.ts";
@@ -524,7 +524,7 @@ ${stringifyYaml(info)}
writeLogToTheFile(now: Date, vaultName: string, newMessage: string) {
fireAndForget(() =>
serialized("writeLog", async () => {
withLock("writeLog", async () => {
const time = now.toISOString().split("T")[0];
const logDate = `${PREFIXMD_LOGFILE}${time}.md`;
const file = await this.core.storageAccess.isExists(normalizePath(logDate));
@@ -27,7 +27,7 @@ import type { LiveSyncEventHub } from "@vrtmrz/livesync-commonlib/context";
import type { StorageAccess } from "@vrtmrz/livesync-commonlib/compat/interfaces/StorageAccess";
import { EVENT_CONFLICT_CANCELLED } from "@vrtmrz/livesync-commonlib/compat/events/coreEvents";
import { NO_INTERACTION } from "@vrtmrz/livesync-commonlib/replication";
import { isLockAcquired, serialized } from "octagonal-wheels/concurrency/lock";
import { isLockAcquired, withLock } from "octagonal-wheels/concurrency/lock";
import diff_match_patch from "diff-match-patch";
import { stripAllPrefixes, isPlainText } from "@vrtmrz/livesync-commonlib/compat/string_and_binary/path";
@@ -192,7 +192,7 @@ export function createConflictResolutionOperations(
// per-file lock. Dialogues for other paths remain untouched.
dependencies.events.emitEvent(EVENT_CONFLICT_CANCELLED, filename);
}
return await serialized(serialisationKey, async () => {
return await withLock(serialisationKey, async () => {
if (latestResolveRequestByFilename.get(filename) !== requestId) {
return;
}
@@ -22,7 +22,7 @@ import type {
import type { LiveSyncLocalDB } from "@vrtmrz/livesync-commonlib/compat/pouchdb/LiveSyncLocalDB";
import type { LiveSyncEventHub } from "@vrtmrz/livesync-commonlib/context";
import { fireAndForget } from "octagonal-wheels/promises";
import { serialized } from "octagonal-wheels/concurrency/lock";
import { withLock } from "octagonal-wheels/concurrency/lock";
import { NO_INTERACTION } from "@vrtmrz/livesync-commonlib/replication";
import { displayRev } from "@vrtmrz/livesync-commonlib/compat/common/utils";
import { EVENT_CONFLICT_CANCELLED, EVENT_ON_UNRESOLVED_ERROR } from "@/common/events.ts";
@@ -160,7 +160,7 @@ export function createInteractiveConflictResolutionOperations(
// UI for resolving different files should proceed one-by-one. A newer
// request for the active file replaces its dialogue instead of waiting
// behind a comparison which is already stale.
return await serialized(dialogueSerialisationKey, async () => {
return await withLock(dialogueSerialisationKey, async () => {
if (disposed || latestRequestByFilename.get(filename) !== requestId) {
return false;
}
@@ -26,7 +26,7 @@ import {
import { fireAndForget, isAnyNote, throttle } from "@vrtmrz/livesync-commonlib/compat/common/utils";
import { Semaphore } from "octagonal-wheels/concurrency/semaphore_v2";
import { withCounting } from "octagonal-wheels/concurrency/counting";
import { serialized } from "octagonal-wheels/concurrency/lock";
import { withLock } from "octagonal-wheels/concurrency/lock";
import type { ReactiveSource } from "octagonal-wheels/dataobject/reactive_v2";
import type { LiveSyncBaseCore } from "@/LiveSyncBaseCore";
import { isNotFoundError } from "@vrtmrz/livesync-commonlib/compat/common/utils.doc";
@@ -534,7 +534,7 @@ export class ReplicateResultProcessor {
private _applyToDatabase(doc_: PouchDB.Core.ExistingDocument<AnyEntry>) {
const dbDoc = doc_ as LoadedEntry; // It has no `data`
const path = this.getPath(dbDoc);
return serialized(`replication-process:${dbDoc._id}`, async () => {
return withLock(`replication-process:${dbDoc._id}`, async () => {
const docNote = `${path} (${shortenId(dbDoc._id)}, ${shortenRev(dbDoc._rev)})`;
const isRequired = await this.checkIsChangeRequiredForDatabaseProcessing(dbDoc);
if (!isRequired) {
+47 -53
View File
@@ -12,52 +12,71 @@ Earlier releases remain available in the 1.0 release history, the 1.0 preview hi
## Unreleased
## 1.0.44
1st November, 2026
Recently, I have been concentrating on maintenance and improvements to individual features. I began to wonder whether I had been focusing a little too closely on the details and could give more attention to the overall picture. That led me to a somewhat broader round of changes, including reworking parts of the architecture. Along the way, I was able to bring several implementations of the same concepts together, introduce shared abstractions, and tidy up the codebase a little.
Please let me know if any problems arise from these changes.
### Fixed
#### Synchronisation
- Files containing a raw unit separator (`U+001F`) are no longer truncated when received through S3-compatible Object Storage. (#1303)
- Update every participating client to prevent further truncation. Updating alone does not repair copies already damaged.
- Before recovery, preserve verified intact copies, unique unsynchronised edits, and any required history, then follow the [recovery procedure](docs/recovery.md#recover-files-truncated-during-object-storage-reception).
- Rebuild and Fetch leave Hidden File Sync and Customisation Sync disabled. Initialise them deliberately after ordinary file recovery is complete.
- We can continue CouchDB synchronisation after rebuilding an encrypted remote when **Use Internal API** is enabled. (#1234)
- CouchDB read requests now carry cache-control headers to avoid reusing responses encrypted with the previous Security Seed.
- Cached responses encrypted with the previous Security Seed no longer interfere with subsequent reads.
- CLI `fetch` and `rebuild` retain their selected local database for subsequent commands even without `--write-settings`, preventing the empty-database restart and file deletion reported in #1304.
- Runtime state is separated by database and Vault. Interrupted reconstruction blocks ordinary synchronisation until recovery, and a pending settings commit can be completed on restart.
- Older runtime state with no recorded owner is preserved and requires explicit reconstruction from a verified source before ordinary writes resume. Back up unique edits before choosing Fetch or Rebuild.
#### Conflict resolution
- Files containing a raw unit separator (`U+001F`) retain their complete contents when received through S3-compatible Object Storage. (#1303)
- The Journal receiver in Commonlib 0.2.3 preserves the complete Chunk payload, including batches written by earlier versions, without changing the stored format.
- Updating prevents further truncation during reception but does not repair existing damaged copies. Update every participating client, preserve an intact recovery source, and follow the [recovery procedure](docs/recovery.md#recover-files-truncated-during-object-storage-reception). Rebuild and Fetch leave Hidden File Sync and Customisation Sync disabled until they are deliberately initialised again.
- A queued conflict check no longer gets stuck waiting for a response to an outdated comparison of the same file.
- Files waiting for encrypted Chunks which arrive after their Metadata receive the decrypted contents once the local database is ready.
- Commonlib 0.2.3 restarts the database change feed after initialisation installs the decryption transforms. This corrects delivery of later Chunks; it does not replay earlier events. ([Commonlib #191](https://github.com/vrtmrz/livesync-commonlib/pull/191))
#### Hidden File Sync and Customisation Sync
- The CLI mirror now starts correctly on Windows when the Vault path is omitted and the database directory is also used as the Vault. It checks logical Vault paths using `/` separators, correctly excluding internal database files and nested hidden directories even when the host uses Windows path separators.
- Newer local hidden files are no longer omitted when initialising Hidden File Sync with `Merge`.
- Refreshing the Customisation Sync catalogue no longer brings back outdated items from an earlier scan.
- Hidden File Sync now stores newer local files during `Merge` initialisation by comparing local and database modification times in the correct direction.
### Improved
- Hidden File Sync refreshes an open manual JSON comparison when a later conflict check arrives for the same file.
- The earlier dialogue closes, and the latest revisions are checked again before another comparison opens. A replicated resolution also closes the affected comparison. Dialogues for other files keep their order.
- An `Apply` operation already in progress finishes before its replacement proceeds.
#### Hidden File Sync
- Reloading the Customisation Sync catalogue no longer allows an earlier legacy-format scan to put outdated items back into the list.
- Reloading or disabling Customisation Sync discards unstarted scans and ignores stale read results. Migrations to per-file storage which have already started are allowed to finish.
- We can review the latest hidden-file revisions when changes arrive during a manual JSON comparison.
- The earlier comparison closes, and the latest revisions are checked again. If another device has resolved the conflict, the comparison closes without asking for another selection.
### Synchronisation
### Sunset
- Devices using the legacy IndexedDB adapter now use the standard recovery dialogue when a remote retains an old cleanup lock.
- The obsolete in-place `Cleanup` option has been removed. We can fetch the remote again, explicitly unlock the device, or dismiss the dialogue; dismissal remains the default.
- Fetch replaces the local database, so preserve unsynchronised work and any required local history first. Unlock only when the local and remote databases are already aligned. Garbage Collection V3 is unchanged.
#### Recovery
- Recovery from old cleanup locks is now unified around the standard recovery dialogue, including on devices using the legacy IndexedDB adapter.
- Use Fetch to reconstruct the local database from the remote, unlock the device when both databases are already aligned, or dismiss the dialogue.
- These choices replace the legacy in-place `Cleanup` operation. Garbage Collection V3 remains available.
- Before Fetch, preserve unsynchronised work and any required local history, as it replaces the local database.
### Maintenance
### CLI
- The plug-in bundle is smaller, reducing the download for installation and updates.
- Builds remove unused code and shorten local identifiers while retaining class and function names for diagnostics.
#### Fixed
### Diagnostics
- We can continue using the reconstructed database after CLI `fetch` or `rebuild`, avoiding the empty-database restart and file deletion reported in #1304.
- Subsequent commands retain the selected database even without `--write-settings`. Interrupted reconstruction blocks ordinary synchronisation until recovery.
- Older runtime state with no recorded owner requires explicit reconstruction from a verified source. Back up unique edits before choosing Fetch or Rebuild.
- Service log calls stay with the plug-in instance that registered them. Its log connection is released after the full shutdown, so stale instances cannot receive later service messages.
- The CLI mirror starts correctly on Windows when the Vault path is omitted and the database directory is also used as the Vault.
### Browser applications
### Miscellaneous
- WebApp and WebPeer dialogues and menus remain with their owning application
and close on shutdown; closing or replacing a menu releases its component.
- Installation and update downloads are smaller.
- Database initialisation handles encrypted change notifications more reliably. (Commonlib #191)
### Acknowledgements
Thank you for your contributions!
- @AdrianNicu, for improving decryption handling during database initialisation (Commonlib #191).
## 1.0.43
@@ -254,28 +273,3 @@ I have been finding troubleshooting difficult, so I decided to go back to basics
- The five reporting tools are grouped separately from checks, recovery, and reset operations. Name and format choices remain visible, and the report preview resizes around the controls.
- We can request and provide targeted troubleshooting evidence through the issue-reporting guide.
- Direct links cover Integrity, Usage, pseudonym lookup, and follow-up reports, with request examples for issue discussions.
## 1.0.38
17th October, 2026
### Synchronisation
#### Added
- We can opt into NFC path identity for CouchDB synchronisation between the CLI and Obsidian. This prevents decomposed or mixed Unicode file names from becoming inaccessible through a receiving device's normalised path. (#1259)
- Existing Vaults retain their current identity rules. Migration requires updated clients, a Rebuild from a complete authoritative Vault, and a Fetch on the other devices.
- Reconstruction stops when the supported host file listing or local Metadata exposes ambiguous files or parent folders under NFC and the configured case handling. The CLI retains existing physical file names. P2P and Object Storage do not support the new mode.
#### Fixed
- We can synchronise file deletions and renames when **Do not keep metadata of deleted files.** is enabled.
- A file restored during the final deletion check remains in the receiving Vault.
- We can resolve configuration mismatches when **Encrypt internal file Properties** is enabled but its encryption prerequisites are inactive. (#1180)
- Compatible chunk settings can be aligned automatically, or reviewed through `Sync now`. Applying those settings preserves our inactive encryption preference.
- We can receive synchronised updates to unchanged files even after the local database has discarded their recorded revision, avoiding unnecessary conflicts. (#1207)
- Saved content evidence survives revision-history pruning. Genuine local edits remain protected, and older records are upgraded only after their content is verified.
### Maintenance
- The Review Harness can compare SHA-256 and xxHash64 file-content hashing on the current device, with fixed inputs from 4 KiB to 50 MiB and separate initialisation and warm-up measurements.
+2 -1
View File
@@ -54,5 +54,6 @@
"1.0.40": "1.7.2",
"1.0.41": "1.7.2",
"1.0.42": "1.7.2",
"1.0.43": "1.7.2"
"1.0.43": "1.7.2",
"1.0.44": "1.7.2"
}