Compare commits

...
Author SHA1 Message Date
vorotamoroz b8191e548d Record 1.0.19 pre-release metadata 2026-08-25 13:25:47 +00:00
vorotamoroz 1c3feb526c Merge pull request #1133 from vrtmrz/refactor/obsidian-api-modernisation
Modernise LiveSync settings for Obsidian's declarative API
2026-08-25 20:14:42 +09:00
vorotamoroz aebf4874b3 Restore npm 10 lockfile compatibility 2026-08-25 11:03:25 +00:00
vorotamoroz 3ea8eac212 Reduce deprecated API and dependency warnings 2026-08-25 10:29:21 +00:00
vorotamoroz f6eefed97c Route pending settings through initialisation choices 2026-08-25 09:12:16 +00:00
vorotamoroz 131ffeb0ef Separate declarative root group identity from display 2026-08-25 07:29:56 +00:00
vorotamoroz 070ce0e307 Reorganise settings around common tasks 2026-08-25 05:34:57 +00:00
vorotamoroz 8adc88b8cb Complete declarative settings runtime integration 2026-08-24 18:11:46 +00:00
vorotamoroz dd11a753d5 Align host integrations with current API recommendations 2026-08-24 16:39:32 +00:00
vorotamoroz 47759f6205 Adapt settings pages to Obsidian's declarative API 2026-08-24 12:17:19 +00:00
vorotamoroz d8f7762d01 Introduce shared setting specifications for Advanced settings 2026-08-24 10:36:39 +00:00
vorotamoroz ebaf89f822 Remove obsolete settings wizard 2026-08-24 10:02:32 +00:00
vorotamoroz 06b9f32bdf Design incremental declarative settings migration 2026-08-24 09:36:16 +00:00
vorotamoroz 32e827692f Merge pull request #1132 from vrtmrz/1_0_18
Releasing 1.0.18
2026-08-24 16:23:00 +09:00
github-actions[bot] cdf6935042 Releasing 1.0.18 2026-08-24 06:45:25 +00:00
vorotamoroz 665a5b5b61 Merge pull request #1131 from vrtmrz/release-notes/database-readiness
Document database readiness and rebuild improvements
2026-08-24 13:23:08 +09:00
vorotamoroz 83faec9280 Add database readiness release notes 2026-08-24 04:18:28 +00:00
vorotamoroz d0bdae2676 Merge pull request #1128 from vrtmrz/update/commonlib-0.1.19-rc.0
Update Commonlib to 0.1.19
2026-08-24 13:10:06 +09:00
vorotamoroz cfbf94a38a Pin Commonlib 0.1.19 stable release 2026-08-24 03:23:59 +00:00
vorotamoroz 35e170463e Integrate Commonlib 0.1.19-rc.0 2026-08-23 16:54:23 +00:00
vorotamoroz 694371898a Merge pull request #1127 from vrtmrz/1_0_17
Releasing 1.0.17
2026-08-23 15:22:58 +09:00
vorotamoroz 3f1d4efd86 Add translation fix to 1.0.17 release notes 2026-08-23 05:39:47 +00:00
github-actions[bot] d27a5b3725 Releasing 1.0.17 2026-08-23 05:33:35 +00:00
vorotamoroz cbd42bdecb Merge pull request #1125 from vrtmrz/feature/p2p-transport-compatibility
Add P2P connection compatibility controls
2026-08-23 01:16:47 +09:00
vorotamoroz e20386b8e2 Use Commonlib 0.1.18 for P2P compatibility 2026-08-22 05:10:52 +00:00
vorotamoroz f09b4c779e Expose P2P transport compatibility profiles 2026-08-21 16:28:42 +00:00
vorotamoroz 552d286392 Merge pull request #1124 from vrtmrz/design/p2p-transport-compatibility
Design P2P connection compatibility controls and Coturn deployment
2026-08-21 21:28:36 +09:00
vorotamoroz 22ae1b4a6e Credit P2P transport compatibility diagnosis 2026-08-21 11:17:36 +00:00
vorotamoroz d1ae42a134 Document P2P transport compatibility and Coturn setup 2026-08-21 11:05:40 +00:00
vorotamoroz c7443ee728 Merge pull request #1123 from nimula/fix/settings-manifest-translator
Wire the display-language translator into the settings manifest
2026-08-21 17:25:13 +09:00
nimulaandClaude Opus 5 f8ee3c8662 i18n: wire the display-language translator into the settings manifest
Commonlib's `getConfig(key, translate?)` and `getConfName(key, translate?)`
default `translate` to `englishMessageTranslator`, and this plug-in never
passed the second argument. Every automatically wired setting therefore
rendered its name and description in English, whatever `displayLanguage` was
set to. Commonlib's own Config Doctor already threads a translator through
`getConfName`, so this only restores the argument which was missing here.

`src/modules/features/SettingDialogue/settingConstants.ts` now re-exports the
names it supplies explicitly and adds thin `getConfig` and `getConfName`
wrappers which default the translator to `translateLiveSyncMessage`. That
reaches all three existing call sites, and therefore the 102 `setAuto` and
`autoWire*` calls across the setting panes, the setup-wizard configuration
summaries, and the externally-modified-setting prompt. Of the 225 distinct
name and description strings in the two manifest tables, 160 are already
catalogue keys with translations; the remaining 65 are not catalogue keys and
pass through unchanged.

`ModuleResolveMismatchedTweaks` used `confName()`, which accepts no
translator, so it gains a local `localisedConfName()` instead. Swapping in
`getConfName()` there would have silently dropped the `statusDisplay()`
suffix, replaced the empty-string fallback for an unknown key with
`${key} (No info)`, and introduced `SettingInformation` as a second source.

English output is unchanged: every catalogue key which contains a space has a
value identical to the key itself, so translating under the default language
is idempotent.

Verified with `npm run check`, `npm run test:unit`, and `npm run build`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-20 02:17:20 +00:00
vorotamoroz fbe868092a Merge pull request #1122 from vrtmrz/1_0_16
Releasing 1.0.16
2026-08-19 19:25:44 +09:00
vorotamoroz 693cd77576 Archive older 1.0 release notes 2026-08-19 09:17:18 +00:00
github-actions[bot] 98ea2b516a Releasing 1.0.16 2026-08-19 09:13:29 +00:00
vorotamoroz 4d9e24d8ed Merge pull request #1121 from vrtmrz/docs/define-synchronising-devices
Define synchronising devices terminology
2026-08-19 16:39:05 +09:00
vorotamoroz c6a964548a docs: define synchronising devices 2026-08-19 07:34:19 +00:00
vorotamoroz d69cff0bc6 Merge pull request #1120 from vrtmrz/fix/document-history-restoration
Persist Document History restorations before Vault reflection
2026-08-19 15:39:27 +09:00
vorotamoroz f02470ec5c docs: define independent revision properties 2026-08-19 06:25:00 +00:00
vorotamoroz a0684c4b3a fix: use commonlib 0.1.17 for history restoration 2026-08-19 06:25:00 +00:00
vorotamoroz dc5274df27 fix: persist document history restoration in the database 2026-08-19 06:24:50 +00:00
vorotamoroz a6c93c358e Merge pull request #1117 from vrtmrz/fix/couchdb-remote-connection-ownership
Record CouchDB remote connection ownership
2026-08-18 19:41:37 +09:00
vorotamoroz d9df2a859f docs: record pending CouchDB robustness improvement 2026-08-18 09:46:44 +00:00
vorotamoroz b7c2512da6 chore: adopt Commonlib 0.1.16 2026-08-18 09:17:22 +00:00
vorotamoroz 97b0ec25c7 docs: record CouchDB remote connection ownership 2026-08-18 07:52:16 +00:00
vorotamoroz bc41355a74 Merge pull request #1115 from vrtmrz/1_0_15
Releasing 1.0.15
2026-08-16 00:28:55 +09:00
github-actions[bot] 3d69142a52 Releasing 1.0.15 2026-08-15 15:05:11 +00:00
vorotamoroz 2403a66441 Merge pull request #1114 from vrtmrz/release-notes/1.0.15
Add 1.0.15 release notes
2026-08-16 00:03:46 +09:00
vorotamoroz 54e20dede3 docs: add 1.0.15 release notes 2026-08-15 14:57:29 +00:00
vorotamoroz 277091c1e4 Merge pull request #1113 from vrtmrz/docs/clarify-couchdb-provisioning-credentials
Clarify CouchDB setup credentials
2026-08-15 23:50:29 +09:00
vorotamoroz e294747557 Clarify CouchDB setup credentials 2026-08-15 14:38:28 +00:00
vorotamoroz 3062d45dc5 Merge pull request #1106 from nimula/i18n/zh-tw-catalogue-coverage
i18n(zh-tw): complete the catalogue coverage
2026-08-15 23:08:40 +09:00
vorotamoroz 8e14bc35d2 Merge pull request #1112 from vrtmrz/chore/commonlib-0.1.15
Update Commonlib to 0.1.15
2026-08-15 22:43:14 +09:00
vorotamoroz a07082d7bc Update Commonlib to 0.1.15 2026-08-15 13:22:47 +00:00
vorotamoroz 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
vorotamoroz 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
vorotamoroz 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
vorotamoroz 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
vorotamoroz 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
nimulaandClaude Sonnet 5 0a1e1f3625 i18n(zh-tw): polish translation naturalness and terminology consistency
- Unify 資料庫適配器 -> 資料庫轉接器 across 6 entries so the database
  adapter setting uses one term throughout the catalogue.
- Drop the calqued "透過⋯⋯來⋯⋯" construction in 3 entries (Testing-only
  conflict resolution, the P2P replicator description, and the
  duplicated garbage-collection cleanup description); the sentences
  read the same without the borrowed English "by X to Y" shape.
- Rename the Eden-chunk "Incubate" feature's label from 孵化 (hatch) to
  暫存 (temporarily store) across 7 entries, matching the wording
  already used in the toggle's own description and avoiding a
  half-metaphor/half-literal split within the same feature.
- Convert the ASCII ellipsis "..." to the full-width "⋯" in 22
  in-progress/loading strings, per Taiwanese typography convention;
  the English source keys (which legitimately end in "...") are
  untouched.
- Reword "Fetch database with previous behaviour" to avoid a 以前/以
  + 前一種 parsing ambiguity, matching the 先前的行為 wording already
  used by the sibling 保留先前的行為 entry.
- Drop the orphaned closing paren in errMaxDocumentSize/errMaxRequestSize,
  fixing a display defect inherited from the English source.
- Add 同步 to beforeLiveSync for clarity.

Regenerated messagesJson/zh-tw.json and combinedMessages.prod.ts via
`npm run i18n:bake`; no other locale's entries changed.

Verified with tsc-check, lint, lint:community, lint:community:tools,
svelte-check, build, check:compatibility, and test:unit (630 tests,
88 files) — all pass.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-13 15:18:35 +08:00
nimulaandClaude Opus 5 b939687cb3 i18n(zh-tw): complete the catalogue coverage
Continues the zh-tw work from commit 0f50f290, which only translated the
190 keys `$msg()` calls in `src/` resolved through the catalogue at the
time. This closes the remaining gap: 573 of 1450 keys (39.5%) to 1446 of
1450 (99.7%), matching es (99.9%) and comfortably ahead of every other
locale except it.

While re-scanning to find the next batch, the earlier "code-referenced"
count turned out to be wrong: the previous grep only matched single-line
`$msg("literal")` calls and missed multi-line calls and the `msg`, `_msg`,
and `translateMessage` aliases various Svelte components import (e.g.
`import { $msg as msg } from "@/common/translation"`). A corrected scan
found 624 referenced keys, not 309, of which 350 were still untranslated —
mostly the P2P Sync status panes and several SetupWizard dialogues. Since
this pass translates everything remaining regardless of reference status,
that gap closes as a side effect; it is called out here only because it
explains why some of the newly translated strings are already visible in
today's UI rather than being pure catalogue completeness.

- Translated by area: P2P Sync replicator panes and the `P2P.*` runtime
  messages; ConfigSync's plugin/snippet/theme comparison UI; the
  SetupWizard's remote-setup, rebuild/fetch, and outro dialogues (both the
  legacy flat-key strings and the newer `Ui.SetupWizard.*` Svelte
  component keys); the Svelte-based `Ui.Settings.*` settings panels
  (Maintenance, Patches, Hatch, Remote, Common, Selector, PowerUsers,
  CustomisationSync, Advanced, Setup, SyncSettings); Setting Doctor
  (`Doctor.*`); the remote-size warning flow (`moduleCheckRemoteSize.*`);
  the redflag3 fetch-method chooser (`RedFlag.*`); the remaining
  `moduleMigration.*`, `liveSyncReplicator.*`, `Replicator.*`, and
  `TweakMismatchResolve.*` entries; and the rest of the plain
  English-sentence keys, including the language-picker's native-script
  entries (added `lang-he: עברית`, matching how `lang-de`/`lang-ja`/etc.
  already show each language's own name rather than a Chinese gloss).
- Wording follows docs/terms.md and the register already established in
  this file: `Setup URI`, `Vault`, `chunk(s)`, `Peer`, `Peer-to-Peer`,
  `Hatch`, and `CouchDB` stay untranslated as proper nouns; `resolved`
  (device/database state) is consistently 已處理, matching the existing
  Scram-dialogue usage.
- `${...}` placeholders and `%{...}` keyword references are kept
  byte-identical to the English source, including the multi-paragraph
  `RedFlag.Fetch.Method.Desc` and `moduleCheckRemoteSize.msgDatabaseGrowing`
  callout blocks; verified programmatically against all 1446 zh-tw
  entries.
- 4 keys are still deliberately left untranslated, unchanged from the
  previous commit: `moduleMigration.docUri` and
  `obsidianLiveSyncSettingTab.linkTroubleshooting` are a URL and a doc
  path, `obsidianLiveSyncSettingTab.logServerConfigurationCheck`'s English
  value is the key name itself, and `moduleLiveSyncMain.logPluginVersion`
  is a version-number template.
- Regenerated `messagesJson/zh-tw.json` and `combinedMessages.prod.ts`
  through the documented `i18n:bake` pipeline; no other language's
  entries changed.

Verified with tsc-check, tsc-check:apps, lint, lint:community,
lint:community:tools, svelte-check, check:compatibility, and test:unit
(604 tests, 86 files) — all pass.

Not in scope, unchanged from the previous commit: the ~137 Commonlib-owned
setting labels/descriptions never reach any translator because
`getConfig()` call sites in SettingDialogue omit the `translate` argument;
German is staged in the catalogue but never imported by
`combinedMessages.dev.ts`; Hebrew keyword references are never expanded by
`expandKeywords()`. None of these are specific to zh-tw and none are
touched here.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-13 15:18:35 +08:00
nimulaandClaude Opus 5 df8c79538e i18n(zh-tw): translate the keys the application actually renders
The zh-tw catalogue was the weakest of the ten supported locales at 380 of
1450 keys (26%), well behind zh (75%) and ja (57%). Users who select
繁體中文 saw English for most of the interface.

This does not attempt full parity. It closes the gap for the 190 keys that
`$msg()` calls in `src/` actually resolve through the catalogue (zh-tw had
no entry for any of them), plus the 7 `K.*` keyword fragments that
`expandKeywords()` substitutes into `%{...}` references at build time
(zh-tw had none of these either, so any zh-tw message referencing `%{Peer}`
etc. would have shown the raw token on screen — the same defect currently
affecting `he`).

- 186 of the 190 code-referenced keys are translated. The remaining 4 are
  left as-is because their English value is not translatable prose:
  `moduleMigration.docUri` and `obsidianLiveSyncSettingTab.linkTroubleshooting`
  are a URL and a doc path, `obsidianLiveSyncSettingTab.logServerConfigurationCheck`'s
  English value is the key name itself, and `moduleLiveSyncMain.logPluginVersion`
  is a version-number template.
- Two pre-existing entries are corrected for internal consistency: `Bucket
  Name` (儲存桶→儲存庫) and `Peer-to-Peer Synchronisation` (點對點同步→
  Peer-to-Peer 同步), the latter matching the other three occurrences of
  "Peer-to-Peer" already left untranslated in this same file.
- Wording follows docs/terms.md and the existing zh-tw register: `Setup
  URI`, `Vault`, `chunk(s)`, `Hatch`, and `Peer-to-Peer` stay untranslated
  as proper nouns, matching how the rest of the file already treats them.
- `${...}` placeholders and `%{...}` keyword references are kept
  byte-identical to the English source; verified programmatically against
  all 573 zh-tw entries, not just the new ones.
- Regenerated `messagesJson/zh-tw.json` and `combinedMessages.prod.ts`
  through the documented `i18n:bake` pipeline; no other language's entries
  changed.

Coverage: 380/1450 (26.2%) -> 573/1450 (39.5%).

Verified with tsc-check, tsc-check:apps, lint, lint:community,
lint:community:tools, svelte-check, check:compatibility, and test:unit
(604 tests, 86 files) — all pass.

Not in scope, left for follow-up: the ~137 Commonlib-owned setting
labels/descriptions never reach any translator because `getConfig()` call
sites in SettingDialogue omit the `translate` argument; German is staged
in the catalogue but never imported by `combinedMessages.dev.ts`; Hebrew
keyword references are never expanded by `expandKeywords()`. None of these
are specific to zh-tw and none are touched here.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-13 15:18:35 +08:00
vorotamoroz 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
vorotamoroz 510db277cd Merge pull request #1105 from vrtmrz/1_0_13
Releasing 1.0.13
2026-08-13 13:01:17 +09:00
github-actions[bot] b3bf717947 Releasing 1.0.13 2026-08-13 02:48:28 +00:00
vorotamoroz 93f0f78494 Merge pull request #1104 from vrtmrz/release-notes/1.0.13
Add Metadata document ID repair release note
2026-08-13 11:46:39 +09:00
vorotamoroz 11cb09d49a docs: add metadata repair release note 2026-08-13 02:42:18 +00:00
vorotamoroz e0160f15f3 Merge pull request #1103 from vrtmrz/docs/clarify-metadata-identity-handling
Clarify Metadata document ID mismatch handling
2026-08-13 11:31:18 +09:00
vorotamoroz a3aa79ead9 docs: clarify metadata identity mismatch handling 2026-08-13 02:22:34 +00:00
vorotamoroz db8dac2db4 Merge pull request #1102 from vrtmrz/fix/metadata-document-id-repair
Add safe Metadata document ID repair
2026-08-13 11:13:13 +09:00
vorotamoroz c933674a0d Add safe Metadata document ID repair 2026-08-12 18:56:17 +00:00
vorotamoroz eda78dee36 Merge pull request #1098 from vrtmrz/1_0_12
Releasing 1.0.12
2026-08-11 18:18:44 +09:00
vorotamoroz 67c8424231 Polish 1.0.12 release notes 2026-08-11 03:37:13 +00:00
github-actions[bot] 1735bc4d66 Releasing 1.0.12 2026-08-11 03:29:34 +00:00
vorotamoroz d11a92498d Merge pull request #1097 from vrtmrz/update/commonlib-0.1.11
Update Commonlib to 0.1.11
2026-08-11 12:06:49 +09:00
vorotamoroz 950623f5d6 Update Commonlib to 0.1.11 2026-08-11 02:54:07 +00:00
vorotamoroz bb72b6b317 Merge pull request #991 from odpay/docs/zenith-badge
docs: add managed CouchDB hosting option
2026-08-11 10:53:53 +09:00
vorotamoroz 769b7ff4b6 Merge pull request #1095 from vrtmrz/1_0_11
Releasing 1.0.11
2026-08-10 01:10:53 +09:00
github-actions[bot] 1bb50c1580 Releasing 1.0.11 2026-08-09 14:49:28 +00:00
vorotamoroz d17330f3c7 Merge pull request #1094 from vrtmrz/fix/issue-1020-fast-fetch-transport
Fall back to Standard Fetch for CouchDB's internal request API
2026-08-09 23:29:40 +09:00
vorotamoroz bd45924649 Use Commonlib 0.1.10 for buffered Fast Fetch fallback 2026-08-09 12:52:03 +00:00
vorotamoroz b1ad3e0653 docs: define Fast Fetch transport eligibility 2026-08-09 11:37:14 +00:00
vorotamoroz 6b94f0ce47 Merge pull request #1039 from andrewleech/fix/float-mtime-mobile-crash
fix: prevent float mtime from crashing mobile clients
2026-08-09 19:04:35 +09:00
vorotamoroz 08b55b7677 docs: note fractional timestamp fix 2026-08-09 09:14:54 +00:00
vorotamoroz 21d904cfd6 fix(storage): adapt timestamp guard to current Commonlib
Use the packaged Commonlib type import and add regression coverage for every Obsidian vault and storage write method. Verify that fractional timestamps are floored without mutating caller-owned options.
2026-08-09 08:46:21 +00:00
vorotamoroz 00de35e5d4 Merge current main into PR 1039 2026-08-09 08:45:16 +00:00
vorotamoroz f2976bc89a Merge pull request #1093 from vrtmrz/1_0_10
Releasing 1.0.10
2026-08-09 13:19:50 +09:00
github-actions[bot] b65deede79 Releasing 1.0.10 2026-08-09 02:50:06 +00:00
vorotamoroz 9203bdd40e Merge pull request #1092 from vrtmrz/update/commonlib-0.1.9
Update Commonlib to 0.1.9
2026-08-09 11:44:44 +09:00
vorotamoroz fcd30d07be Update Commonlib to 0.1.9 2026-08-09 02:35:57 +00:00
vorotamoroz cfb75a05db Merge pull request #1091 from vrtmrz/fix/issue-reporting-guide
Refresh issue reporting guidance
2026-08-09 00:36:16 +09:00
vorotamoroz 5b19f4415d Refresh issue reporting guidance 2026-08-08 15:10:54 +00:00
vorotamoroz 047429033f Merge pull request #1090 from vrtmrz/1_0_9
Releasing 1.0.9
2026-08-08 22:43:59 +09:00
vorotamoroz 9765569bb6 Add personal note for 1.0.9 2026-08-08 13:04:48 +00:00
github-actions[bot] c5835a9da6 Releasing 1.0.9 2026-08-08 13:00:23 +00:00
vorotamoroz 8311060f77 Merge pull request #1089 from vrtmrz/reconcile/1.0.8-pre-release
Record the unpromoted 1.0.8 pre-release
2026-08-08 21:47:44 +09:00
vorotamoroz 13d9624407 Record unpromoted 1.0.8 pre-release 2026-08-08 12:33:42 +00:00
vorotamoroz 76560e3bf2 Merge pull request #1083 from calvinbui/fix/qr-aggregator-special-characters
Fix special characters in multi-part settings QR codes
2026-08-08 21:19:22 +09:00
vorotamoroz 1e190d042c Merge main into multi-part settings QR fix 2026-08-08 12:13:31 +00:00
vorotamoroz 40215032dd Merge pull request #1088 from vrtmrz/fix/fast-fetch-page-timeout
Complete bounded Fast Fetch pages on CouchDB 3.2
2026-08-08 20:39:19 +09:00
vorotamoroz fd9a9175dd Use Commonlib 0.1.8 for reliable Fast Fetch pagination 2026-08-08 11:22:34 +00:00
vorotamoroz 1dfdb72fbd Merge pull request #1085 from vrtmrz/fix/fast-fetch-bounded-pages
Complete bounded Fast Fetch and stop repeated cancelled Fetch
2026-08-08 17:02:05 +09:00
vorotamoroz 23d9fa360d Use Commonlib 0.1.7 for bounded Fast Fetch 2026-08-08 07:44:33 +00:00
vorotamoroz 070b63c952 Document bounded Fast Fetch completion 2026-08-08 05:39:49 +00:00
vorotamoroz d37af53858 Stop repeating cancelled scheduled Fetch 2026-08-08 04:29:57 +00:00
github-actions[bot] b05309ad9c Releasing 1.0.7 2026-08-08 02:51:53 +00:00
vorotamoroz 92e01a17e7 Merge pull request #1082 from vrtmrz/fix/fast-fetch-persistence
Keep incomplete Fast Setup isolated until Fast Fetch completes
2026-08-08 11:11:03 +09:00
vorotamoroz b3030bba53 Document Fast Setup failure recovery 2026-08-08 02:05:42 +00:00
Calvin Bui cf5181bb28 Fix special characters in aggregated QR settings 2026-08-07 21:48:14 +10:00
vorotamoroz 002cf57116 Use Commonlib 0.1.6 for Fast Fetch 2026-08-07 09:35:25 +00:00
vorotamoroz 15abe344bb Keep incomplete Fast Setup isolated 2026-08-07 08:27:04 +00:00
vorotamoroz a9e64860d5 Merge pull request #1081 from vrtmrz/1_0_6
Releasing 1.0.6
2026-08-06 21:51:24 +09:00
vorotamoroz 741551be3a Releasing 1.0.6 2026-08-06 10:29:58 +00:00
vorotamoroz 5f69d301f0 Merge pull request #1080 from vrtmrz/fix/setup-synchronisation-settings-guidance
Distinguish absent and unreadable remote settings during setup
2026-08-06 18:57:52 +09:00
vorotamoroz b42df389fb Distinguish absent and unreadable remote settings during setup
Use typed Commonlib outcomes to guide Fetch and Rebuild without treating a new remote as a failed read. Preserve automatic synchronisation choices when scheduled initialisation is cancelled.
2026-08-06 08:34:41 +00:00
vorotamoroz 6a46966957 Merge pull request #1079 from vrtmrz/1_0_5
Releasing 1.0.5
2026-08-05 20:22:10 +09:00
github-actions[bot] 18bf31a01b Releasing 1.0.5 2026-08-05 10:46:11 +00:00
vorotamoroz 33250c6961 Merge pull request #1078 from vrtmrz/docs/prepare-1.0.5-release-notes
Prepare 1.0.5 release notes
2026-08-05 19:42:05 +09:00
vorotamoroz a36fe6c36e Prepare 1.0.5 release notes 2026-08-05 10:39:17 +00:00
vorotamoroz 92a5e6e145 Merge pull request #1067 from Hsiii/fix/mobile-status-header-offset
fix(ios): position sync status below header controls
2026-08-05 19:32:11 +09:00
vorotamoroz be6da86a3a Merge pull request #1075 from motolies/korean-translations-100
Complete the Korean translations to full coverage
2026-08-05 19:31:37 +09:00
vorotamoroz 8bb2d5038d Merge pull request #1077 from vrtmrz/fix/issue-1073-sleep-during-synchronisation
Allow desktop sleep during finite synchronisation
2026-08-05 19:13:37 +09:00
vorotamoroz 9d25a00c1d Add sleep preferences for finite synchronisation 2026-08-05 09:50:13 +00:00
vorotamoroz 277337af40 Merge pull request #1076 from vrtmrz/1_0_4
Releasing 1.0.4
2026-08-05 15:08:06 +09:00
vorotamoroz da3e2c983f Complete 1.0.4 release notes 2026-08-05 04:38:01 +00:00
github-actions[bot] 185ec2c392 Releasing 1.0.4 2026-08-05 04:27:56 +00:00
vorotamoroz b1d7f3b622 Merge pull request #1074 from vrtmrz/fix/issue-1070-cli-settings-writeback
Fix CLI settings write-back behaviour
2026-08-05 12:47:48 +09:00
Namwon KimandClaude Opus 5 697ebe8272 Complete Korean translations to full coverage
Fill in the 318 remaining untranslated message keys for Korean, bringing
`ko` from 1132/1450 to 1450/1450 against the English base locale.

The gap was concentrated in recently added areas: P2P synchronisation UI,
the setup wizard confirmation dialogues, S3/MinIO/R2 and JWT authentication
settings, and the diagnostics/maintenance panels. These keys are looked up
by their English source text, so an absent translation silently fell back
to English.

Terminology follows the existing Korean resource: Vault as 보관함, chunk as
청크, passphrase as 패스프레이즈, replication as 복제, rebuild as 재구축.
Proper nouns (CouchDB, LiveSync, Setup URI, JWT, TURN) are left as-is.

Sentence fragments that are concatenated in the setup wizard were adapted
to Korean word order — Korean places the predicate last, so the emphasised
trailing fragment carries the verb (e.g. "the single, authoritative master
copy" renders as a complete predicate).

Only ko.yaml was hand-edited; ko.json and combinedMessages.prod.ts are
regenerated by `npm run i18n:bake`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R5avHoUL5zBwBfakGHyeFz
2026-08-05 12:37:15 +09:00
vorotamoroz c27dbbd572 Fix CLI settings write-back behaviour 2026-08-05 03:24:56 +00:00
vorotamoroz 83087f159e Merge pull request #1072 from vrtmrz/deps/commonlib-0.1.3
Update Commonlib to 0.1.3
2026-08-05 12:08:09 +09:00
vorotamoroz d12d0f8ef7 Update Commonlib and secure brace-expansion resolutions 2026-08-05 01:58:39 +00:00
vorotamoroz e9b2477bf3 Release Self-hosted LiveSync 1.0.3 (#1069)
Merge the reviewed 1.0.3 release commit after BRAT validation and stable promotion.
2026-08-03 22:26:35 +09:00
Hsi 96a69c85bd docs: document iOS status layout fix 2026-08-03 18:24:10 +08:00
Hsi de190e2b23 fix: position ios sync status below header controls 2026-08-03 17:37:00 +08:00
odpay 34ae802796 docs: add managed CouchDB hosting option 2026-08-03 16:26:10 +10:00
Andrew Leech a3a09df3c8 fix(storage): floor write-option timestamps in Obsidian adapters
Obsidian's mobile storage layer forwards mtime/ctime to Capacitor's
Filesystem.setTimes, whose native binding casts the value to a Java Long.
A non-integer (float) timestamp makes that cast throw (ClassCastException:
Double cannot be cast to Long), which crashes the app on launch as soon as
such a document is replicated in.

Float timestamps can reach the database from any client that stores
fs.Stats.mtimeMs without flooring. Coerce mtime/ctime to integer ms at the
Obsidian vault and storage adapter write boundary, so a float already
present in the mesh can't crash the app regardless of where it came from.

The truly central choke point is dbToStorage in livesync-commonlib; a
matching guard there would cover the CLI and webapp too. This change
protects the platform that actually crashes.

Claude-Session: https://claude.ai/code/session_0123E9jVQrsgu3zb82Csuwhi
2026-07-22 14:08:21 +10:00
Andrew Leech 4393a49cba fix(cli): floor watch-mode stat timestamps to integer ms
CLIWatchAdapter._toNodeFile passed chokidar's raw stats.ctimeMs/mtimeMs
straight through. On Linux those carry sub-millisecond precision (e.g.
1778511180024.462), so watch-mode file changes wrote non-integer
timestamps into the database.

The earlier floor fix (3f7bb047) covered the scan/stat adapters but missed
this watch path, which is the one the daemon actually uses at runtime.
Mobile clients then crash on such a document because Capacitor's
Filesystem.setTimes casts the value to a Java Long (ClassCastException:
Double cannot be cast to Long).

Floor ctimeMs/mtimeMs here, with a null guard so a partial stat still
falls back to Date.now(). Adds a regression test that a fractional stat is
floored.

Claude-Session: https://claude.ai/code/session_0123E9jVQrsgu3zb82Csuwhi
2026-07-22 14:08:21 +10:00
186 changed files with 15374 additions and 2976 deletions
+6 -5
View File
@@ -8,7 +8,7 @@ assignees: ''
---
Thank you for taking the time to report this issue!
Before filling in this form, please read: [How to report an issue](../docs/to_issue_reporting.md).
Before filling in this form, please read [How to report an issue](https://github.com/vrtmrz/obsidian-livesync/blob/main/docs/to_issue_reporting.md).
Issues with sufficient information will be prioritised.
@@ -49,13 +49,14 @@ To get it: open the command palette → "Show debug info".
</details>
### LiveSync version
The hatch report (below) includes version information. If you cannot provide the report, please fill in the version here.
The full LiveSync report below includes version information. If you cannot provide the report, please fill in the version here.
- Self-hosted LiveSync version: <!-- e.g. 0.23.0 — find it in Obsidian Settings → Community Plugins -->
- Self-hosted LiveSync version: <!-- Find it in Obsidian Settings → Community plugins. -->
### Report and Logs from LiveSync
Perform a `Generate full report for opening the issue with debug info` command and provide the generated report. This contains detailed information and recent 1000 log lines, which is very helpful for debugging. **PLEASE AMEND THE REPORT TO REMOVE ANY SENSITIVE INFORMATION BEFORE PASTING.**
If too large to paste here, upload to [Gist](https://gist.github.com/) and share the link.
Run `Generate full report for opening the issue with debug info` and provide the generated report. It contains detailed information and up to 1,000 recent log lines. Review the complete output, and remove credentials, private remote details, Vault names, file paths, file contents, and other private information before sharing it.
If the report is too large to paste here, upload the redacted report to [Gist](https://gist.github.com/) and share the link.
<details>
<summary>Report and Logs (primary)</summary>
+7
View File
@@ -68,6 +68,13 @@ Each workflow establishes ordinary note synchronisation on the first device, gen
> CouchDB can also be run on a Raspberry Pi (please be mindful of your server's security).
### Third-party managed CouchDB hosting
> [!NOTE]
> The following is a third-party hosting option proposed by Zenith Hosting. It is not an official Self-hosted LiveSync service, and it is neither endorsed nor recommended by this project.
If you would rather not set up and maintain a server yourself, Zenith Hosting offers a managed CouchDB server which this plug-in can be configured to connect to: [Zenith Hosting](https://zenith.hosting/host/obsidian-livesync). As with any hosted service, your data will reside on a server operated by a third party, so please consider whether that is acceptable for your vault before using it.
## Information in the Status Bar
Synchronisation status is shown in the status bar with the following icons.
+2 -1
View File
@@ -33,7 +33,8 @@
const id = params.get('id');
const total = parseInt(params.get('n') || '0');
const index = parseInt(params.get('i') || '-1');
const data = params.get('d');
// Keep the chunk percent-encoded so URI delimiters remain part of the settings payload.
const data = hash.match(/(?:^|&)d=([^&]*)/)?.[1];
const app = document.getElementById('app');
+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
+2
View File
@@ -3,6 +3,8 @@
A fully self-hosted CouchDB stack for the [obsidian-livesync](https://github.com/vrtmrz/obsidian-livesync) plugin.
**No fly.io. No IBM Cloudant. No cloud accounts required for basic use.**
The optional [Coturn Compose starter](coturn/README.md) is a separate Linux-only service for P2P connectivity. It is not part of the CouchDB stack below.
> ✅ **Tested on Docker Desktop for Windows (Docker 29.2, Compose v5, WSL2 backend)** — full init, CORS, auth, and idempotent restart verified.
---
+11
View File
@@ -0,0 +1,11 @@
# Public DNS name used as the TURN authentication realm.
TURN_REALM=
# Public IPv4 address advertised by Coturn. If Coturn is behind NAT, forward
# port 3478 (TCP and UDP) and the UDP relay range to this host.
TURN_EXTERNAL_IP=
# Static long-term credential used by the LiveSync P2P profile. Use a simple
# username without a colon and a high-entropy password, such as hexadecimal.
TURN_USERNAME=
TURN_PASSWORD=
+1
View File
@@ -0,0 +1 @@
/.env
+81
View File
@@ -0,0 +1,81 @@
# Coturn starter for LiveSync P2P
This optional Compose project runs a small, static-credential TURN service for LiveSync P2P. It uses the upstream `coturn/coturn` image directly; the repository does not maintain a separate Coturn Dockerfile.
The starter is deliberately limited to a Linux server with a public IPv4 address, TURN over UDP and TCP on port 3478, and UDP relay ports 4916049200. It does not configure TLS, automatic certificate renewal, monitoring, quotas, or a managed credential endpoint.
## Before starting
Prepare:
- a Linux host with Docker Engine and the Compose plug-in;
- a public IPv4 address, either on the host or forwarded to it;
- a DNS name such as `turn.example.com`;
- firewall and NAT rules for TCP and UDP port 3478, and UDP ports 4916049200; and
- enough bandwidth for every relayed P2P transfer.
Docker host networking is intentional. Coturn's upstream image recommends it because forwarding a large relay port range through Docker performs poorly. This starter therefore does not support Docker Desktop.
## Configure and start
From this directory:
```sh
cp .env.example .env
chmod 600 .env
```
Set every value in `.env`. Generate a high-entropy password, for example:
```sh
openssl rand -hex 32
```
Use a simple username without a colon. The static username and password are passed to Coturn as process arguments. They are visible to a local Docker administrator, who already controls the host. The `.env` file is excluded from Git and should remain private. The resolved output of `docker compose config` also contains the credential, so do not publish it.
Validate the resolved configuration, then start it:
```sh
docker compose config
docker compose up -d
docker compose logs -f coturn
```
The pinned image version is deliberate. Review the upstream Coturn release notes and update the pin explicitly rather than following `latest` automatically.
## Configure LiveSync
Enter both client paths in the P2P profile's TURN server list:
```text
turn:turn.example.com:3478?transport=udp,turn:turn.example.com:3478?transport=tcp
```
Use `TURN_USERNAME` and `TURN_PASSWORD` as the TURN username and credential. Keep the normal `Automatic` ICE policy unless a future LiveSync release offers `TURN relay only` and the direct path needs to be excluded deliberately.
Both synchronising devices must be able to reach the server. Prove an explicit two-way `Replicate now` round trip on the intended networks before relying on the configuration.
The repository check can validate Compose expansion and local TURN allocations. It cannot prove the public firewall, NAT, carrier, or client path for a particular deployment. Validate both UDP and TCP from outside the server network.
## TLS and port 443 are advanced extensions
This starter does not recommend putting Coturn on port 443. Coturn cannot bind to the same IP address and TCP port as Caddy or another HTTPS entry point. In particular, it conflicts with the bundled CouchDB Caddy profile when both use the same host address.
If a restrictive network requires TURN over TLS on port 443, prefer a separate TURN host or a separate public IP address. An outbound tunnel used for CouchDB may also leave the host's public port 443 available for Coturn, provided that TURN uses a separate DNS record which resolves directly to that host. The tunnel itself does not carry TURN traffic.
A single public IP can technically be shared when one layer-4 TLS router owns port 443 and routes separate CouchDB and TURN hostnames by Server Name Indication (SNI). This adds another certificate and connection-routing boundary, depends on every intended TURN client supplying usable SNI, and is outside this starter. The standard Caddy image used by the bundled CouchDB profile does not provide that layer-4 routing.
TURN over TLS is not HTTP. An ordinary HTTP reverse proxy or Cloudflare Tunnel route is not a substitute for a TURN listener. Follow Coturn's upstream configuration guidance for `tls-listening-port`, `cert`, and `pkey`, arrange renewal and restart behaviour, and test the resulting `turns:` URL from outside the server network.
This starter disables Coturn's TLS listener and does not add a TURN-over-DTLS path, so it cannot appear to provide a secure TURN port without those operator-owned prerequisites. This does not disable the end-to-end DTLS encryption used by the WebRTC peer connection carried through TURN.
## Security and operations
- Rotate the static credential if the Setup URI, `.env` file, or credential is exposed.
- Treat TURN as an internet-facing bandwidth service and monitor traffic and logs.
- Add appropriate allocation and bandwidth quotas for a shared or public deployment.
- Keep the private-address restrictions unless the TURN server is intentionally permitted to relay to those networks.
- Keep independent Vault backups. TURN improves connection reachability; it does not store a backup of Vault data.
- A TURN operator can observe endpoint addresses, timing, and traffic volume even though LiveSync content remains end-to-end encrypted.
The authoritative image and configuration references are the [Coturn Docker image guide](https://github.com/coturn/coturn/blob/master/docker/coturn/README.md) and [Coturn server documentation](https://github.com/coturn/coturn/blob/master/README.turnserver).
+33
View File
@@ -0,0 +1,33 @@
name: livesync-coturn
services:
coturn:
image: coturn/coturn:4.17.2
restart: unless-stopped
network_mode: host
# Compose has already interpolated the environment values. Invoke Coturn
# directly so that the image's shell entrypoint does not expand them again.
entrypoint:
- turnserver
command:
- "-n"
- "--log-file=stdout"
- "--pidfile=/tmp/turnserver.pid"
- "--listening-ip=0.0.0.0"
- "--listening-port=3478"
- "--min-port=49160"
- "--max-port=49200"
- "--external-ip=${TURN_EXTERNAL_IP:?Set TURN_EXTERNAL_IP in docker/coturn/.env}"
- "--realm=${TURN_REALM:?Set TURN_REALM in docker/coturn/.env}"
- "--user=${TURN_USERNAME:?Set TURN_USERNAME in docker/coturn/.env}:${TURN_PASSWORD:?Set TURN_PASSWORD in docker/coturn/.env}"
- "--fingerprint"
- "--lt-cred-mech"
- "--stale-nonce=600"
- "--unauthorized-ratelimit"
- "--no-multicast-peers"
- "--denied-peer-ip=10.0.0.0-10.255.255.255"
- "--denied-peer-ip=100.64.0.0-100.127.255.255"
- "--denied-peer-ip=169.254.0.0-169.254.255.255"
- "--denied-peer-ip=172.16.0.0-172.31.255.255"
- "--denied-peer-ip=192.168.0.0-192.168.255.255"
- "--no-tls"
@@ -267,7 +267,7 @@ The Community directory scanner preview completes with no source-code errors. Th
The remaining source warnings belong to application code. Browser dialogue visibility now uses DOM state instead of inline static styling, so the earlier styling warning is absent. Direct diagnostic output was resolved at its existing ownership boundaries: Webapp components use an injected log function backed by `BrowserAPIService`, WebPeer retains output in its Svelte log store, Obsidian modules use the established Logger path, and duplicate console emission was removed from `ModuleLog`. The later Webapp and WebPeer recomposition around maintained Context and serviceFeature APIs should preserve these explicit output paths.
The final Community lint inventory for this boundary has no errors and 126 warnings: 67 sentence-case findings, 58 deprecated-API findings, and one declarative setting-definition suggestion. The sentence-case strings and deprecated interfaces are retained deliberately to avoid an unrelated localisation and host-lifecycle change. Declarative definitions would migrate the complete Obsidian setting tab into the 1.13 settings-search model; that is a separate visible UI project after LiveSync 1.0, not a hidden package-boundary release gate. Revisit each category through focused UI and compatibility work rather than suppressing the rules or treating the warning count as zero.
The final Community lint inventory for this boundary has no errors and 126 warnings: 67 sentence-case findings, 58 deprecated-API findings, and one declarative setting-definition suggestion. The sentence-case strings and deprecated interfaces are retained deliberately to avoid an unrelated localisation and host-lifecycle change. Declarative definitions would migrate the complete Obsidian setting tab into the 1.13 settings-search model; that is a separate visible UI project after LiveSync 1.0, not a hidden package-boundary release gate. Revisit each category through focused UI and compatibility work rather than suppressing the rules or treating the warning count as zero. The declarative-settings suggestion was subsequently addressed by [Adapt Standard Settings to Obsidian's Declarative API](2026_08_declarative_settings_adapter.md); the figures above remain the historical package-boundary inventory.
WebPeer's production build still reports that Vite externalises the guarded Node `crypto` fallback reached through a compatibility path. Browser execution selects `globalThis.crypto`, and the focused root, `context`, and `browser` bundle checks do not include the Node fallback, so this is not a leak in the reviewed public browser entries. Removing the compatibility-build warning requires a focused crypto-capability contract or a platform-specific implementation split and remains part of compatibility-surface narrowing.
@@ -275,7 +275,7 @@ The dependency preview also reports `uuid`, but the installed and locked graph r
The 1.0 dependency review found newly disclosed parser and denial-of-service advisories with compatible fixes. All locked `brace-expansion` generations now use their patched releases, including the production generation reached by Commonlib path matching and the CLI's user-configured ignore patterns. The development-only ESLint and Istanbul `js-yaml` generations likewise use patched releases. The production Markdown parser uses the patched `linkify-it` release to avoid quadratic processing of maliciously structured `mailto:` links, while the development-only JSON Schema toolchain uses the patched `fast-uri` release for unambiguous hostname parsing. A clean install and both complete and production-only `npm audit` checks no longer report these packages.
The remaining audit report is the existing `werift` and `werift-ice` dependency on `ip`, for which npm offers no patched version. The advisory concerns `ip.isPublic()` misclassifying unusual loopback representations. The locked werift implementation uses `ip` for address encoding, decoding, format detection, and loopback filtering, but does not call `isPublic()` or `isPrivate()`. LiveSync reaches werift only through the Node CLI's injected `RTCPeerConnection`; the Obsidian plug-in and browser applications use their platform WebRTC implementation, and the plug-in artefact does not contain werift. The package-level finding is therefore accepted for the 1.0 integration preview as a non-reachable advisory in the reviewed call path, not as a general waiver. Revisit it when werift or `ip` publishes a replacement, or before any change which delegates address trust, routing, or URL access decisions to that dependency.
The initial 1.0 dependency review also found `werift` and `werift-ice` pulling in `ip`, for which npm offers no patched version. The advisory concerns `ip.isPublic()` misclassifying unusual loopback representations. LiveSync reached werift only through the Node CLI's injected `RTCPeerConnection`, and the reviewed call path did not use `isPublic()` or `isPrivate()`. This temporary acceptance ended when werift removed the dependency: the CLI now uses `werift` 0.24.4 or later, the installed production graph contains no `ip` package, and `npm audit --omit=dev` reports no vulnerabilities.
The local real-Obsidian suite verifies the actual loaded `ObsidianServiceContext`, all 18 services, Vault reflection, CouchDB and Object Storage transfer, remote-activity accounting, CLI-to-Obsidian encrypted synchronisation, startup scanning, two-Vault create, update, delete, ordinary rename, case-only rename, target mismatch, Hidden File Sync, Customisation Sync, setting Markdown export, and two-device CouchDB, Object Storage, and P2P Setup URI workflows. These checks establish observable results and host composition rather than relying on declaration compatibility alone.
+1 -1
View File
@@ -34,7 +34,7 @@ Version 1.0 needs to distinguish supported opt-in features from previews and fro
- `xxhash64` is the current hash contract. Other hash algorithms remain available for existing databases and edge-case recovery, not as experimental alternatives for new Vaults.
- Eden chunks remain accepted at runtime and in transported settings, but are not offered in the settings interface.
- `doNotUseFixedRevisionForChunks` remains an inert compatibility input. Chunk revisions are always content-derived.
- The deprecated cleaned-database reconciliation callback remains internal while an old IndexedDB client may still encounter that remote state. It is not a user-selectable maintenance action and is omitted from the settings reference.
- The cleaned-database compatibility path remains internal while an old IndexedDB client may still encounter that remote state. It is not a user-selectable maintenance action and is omitted from the settings reference.
### Already removed
@@ -0,0 +1,329 @@
# Architectural Decision Record: CouchDB Remote Connection Ownership
## Status
Accepted. The first implementation is deliberately limited to the
abort-capable CouchDB connectivity preflight used by one-shot replication.
## Context
Commonlib opens a remote CouchDB database through `RemoteService.connect()`.
Before this decision, a successful call returned only a PouchDB handle and an
information snapshot:
```typescript
{
db: PouchDB.Database<EntryDoc>;
info: PouchDB.Core.DatabaseInfo;
}
```
The value did not state who must close the handle or how requests which outlive
the current operation are cancelled. Callers consequently accumulated their
own `try`/`finally` blocks and closing helpers. Commonlib PR 112 made finite
handle clean-up substantially safer, but closing a raw PouchDB handle still did
not define ownership of its outstanding HTTP work.
A remote PouchDB HTTP handle is not a dedicated socket. `db.close()` emits
PouchDB's normal `closed` event and closes the logical handle, but the HTTP
adapter does not establish that a pending browser fetch or response-body read
has stopped. `RemoteService.performFetch()` also records a physical request as
complete once response headers have arrived, while PouchDB may still be reading
and parsing the body. Neither the request counters nor raw `db.close()` can
therefore prove that the transport work has settled.
One-shot CouchDB replication performs the following work before it creates the
replication controller:
1. prepare the encryption security seed;
2. construct the remote PouchDB handle and read database information;
3. check and, where required, migrate the remote database version;
4. read and update compatibility Metadata, including the milestone document;
and
5. create the PouchDB replication operation.
The controller owned by `processSync()` begins only at step 5. A request which
never settles during steps 2 to 4 is outside that cancellation scope. The
shared one-shot result remains pending as well, so later triggers join the same
pending operation rather than beginning a fresh attempt.
Self-hosted LiveSync issue 1116 provides evidence of this failure shape. On one
Linux and Electron combination, a one-shot attempt remained pending while
writing the remote milestone document. Bypassing that write allowed the
attempt to reach later replication requests. A local real-Obsidian exercise
reproduced the preceding Fast Fetch state but not the indefinite write. The
evidence does not establish a milestone-specific defect, a CouchDB defect, a
browser connection-pool defect, or a lock cycle. It does establish that the
connectivity preflight lacks an owner which can terminate its abort-capable
transport work.
No code-level circular wait has been identified. `shareRunningResult()` shares
a logical promise, the remote-activity counters observe work, and the global
replication concurrency controller is entered only after the preflight. Adding
a semaphore or another connection lock would let a stalled request retain the
permit; it would not make that request settle.
## Decision
### Extend the existing connection result
Commonlib will define the owned connection as the existing flat result with one
additional operation:
```typescript
interface RemoteConnectionOpenOptions {
readonly signal?: AbortSignal;
readonly allowNativeFallback?: boolean;
}
interface OwnedCouchDBConnection<T extends object> {
readonly db: PouchDB.Database<T>;
readonly info: PouchDB.Core.DatabaseInfo;
close(): Promise<void>;
}
interface CouchDBReplicationConnection extends OwnedCouchDBConnection<EntryDoc> {
readonly syncOptionBase: PouchDB.Replication.SyncOptions;
readonly syncOption: PouchDB.Replication.SyncOptions;
}
```
`RemoteService.connect()` remains the entry point. It returns
`OwnedCouchDBConnection` directly; there is no nested resource wrapper, separate
lease API, public connection signal, or public `abort()` operation. The
connection and checked replication types are owned and documented by
Commonlib. Compatibility checks enrich the same connection with replication
options instead of creating another lifetime object.
The following properties are part of the contract:
- `close()` is idempotent;
- `close()` first cancels abort-capable requests scoped to the connection, then
calls PouchDB's ordinary `db.close()`;
- PouchDB retains its normal `closed` event behaviour;
- the optional input signal cancels the same internal request scope;
- skipping the information request retains the established placeholder in
`info` for source and behaviour compatibility;
- a close failure is reported diagnostically but does not replace the primary
replication result; and
- ownership of the connection does not imply ownership of a dedicated physical
HTTP socket.
The public connection does not expose its internal signal because no caller
needs to make a second shutdown decision. Owners either cancel through the
input signal or finish through `close()`. This leaves one operation responsible
for final clean-up.
Replacing the existing error-string union with a typed connection-failure
result remains desirable, but it is outside this change. Mixing that migration
into the first implementation would enlarge the consumer and user-message
surface without improving cancellation.
### Bind cancellation at the custom fetch boundary
`RemoteService.connect()` creates its internal request scope before PouchDB is
constructed, so the initial `db.info()` request is covered. The custom PouchDB
fetch implementation combines, without replacing, both applicable signals:
- the signal supplied by PouchDB for the individual request; and
- the signal for the owned connection, which follows its optional owner signal.
The combined signal remains applicable while the response body is consumed.
Receiving response headers is not the end of cancellation ownership.
Cancellation is not a CORS failure. If the connection scope has been aborted,
the request must not enter the diagnostic fallback from the web-compatible
fetch path to the native request API. A bounded owner can also disable that
fallback explicitly.
The web-compatible fetch path honours `AbortSignal`. Obsidian's current native
`requestUrl` adapter does not expose physical cancellation through
`RequestInit.signal`. The implementation must not use `Promise.race()` to
declare a native remote write cancelled while it may still complete. A bounded
guarantee therefore applies only where the selected request path remains
abort-capable.
### Transfer the same connection between owners
At every point, exactly one operation is responsible for calling `close()`:
1. `RemoteService.connect()` owns the connection until it returns successfully;
2. the connectivity preflight owns it while checking version and compatibility;
3. a successful preflight transfers the same connection to the one-shot or
continuous replication operation; and
4. the final replication owner closes it after replication settles or is
terminated.
A failed factory or preflight closes the connection in its own failure path. A
successful transfer clears the former owner's deadline before replication
continues. Borrowing `connection.db` does not transfer close responsibility.
`shareRunningResult()` owns no connection. It may share the result of an
operation which owns one, but that operation must settle and close the
connection before the shared entry can be released for a later attempt.
## Limited Introduction
The first bounded consumer is the CouchDB connectivity preflight reached from
one-shot replication. Its boundary includes:
- PouchDB construction and the initial `db.info()` request;
- the database-version check and migration negotiation; and
- compatibility and milestone reads and writes before replication starts.
The preflight receives an internal 60-second wall-clock deadline. This is a
last-resort safety fuse for an owner which would otherwise remain pending
indefinitely. It is not the expected completion time, a service-level target, a
per-request inactivity timeout, a user setting, a limit on replication
duration, or the `useTimeouts` changes-feed setting. Tests inject a shorter
deadline.
When that deadline expires on the web-compatible path:
1. the owner signal aborts the connection's request scope;
2. the preflight closes the connection;
3. no PouchDB replication operation is created;
4. the shared one-shot result settles as failed; and
5. a later trigger may create a fresh connection and attempt.
On success, the deadline is cleared before the connection is transferred to
replication, so an old timer cannot interrupt a healthy long-running transfer.
The explicitly selected native Request API retains its previous unbounded
behaviour because its host adapter cannot honour transport cancellation. The
security-seed preparation which precedes the shared one-shot operation is also
outside this first boundary. Fast Fetch, setup probes, maintenance commands,
status inspection, and other direct CouchDB consumers retain their existing
deadline and retry policies. They receive the additive `close()` contract but
are not silently given this one-shot deadline.
The first implementation does not add automatic retry. Retrying before the old
request is known to be cancelled could duplicate remote writes or consume more
connections without changing the failing condition.
## Ownership
The legacy `_ensureConnection()` method retains its raw PouchDB return type for
source compatibility. New Commonlib paths use an internal owned-connection
helper and do not discard the lifecycle object.
Commonlib owns:
- `OwnedCouchDBConnection`, `RemoteConnectionOpenOptions`, and
`CouchDBReplicationConnection`;
- composition of PouchDB request and connection cancellation signals;
- the PouchDB custom-fetch integration;
- idempotent connection close behaviour;
- ownership transfer within the CouchDB replicator; and
- timeout classification at the connectivity-preflight boundary.
Self-hosted LiveSync owns:
- the concrete Obsidian fetch adapters and their declared capabilities;
- user-facing logs or notices for a timed-out attempt;
- integration of an immutable Commonlib release; and
- real-Obsidian validation of the affected consumer path.
No host may claim physical cancellation unless its injected fetch
implementation honours the supplied signal.
## Non-Goals
This decision does not:
- identify the exact environmental cause reported in issue 1116;
- guarantee that a one-shot attempt succeeds;
- special-case the milestone document or its URL;
- impose a global connection limit or connection semaphore;
- add an automatic retry, fallback, or remote reconciliation policy;
- apply a deadline to ordinary replication, continuous changes feeds, Fast
Fetch, rebuilds, or bulk transfers;
- change CouchDB documents, checkpoints, encryption, the security seed, or
compatibility Metadata;
- make `close()` equivalent to closing a browser socket;
- detach a potentially mutating native request and report it as cancelled; or
- migrate every direct PouchDB borrower to the one-shot deadline policy.
## Alternatives Rejected
### Time out only the milestone write
The observed write is where one report stopped, not an established ownership
boundary. Another environment could stop at `db.info()`, version Metadata,
response-body parsing, or an adjacent compatibility request.
### Race the preflight without cancelling its transport
This would release `shareRunningResult()` while the old request remained able
to complete. It is especially unsafe for a remote `PUT`, because a later
attempt could begin after the first had been reported as failed.
### Add a global semaphore or lower the connection count
No connection-limit failure has been demonstrated. A stalled owner would
retain its permit indefinitely and turn an unexplained request into an explicit
queue deadlock.
### Add a separate lease wrapper
A nested `{ connection: { db, close }, info }` result would make ownership
visible, but it would duplicate the existing connection shape, force callers
through another projection, and expose lifetime operations which have no
consumer. Adding `close()` to the existing value preserves source compatibility
and keeps the PouchDB handle, information snapshot, and lifetime together.
### Share one global remote PouchDB handle
A singleton would couple setup, maintenance, one-shot, and continuous
lifecycles, make credential and setting changes harder to isolate, and turn one
stalled request into a process-wide resource.
## Verification
The regression tests were changed before the implementation. Against the old
flat result they demonstrated that:
- an owner signal left an in-flight request pending;
- the result had no `close()` operation capable of interrupting a body read;
- a bounded web-compatible request could enter the native fallback; and
- the one-shot path still depended on the discarded nested connection API.
After the change, focused Commonlib tests verify that:
- an owner signal settles a pending request;
- `close()` interrupts a response body read before closing PouchDB;
- repeated `close()` calls close the handle once;
- an aborted or bounded request does not enter the non-abortable native adapter;
- deadline expiry closes the same flat connection and releases the shared
one-shot attempt;
- a later invocation begins after that release;
- a successful preflight clears its deadline and transfers ownership; and
- close failures are logged without replacing timeout or replication results.
Type checking and package-boundary checks verify the Commonlib-owned
declarations and compatibility export. Self-hosted LiveSync must then validate
the exact packed Commonlib artefact with its focused consumer tests and an
ordinary real-Obsidian and CouchDB smoke test. The reporter's environment
remains the validation boundary for the original platform-specific symptom.
## Consequences
- A successful remote connection has one explicit close operation without a
parallel lease abstraction.
- Existing `{ db, info }` consumers remain source-compatible and may adopt
`close()` without changing their projections.
- One-shot connectivity can become a bounded failure on an abort-capable
transport instead of retaining the shared operation indefinitely.
- Native `requestUrl` cancellation remains an acknowledged gap rather than a
falsely satisfied contract.
- Other finite consumers can adopt owner signals and explicit `close()` one at
a time, with tests for their own side effects and retry policies.
## References
- [Bounded Remote Activity](2026_07_bounded_remote_activity.md)
- [Fast Fetch Persistence and Completion Semantics](2026_08_fast_fetch_persistence_and_completion.md)
- Commonlib PR 112, which closes finite remote PouchDB handles after their
logical owners settle
- Self-hosted LiveSync issue 1116, which reports a one-shot compatibility write
remaining pending on one Linux and Electron environment
@@ -0,0 +1,688 @@
---
date: 2026-08-25
commonlib-version: "0.1.19"
self-hosted-livesync-version: "1.0.18"
status: accepted
---
# Architectural Decision Record: Adapt Standard Settings to Obsidian's Declarative API
## Status
Accepted and implemented through Stage C1 and two bounded Stage C2
landing-page improvements. The shared specification remains deliberately
limited to one-key, immediately persisted controls. Complex pages retain their
existing renderers instead of being forced through a general abstraction.
Settings pending application which require database initialisation now delegate
their decision, scheduling, and restart boundary to `SetupManager` and
`Rebuilder`.
## Context
Obsidian 1.13 introduced declarative plug-in settings through
`PluginSettingTab.getSettingDefinitions()`. Declarative definitions are used for
native rendering, validation, navigation, and global settings search. When the
method returns a non-empty array, Obsidian does not call the existing
`display()` implementation.
Self-hosted LiveSync still supports Obsidian versions before 1.13 through its
`minAppVersion` of 1.7.2. It must therefore retain an imperative `display()`
fallback unless the minimum supported Obsidian version is raised separately.
Maintaining an unrelated declarative definition and imperative implementation
for each setting would allow the two interfaces to drift.
The current `LiveSyncSetting` AutoWire implementation combines several
responsibilities:
- Commonlib setting metadata supplies setting names, descriptions, maturity, and
configuration level;
- pane functions decide page and group membership, control type, options, and
conditional visibility;
- `LiveSyncSetting` creates and updates Obsidian DOM components;
- `ObsidianLiveSyncSettingTab` owns an editing buffer, dirty state, local and
persisted settings, and save operations; and
- selected controls add staged Apply behaviour, derived values, or effects
which run after a successful save.
These responsibilities are not all declarative setting data. In particular,
Remote Configuration, Hatch, Maintenance, Help, and Selector contain dynamic
lists, Svelte components, diagnostic results, multi-step actions, and
destructive confirmations. Encoding those interactions in a general settings
DSL would increase the abstraction before a second renderer had proved which
parts are genuinely shared.
Commonlib's `SettingInformation` setting metadata contains more entries than
the current settings interface exposes. Generating definitions from all of it
would therefore make compatibility or internal settings searchable merely
because they have labels. Page membership must remain an explicit LiveSync
decision.
The existing legacy settings wizard also changes DOM classes and selects panes
through `enableMinimalSetup()`. The maintained onboarding path now uses
`SetupManager`. The current call graph has no caller for
`askAgainForSetupURI()`: it is the only emitter of
`EVENT_REQUEST_OPEN_SETTING_WIZARD`, and that event's only handler calls
`enableMinimalSetup()`. The old route is therefore obsolete rather than a
second onboarding interface which the declarative renderer must preserve.
Historically, it was the second prompt after a user reported having no Setup
URI. It offered the in-settings wizard, P2P setup, manual settings, or a reminder
at the next launch, then stopped initialisation while the selected interface
took over. `SetupManager` now owns that decision and continuation.
## Decision
### Retire the obsolete in-settings wizard first
The old in-settings wizard will be removed as a focused prerequisite. This is
cleanup of an unreachable interface, not part of the declarative settings
model. The cleanup will remove:
- `askAgainForSetupURI()`, `EVENT_REQUEST_OPEN_SETTING_WIZARD`, its handler, and
`enableMinimalSetup()`;
- the `inWizard` completion branch in Sync Settings;
- the `isWizard`, `wizardHidden`, and `wizardOnly` styling contract;
- the General-page `Next` control and the already commented Remote
Configuration `Next` control;
- the now-unnecessary `wizardHidden` argument on the old pane builder; and
- message keys whose final production consumer is the removed route, followed
by the normal catalogue regeneration.
This cleanup does not affect `SetupManager`, Setup URI onboarding, QR-code
navigation, document-history navigation, or any other control which happens to
use the word 'Next'. The existing onboarding and ordinary settings E2E paths
must pass before the declarative work begins.
The English quick-setup documentation already describes the maintained
onboarding. Older localised quick-setup pages which still describe the removed
interface are documentation maintenance rather than a prerequisite for this
runtime cleanup.
### Use one explicit page catalogue
LiveSync will define one ordered page catalogue. It will be the sole source for
page identity, name, configuration level, visibility, and content ownership.
Each entry keeps the existing pane renderer for the legacy path and selects one
of two native content forms:
```typescript
type SettingsPageEntry = {
id: string;
name: () => string;
icon: string;
order: number;
level?: ConfigLevel;
content: "native" | "custom";
legacy: PaneRenderer;
};
```
In Stage C1, `native` identifies the Advanced proof page, whose definitions are
supplied by the adapter, and `custom` selects the shared lazy custom-page
factory. The catalogue will gain a per-page native factory only when a second
native page requires one; Stage C1 does not introduce that abstraction in
advance.
A native `items` page may mix groups of `SettingSpec` controls with Obsidian's
direct action, render, list, and nested-page definitions. A native custom
`SettingPage` is the final escape hatch when the page cannot yet be divided
safely. The imperative and declarative interfaces consume the same catalogue:
| Catalogue content | Obsidian before 1.13 | Obsidian 1.13 and later |
| ------------------------------------- | -------------------------------- | ------------------------------------------ |
| Standard `SettingSpec` | Render through `LiveSyncSetting` | Convert to a control definition |
| Native group, action, or rendered row | Use the existing pane renderer | Use `SettingDefinitionPage.items` |
| Full custom page | Use the existing pane renderer | Open a lazily created custom `SettingPage` |
This makes page names and visibility consistent without requiring every page
to migrate at once. Page names must be unique because Obsidian uses them for
nested navigation.
`SettingDefinitionPage` does not expose a separate icon field. The declarative
renderer therefore prefixes each native page name with the emoji already held
by the catalogue, while the imperative renderer continues to pass the same
emoji to its existing menu button. This preserves the established visual
identity without adding host-DOM manipulation.
`SettingDefinitionGroup` likewise exposes only a string heading. Root groups
therefore have semantic identifiers whose catalogue entries keep their emoji
and late-translated names separate. The adapter combines those fields only
when constructing the Obsidian definition, so callers select a group by its
identifier instead of repeating presentation strings.
### Compose the native landing page around common tasks
The declarative root is a composition of native groups and catalogue pages,
not a second flat copy of the legacy tab menu. General Settings contains the
native Appearance, Logging, and Extra menus child pages. Their standard
`SettingSpec` controls remain searchable without crowding the root. The small
Quick Setup actions are native action rows on the root. The old Setup child
page is not retained: its feature-level controls move to Extra menus, its full
reset moves to Maintenance, and its online guidance becomes Help and
troubleshooting. The pane-based interface exposes Quick Setup as a pane and
renders the same controls within General Settings.
Remote Configuration and Sync Settings remain catalogue pages. Obsidian's
native group contract permits navigable pages as group items, so both pages are
placed inside an explicit Synchronisation group. This keeps them near the top
for narrow mobile displays while preventing the unheaded page entries from
appearing to continue the preceding Quick Setup group. The root order reflects
the current task:
| Current state | First root sections |
| --------------------------- | ----------------------------------------------------------------------------------- |
| Synchronisation is inactive | Quick Setup, Synchronisation (Remote Configuration and Sync Settings), then General |
| Synchronisation is active | Synchronisation (Remote Configuration and Sync Settings), General, then Quick Setup |
Set up other devices follows the Quick Setup and General groups when the
plug-in is configured. The remaining destinations are grouped explicitly:
| Group | Pages |
| ------------------------ | ---------------------------------------- |
| Maintenance and recovery | Maintenance and Hatch |
| Extra features | Selector and Customisation sync |
| Advanced settings | Advanced, Power users, and Patches |
| Help and information | Help and troubleshooting, and Change Log |
This prevents Obsidian from presenting them as one undifferentiated 'Detailed
settings' continuation. Changing a setting which can move or reveal a page
requests a catalogue refresh after persistence. External setting reloads use
the same boundary. Constructing the definitions still performs no persistence,
service, file, database, or network operation.
The imperative renderer retains its existing default-page selection: Quick Setup for
an inactive configuration and General for an active configuration. The landing
composition is therefore a native 1.13 improvement rather than a behaviour
change for earlier supported Obsidian versions.
The custom `SettingPage` adapter class will be constructed lazily from the
1.13-or-later path. `SettingPage` may remain a normal runtime import because the
bundle reads Obsidian exports through its namespace object, but the import must
not be subclassed or instantiated while the module is loading. The factory
will first use `requireApiVersion("1.13.0")`, then verify that `SettingPage` is
available. Older supported Obsidian versions therefore continue to call the
imperative `display()` fallback without requiring a dynamic import or a
polyfill for host behaviour which does not exist in those versions.
The adapter sets `title` from the catalogue and renders pane content into the
host-provided `containerEl`. Its `hide()` boundary will unload the page-owned
`Component`, unmount Svelte and markdown content, and remove page-owned update
handlers. The parent tab's `hide()` remains a final cleanup boundary because
Obsidian does not guarantee a page-level `hide()` call when the host window is
destroyed.
Custom pages receive only the current page's `containerEl` and the existing
`addPanel` helper. They do not recreate the old top-level tab menu inside each
native page.
### Prefer native groups and searchable rows to full custom pages
An existing `addPanel` section maps naturally to a
`SettingDefinitionGroup`. Within that group:
- ordinary value controls use `SettingSpec`;
- a simple button operation may use `SettingDefinitionAction` directly;
- a Svelte control or a specialised Obsidian row uses
`SettingDefinitionRender` and returns its cleanup callback; and
- a truly dynamic collection may use `SettingDefinitionList` when its existing
behaviour already matches the list contract.
These Obsidian-specific definitions are written directly in the page adapter.
They are not added to the shared `SettingSpec` vocabulary. This keeps the shared
model small while allowing page, panel, and row names, descriptions, and aliases
to participate in settings search.
Search indexes the metadata on a definition; it does not infer searchable
entries from arbitrary DOM created inside a `render` callback. A whole panel
wrapped in one rendered row therefore provides panel-level search only.
Individual controls or actions require individual standard, action, or render
definitions when control-level search is worthwhile.
A full custom `SettingPage` remains acceptable for a workflow which cannot yet
be split without nesting several existing setting rows inside one synthetic
row. It is a compatibility escape hatch, not the default representation for
every complex pane.
### Keep `SettingSpec` intentionally small
`SettingSpec` describes only controls which have all of the following
properties:
- one explicit persisted setting key, excluding keys from `OnDialogSettings`;
- one standard toggle, number, or dropdown control in the first proof page;
- a value which is read from the current editing buffer;
- a change which can be persisted immediately through the existing
`saveSettings([key])` path; and
- no additional operation which must run after saving.
A representative, key-safe shape is:
```typescript
type PersistedBooleanSettingKey = Exclude<AllBooleanItemKey, keyof OnDialogSettings>;
type PersistedStringSettingKey = Exclude<AllStringItemKey, keyof OnDialogSettings>;
type PersistedNumericSettingKey = Exclude<AllNumericItemKey, keyof OnDialogSettings>;
type PersistedSettingKey = PersistedBooleanSettingKey | PersistedStringSettingKey | PersistedNumericSettingKey;
type SettingSpecBase<K extends PersistedSettingKey, C> = {
key: K;
control: C;
visible?: () => boolean;
disabled?: () => boolean;
aliases?: string[];
};
type SettingSpec =
| SettingSpecBase<PersistedBooleanSettingKey, { type: "toggle"; defaultValue?: boolean }>
| SettingSpecBase<
PersistedNumericSettingKey,
{
type: "number";
min?: number;
max?: number;
allowZero?: boolean;
}
>
| SettingSpecBase<
PersistedStringSettingKey,
{
type: "dropdown";
options: () => Record<string, string>;
}
>;
```
The initial union contains only the three control types used by the Advanced
proof page. Text and textarea controls will be added when a migrated page
provides a concrete use for them. Number validation is derived from `min`,
`max`, and `allowZero`, so the native and imperative renderers enforce the same
constraints without introducing an arbitrary validation language.
Names, descriptions, maturity labels, and placeholders come from the translated
Commonlib setting metadata by default. The native renderer appends the existing
maturity marker to `name`, and maps the description and supported placeholder
directly. Configuration level remains a page and renderer concern: the legacy
renderer retains its existing DOM classes, while the native page catalogue owns
page-level visibility. A mixed-level native group must provide an explicit
visibility predicate at that boundary rather than inferring one in the pure
control converter. The specification may override a label only where the
current interface already uses a deliberate product-specific label. Options
remain LiveSync owned because they can depend on the active remote, platform,
or language. A control which needs the current obsolete-row styling remains
custom because the native definition does not provide an equivalent per-row
class contract.
The catalogue explicitly lists each exposed key. It does not enumerate
`SettingInformation` automatically.
The following behaviours are outside the standard specification and remain a
custom row or custom page:
- `holdValue` and Apply buttons;
- `invert` bindings;
- password inputs;
- a control which maps one displayed value to several stored keys, such as
`syncMode`;
- a control whose save effect cannot remain an explicit tab-owned handler;
- button clusters, dynamic lists, Svelte components, rich diagnostic output,
or destructive actions; and
- styling which exists only to support the old wizard or tab menu.
This is a migration boundary, not a permanent prohibition. A second concrete
use may justify a focused extension, but the first implementation will not add
a generic action language, transaction language, or lifecycle hook system.
### Retain the existing editing and persistence owner
`ObsidianLiveSyncSettingTab` remains the owner of editing values and saves. The
first implementation will expose a small adapter over its existing methods
rather than move settings persistence into a new service.
For declarative controls:
- `getControlValue(key)` reads `editingSettings[key]`;
- `setControlValue(key, value)` resolves an explicitly registered standard
specification, updates the editing value, and calls `saveSettings([key])`;
- successful saves continue to pass through `saveLocalSetting()` or
`services.setting.saveSettingData()` as appropriate; and
- the tab calls `refreshDomState()` after a value changes when another
definition's `visible` or `disabled` predicate can depend on it.
An unknown key is an implementation error. The adapter must not fall through
to `plugin.settings`, because LiveSync does not use that conventional storage
shape.
Specification construction and `getSettingDefinitions()` must remain cheap
and side-effect free. Obsidian calls the method during search indexing and
again on updates; it must perform no file, database, network, or settings
write.
### Give imperative pages an explicit lifetime and refresh boundary
The present `display()` renders every pane together, so arrays of
`settingComponents`, controlled DOM updates, and `onSavedHandlers` can be
cleared and rebuilt as one unit. Native page navigation mounts one custom page
at a time. Reusing those arrays without a page boundary would leak updates from
a hidden page or remove effects which a staged edit still needs.
Each imperative render will therefore receive a small page scope containing:
- its `Component` lifetime;
- its `LiveSyncSetting` instances;
- its controlled DOM update functions; and
- its explicit cleanup callbacks for Svelte, markdown, and other mounted
content.
The legacy `display()` fallback uses one scope for the complete old tab. A
custom declarative page creates one scope when opened and disposes it when
hidden. This scope is renderer state and is not part of `SettingSpec`.
Pane-construction callbacks which are queued by the existing helpers run only
whilst the scope which requested them remains current. Closing or replacing a
page therefore cannot attach delayed controls or cleanup callbacks to its
successor.
Saved-setting effects remain owned by the tab session, not by a DOM page. The
existing handlers are unique by setting key, so `addOnSaved()` will replace the
handler for that key instead of appending duplicate closures when a page is
reopened. A later migration may declare those effects in a separate catalogue,
but they will not be added to the standard control specification merely to
support page navigation.
Direct calls to `this.display()` from pane code and the tab's own reload path
will be replaced by an explicit refresh request with one of two scopes:
- `page` re-renders the active custom page, or the legacy tab; and
- `catalogue` calls the declarative tab's `update()` so translated page names,
page visibility, and search definitions are rebuilt, or re-renders the
legacy tab.
Changing the display language or the Advanced, Power User, or Edge Case mode
uses a catalogue refresh. Dynamic Selector rows, Maintenance status, and
Hidden File Sync status use a page refresh. This keeps the renderer choice out
of pane actions and prevents a direct `display()` call from replacing native
declarative navigation.
### Preserve imperative rendering as a renderer
The existing AutoWire calls are not the shared model. Instead,
`LiveSyncSetting` becomes the legacy renderer for `SettingSpec` where a pane
has migrated. It continues to own DOM classes, dirty-value decoration, and
component updates for older Obsidian versions.
Unmigrated pane functions continue to call `LiveSyncSetting` directly inside a
custom page. This allows incremental migration without first rewriting their
behaviour.
The initial implementation must not modify Commonlib's setting metadata.
Commonlib owns setting identity and shared labels; LiveSync owns page placement,
Obsidian controls, persistence routing, and side effects.
### Use Advanced as the first proof page
The Advanced page is the first page whose groups contain only standard
`SettingSpec` controls. It provides a useful proof without introducing
unrelated workflows:
- number, dropdown, and toggle controls;
- translated Commonlib labels;
- minimum-value validation and a default value;
- CouchDB-dependent visibility; and
- configuration-level page visibility.
It has no current `onSaved` handler, Svelte component, staged Apply group, or
destructive action. General was not the first proof because changing the
display language re-renders the interface and other controls emit status events
after saving. After the standard binding was proven, these effects remained
explicit, tab-owned saved handlers while their one-key controls adopted
`SettingSpec`.
The first native activation does not also divide other pages into searchable
rows. It exposes their established pane renderers as custom pages, limited to
page-level search. A later, optional migration can replace an individual custom
page with standard, action, or rendered rows without changing the page
catalogue.
## Implementation Stages and Checkpoint
### Stage A: remove the old wizard
Complete the focused prerequisite described above. This removes a DOM contract
which would otherwise distort both renderers.
### Stage B: prove the shared standard-control model
The first declarative-settings change remains deliberately small. It will:
1. add the minimal `SettingSpec` type and pure conversion functions;
2. describe only the Advanced controls as specifications;
3. render those specifications through the existing `LiveSyncSetting` path;
4. prove conversion to Obsidian setting definitions with focused tests; and
5. retain the current `display()` behaviour, page menu, persistence owner, and
`minAppVersion`, without returning non-empty setting definitions.
Stage B does not enable the native declarative renderer. It proves that the
shared model can express a real page without first taking ownership of every
page's lifetime. Returning an empty definition array merely to silence review
output is not an outcome of this stage.
### Stage C1: activate the native catalogue
Activation is a separate checkpoint because it is the first cross-cutting
change. It will add the page catalogue, custom `SettingPage` adapter, scoped
imperative lifetime, renderer-neutral refresh operation, declarative control
read and write overrides, and non-empty definitions on Obsidian 1.13 or later.
The non-empty definition array replaces `display()` completely. Partial
activation is therefore not safe: all 12 existing pages must enter the native
catalogue together. Advanced is the only page represented by native groups in
this stage. The other 11 pages use their existing pane renderers inside lazy
custom pages. Obsidian versions before 1.13 retain the complete imperative
renderer and its menu.
The existing rebuild-required action remains available while navigating native
pages. Custom pages render the established action at their page boundary, and
the Advanced definition includes an equivalent action item whose visibility is
derived from the same dirty-state predicate. Both forms call the existing
`confirmRebuild()` owner rather than introducing another apply workflow.
This stage necessarily touches direct `display()` callers, saved-handler
ownership, and cleanup for Svelte and markdown content. It does not expand
`SettingSpec` to absorb those concerns merely to make activation appear
smaller.
### Stage C2: improve search coverage selectively
After activation, an individual custom page may be replaced with native groups,
actions, and rendered rows where the existing panel boundary maps cleanly to
Obsidian's definitions. The bounded improvement converts the General and
Logging controls and the simple Quick Setup actions, then organises Appearance,
Logging, and Extra menus as child pages of General Settings. It also removes
the now-misleading Setup child page and assigns its remaining responsibilities
to their existing owners: Extra menus, Maintenance, and Help and
troubleshooting. Further conversions remain optional follow-up work rather than
a condition of Stage C1. Complex workflows may remain custom pages indefinitely.
Stage C2 must not introduce a general action or lifecycle language. Each page
conversion should be justified by useful settings-search coverage and retain
the catalogue, persistence owner, and refresh boundaries established by Stage
C1.
### Centralise initialisation for settings pending application
Settings which cannot take effect safely through immediate persistence remain
in the settings tab's editing buffer. Their Apply action delegates to a focused
`SetupManager` dialogue which asks whether the next start should use existing
synchronisation data or the files in the current Vault. `SetupManager` reports
user cancellation and validation or reservation failure distinctly instead of
collapsing them into a boolean setup outcome. A settings-persistence exception
still propagates after `Rebuilder` removes the reserved flag.
`Rebuilder.scheduleFetch()` and `Rebuilder.scheduleRebuild()` are the only
owners of the corresponding flag files. They reserve the next-start operation
before the callback persists the edited settings, remove the flag if that
callback fails, and request the restart only after preparation succeeds. The
settings tab must not write those flag files or request the restart directly.
A user cancellation returns to a separate confirmation which offers either to
keep editing or to apply the settings without initialisation. This preserves
the former advanced fallback without presenting it as an equal data-source
choice. A validation or flag-reservation failure is not a cancellation and
must not offer that bypass. The pending action is present on the native root
settings page as well as within custom and native child pages.
## Verification
Stage A runs the maintained onboarding E2E scenario and an ordinary settings
navigation scenario. A source check confirms that no old wizard event, state,
class, or message consumer remains.
Stage B focused unit tests verify:
- only explicitly listed Advanced controls become specifications;
- synthetic `OnDialogSettings` keys cannot be standard specifications;
- control type, options, defaults, validation, metadata, and visibility map
consistently to the legacy and native representations; and
- rendering the Advanced specifications through `LiveSyncSetting` preserves
the current save behaviour.
Stage C1 and the landing-page focused unit tests verify:
- the page catalogue contains all 13 pane-based destinations with stable, unique
identifiers and names;
- Appearance, Logging, Extra menus, and Advanced are native-items child pages,
and ten child pages retain custom factories;
- inactive and active configurations use their specified landing-page order;
- Remote Configuration and Sync Settings remain native navigable pages inside
the separate Synchronisation group;
- maintenance, extra features, advanced settings, and help have explicit page
groups, and the old Setup child page is absent;
- all eight General and Logging controls are registered once, with their
existing conditional visibility;
- the three Extra menus controls are registered once and refresh page
visibility after persistence;
- every standard setting key is registered once;
- a setting pending application exposes its Apply action on the native root
page;
- cancelling the initialisation choice preserves the editing buffer, while the
separately confirmed settings-only path persists it;
- Fetch and Rebuild reserve their flag through `Rebuilder` before pending
settings are persisted, and a reservation failure leaves them unapplied;
- reads use the editing buffer;
- writes use `saveSettings([key])` and never `plugin.settings`;
- custom pages dispose their page-owned resources and do not duplicate saved
handlers when reopened; and
- importing and opening the imperative fallback does not evaluate or require
`SettingPage` on Obsidian before 1.13.
Real-Obsidian verification on 1.13 or later confirms:
- the common landing controls and actions render before native page navigation;
- the Quick Setup action opens the maintained onboarding dialogue;
- Remote Configuration remains visible without initial scrolling in mobile
test mode;
- native page navigation opens every remaining child page;
- Advanced controls appear in global settings search;
- Advanced values persist and are restored after reopening settings;
- CouchDB-dependent controls and Advanced-mode visibility update correctly;
- a representative custom page, including its cleanup, still works;
- page and catalogue refreshes preserve native navigation and the
rebuild-required action; and
- no duplicate save, update handler, or saved-setting effect occurs after
leaving and reopening a page.
The maintained real-Obsidian settings scenario also changes a setting which
remains pending until initialisation, captures the source-choice and
settings-only fallback dialogues, and confirms that keeping the setting
pending does not persist it. It mounts the P2P variant directly to confirm that
it offers a source device and local Vault preparation without presenting a
central-server overwrite operation.
Because the manifest continues to support earlier Obsidian versions, a
pre-1.13 real-runtime smoke test must confirm that the imperative fallback
still opens, navigates, saves one Advanced value, and opens one custom page. If
the maintained E2E runner cannot install that runtime, the exact manual version
and procedure must be recorded before the implementation is merged.
The current real-Obsidian runner defaults to Obsidian 1.12.7, so it owns the
fallback smoke path. The declarative path uses a separately installed
1.13-or-later AppImage selected through `OBSIDIAN_BINARY` and `OBSIDIAN_CLI`.
`E2E_OBSIDIAN_SETTINGS_ONLY=true` limits that run to the settings contract so
the same scenario can validate a second Obsidian runtime without repeating its
unrelated compatibility-review and mobile-layout coverage.
Existing E2E scenarios must use one shared settings-page navigation helper.
That helper uses the current `.sls-setting-menu-btn` contract on the legacy
runtime and accessible native page names on 1.13 or later. Individual scenarios
must not duplicate version checks or retain selectors for a menu which the
declarative renderer does not create.
The initial Stage C1 implementation was exercised against the official Obsidian
1.13.4 arm64 AppImage with SHA-256
`20d0b13c6d40bb3d7e73d9b4be6d2e21dfcc145b2106a747d0c1b81e651dabfe`.
That run opened all 12 pages from the native page catalogue, found the Advanced
control through global settings search, persisted a numeric value on Enter,
and restored it after the settings dialogue was closed and reopened. The
complete default scenario also passed on Obsidian 1.12.7, including
compatibility review, mobile layout, imperative page navigation, and immediate
persistence of the same Advanced value. The shared E2E navigator owns both the
separate settings renderer used by Obsidian 1.13 and the legacy
`.sls-setting-menu-btn` interface.
The Stage C2 landing composition was then exercised on Obsidian 1.13.4. With
synchronisation inactive, the real interface rendered Quick Setup, a separate
Synchronisation group containing Remote Configuration and Sync Settings, and a
General Settings group containing Appearance, Logging, and Extra menus in the
specified order. It opened all 14 nested settings pages, found the Advanced
control through global settings search, and restored its saved value after
reopening settings. In mobile test mode, Remote Configuration remained inside
the initial viewport below the two Quick Setup actions and the Synchronisation
heading. The complete scenario also passed with the same bundle on Obsidian
1.12.7, confirming that the imperative fallback retained its navigation and
save behaviour.
## Expansion Checkpoints
Review the scope with the maintainer before any implementation adds one of the
following:
- a generic representation of actions, confirmations, rebuilds, or service
lifecycles;
- staged multi-setting transactions in `SettingSpec`;
- a replacement for the current onboarding workflow;
- a Commonlib setting metadata contract change;
- a minimum Obsidian version increase; or
- further conversion of Remote Configuration, Hatch, Maintenance, Help, or the
Svelte-based Selector controls.
These may become worthwhile after the first proof, but none is required to
establish a shared standard-control model and native settings search.
## Alternatives Rejected
### Return an empty definition array
This can satisfy a syntactic lint check while retaining `display()`, but it
does not add native settings search or prove a migration path.
### Generate every setting from Commonlib setting metadata
Metadata does not define current page membership, control type, options,
visibility, save policy, or whether a compatibility key should be exposed.
Automatic generation would expose settings which the current interface omits.
### Teach `LiveSyncSetting` to run against a simulated DOM
The existing class is a renderer with direct component and element access.
Making it emulate declarative output would preserve its mixed responsibilities
and make the new API depend on implementation details of the old one.
### Model every pane before adopting the API
This would require a general action and lifecycle language for approximately
50 buttons, several dynamic lists, five Svelte-based regular-expression
controls, and multiple recovery workflows. The resulting framework would be
larger than the standard-control problem it is intended to solve.
## References
- [Migrate to declarative settings](https://docs.obsidian.md/plugins/guides/migrate-declarative-settings)
- Obsidian `PluginSettingTab`, `SettingDefinitionItem`, and `SettingPage` type
declarations from the dependency version locked by this repository
@@ -0,0 +1,351 @@
# Architectural Decision Record: Fast Fetch Persistence and Completion Semantics
## Status
Accepted
## Context
Fast Fetch accelerates Fast Setup (Simple Fetch) by reading CouchDB's continuous
changes feed directly, decrypting each document, and writing batches to the local
database. It then allows LiveSync to reflect the completed database into the
Vault.
This path deliberately bypasses PouchDB's ordinary replication machinery. It
must therefore reproduce the correctness guarantees on which the rest of the
initialisation workflow relies:
- a remote document is decrypted and validated before it is written locally;
- every result from a batch write is checked;
- a checkpoint represents the last contiguous remote sequence which is durable
in the local database; and
- successful completion requires CouchDB to terminate every finite changes page,
every returned row to be durable, and a subsequent normal probe to report no
available rows.
The existing implementation combines line parsing, decryption, persistence, and
completion checks within one broad error handler. A decryption or persistence
failure can consequently be reported as a malformed JSON line and skipped. A
batch write can also resolve while containing individual failed results. In both
cases the stream may continue, advance its checkpoint incorrectly, or wait
indefinitely for a completion condition which the failed row would have
satisfied.
Fast Fetch also reads the normal changes feed before opening each continuous
page. A normal response's `pending` value counts items which remain after the
response's `results`, so `pending` alone is not the available workload. With a
one-row probe, the page can contain `results.length + pending` rows.
The documented `limit=0` behaviour cannot be used as a portable zero-payload
probe. CouchDB's API documentation says that `limit=0` has the same effect as
`limit=1`, while CouchDB 3.5.0 with a two-shard database was observed to return
no result rows and leave the complete count in `pending`. Fast Fetch therefore
uses an explicit one-row normal probe and includes no document bodies.
Finite continuous-feed completion differs across supported CouchDB releases.
CouchDB 3.5.0 was observed to close a heartbeat-enabled feed with a
`{ "last_seq": ... }` line when its finite `limit` is met. CouchDB 3.2 instead
continues to wait for database updates after the limit has been consumed. With
a heartbeat configured, each wait emits another heartbeat and the page can
remain open indefinitely, even after every requested row has arrived.
On CouchDB 3.2, an explicit `timeout` without a heartbeat has different
semantics from a total request deadline. Shard-result waits may emit blank
keep-alive lines and continue processing. Once the currently available changes
have been exhausted and the feed is waiting for another database update, the
timeout stops that wait and returns the feed-level `last_seq`. The timeout can
therefore terminate a finite page without limiting the duration of an active
page transfer.
The CouchDB sequence token is opaque and must be handled using CouchDB's
sequence semantics, without parsing, ordering, or comparison. On clustered
CouchDB, a changes row and the feed-level `last_seq` may encode related
positions with different opaque tokens. Separate requests are also not one
locked snapshot: their rows may be partially ordered, and replica failover may
repeat changes. Fast Fetch must therefore be idempotent and must use each
terminal `last_seq` only by returning it to CouchDB as the next `since` value.
## Decision
### Remote page sizing and completion
Fast Fetch obtains an approximate progress target from a normal changes-feed
request with `since=now`, `limit=1`, and `include_docs=false`. This token is for
progress reporting only. It is not compared with any other token and is not
used as a completion checkpoint.
Before every bounded page, Fast Fetch requests a normal changes feed from the
current durable cursor with `limit=1` and `include_docs=false`. The probe and
the following continuous page use the same `since`, style, and filter
selection. Reading the probe does not consume rows from CouchDB; the continuous
request starts again from that same cursor.
The number currently available is `results.length + pending`. If it is zero,
Fast Fetch is caught up and completes without opening another stream. Otherwise,
the next continuous request uses the smaller of that count and 10,000 as its
finite `limit`.
Each finite page omits `heartbeat` and sets `timeout=1000`. This lets CouchDB
3.2 return the page's terminator one second after it exhausts the currently
available changes, rather than keeping the request open for future writes. The
client immediately reconnects from that terminator while another normal probe
reports available work. This bounded cycle also preserves the intent of the
earlier iOS and iPadOS heartbeat workaround: Fast Fetch no longer depends on a
silent continuous request eventually closing at CouchDB's default 60-second
timeout.
The probe and page are separate HTTP requests, not a transactional snapshot.
New writes, replica selection, or administrative changes may alter the rows
between them. A page which returns at least one row and a valid terminator may
therefore be shorter than the probe's estimate. Fast Fetch persists that page
and probes again. A page which terminates without making progress after a
positive probe is a retryable transport failure, avoiding an unbounded busy
loop.
Each continuous request ends with its own `{ "last_seq": ... }` line. Fast
Fetch treats that line separately from a changes row, flushes and validates all
preceding local writes, and only then persists the opaque `last_seq`. The exact
value is replayed as the next request's `since`; it is never parsed, ordered, or
compared with a row's `seq`, another request's `last_seq`, or the database's
`update_seq`.
The limit counts outer changes-result rows. A tombstone is one row and consumes
one page slot even when no document body is present. With `style=all_docs`,
multiple leaf revisions inside one row's `changes` array do not consume
additional slots. Changing `include_docs` between the lightweight probe and the
document-bearing continuous page changes the payload, not the row selection.
### Processing and persistence
Each non-blank line from the changes feed is processed through these ordered
stages:
1. parse and validate the changes-feed row;
2. decrypt and validate its document, when a document is present;
3. add the document to the pending local batch;
4. persist the batch;
5. inspect every result returned by the batch write; and
6. after the finite page ends, persist its `last_seq` terminator.
With `new_edits: false`, PouchDB follows CouchDB behaviour and may omit successful
results. Fast Fetch therefore inspects every returned result and treats any
error result as a failed batch. An empty result is valid when PouchDB accepted
the complete batch.
The checkpoint may advance only to the last contiguous sequence for which all
preceding documents are durable. A row which legitimately requires no local
write may advance the checkpoint only after any preceding buffered documents
have been flushed successfully. A page's `last_seq` is committed under the same
rule before that page reports success.
If a batch is partly written, its checkpoint is not advanced. Retrying the batch
with `new_edits: false` is expected to be idempotent, including for documents
which were accepted during the first attempt.
Blank heartbeat lines are ignored. Malformed rows are failures; they are not
silently skipped. Logs may describe the stage and sequence involved, but must
not include the raw changes-feed line because it may be large or sensitive.
Each finite continuous changes request and its decoded reader must be terminated
on every exit. Releasing a reader lock alone does not cancel the underlying
request. Failure and completion paths therefore abort the request and attempt
to cancel the reader before the bounded remote-activity scope ends.
### Failure classification and retry
The streaming boundary returns a small structured failure with a stage and an
explicit retryability decision. The initial stages are:
```typescript
type StreamingFetchFailureStage = "transport" | "authentication" | "protocol" | "decryption" | "storage";
```
This type is an internal behavioural contract, not a user-interface status
enumeration. It may carry safe diagnostic context, such as an HTTP status or a
sequence token, without carrying document content.
Only explicitly recognised transient transport failures are retried
automatically. Examples include an interrupted connection, HTTP 408, HTTP 429,
and selected HTTP 5xx responses. Authentication, protocol, decryption, and local
storage failures are terminal by default. A future implementation may recognise
a narrower retryable case, but it must do so explicitly rather than retry every
exception.
Each retry resumes from the last durable contiguous checkpoint. Retry exhaustion
returns an actionable classified failure to the caller.
### Initialisation lifecycle
Fast Fetch success is the only path which may continue with the offline scan,
finish the rebuild, resume Vault reflection, clear the Fast Fetch checkpoint,
remove the flag file, or forget the remembered initialisation choice.
The LiveSync initialisation boundary uses an explicit suspension policy.
Ordinary Fetch and Rebuild resume Vault reflection when they finish, SCRAM keeps
file watching suspended, and Fast Fetch keeps both file watching and replication
result parsing suspended only when initialisation fails. Fast Fetch asserts both
suspensions before it begins and owns their final state: success clears both,
whereas a false result or exception sets both. This final assignment also covers
a late failure after rebuild finalisation and the legacy
`doNotSuspendOnFetching` path.
On failure:
- the local checkpoint and any durably fetched documents are retained for a
later retry;
- the local database is not marked as resolved;
- Vault reflection remains suspended;
- the offline scan and rebuild finalisation are not run; and
- the flag file and remembered initialisation choice remain available so that
restart recovery can offer the same operation again.
Any bounded remote-activity scope must still be released in a `finally` path, as
defined by [Bounded Remote Activity](2026_07_bounded_remote_activity.md). Keeping
Vault reflection suspended does not permit a wake lock or similar resource to
leak.
## Ownership
The responsibilities are divided at three injectable boundaries.
### Streaming Fetch
The Commonlib streaming implementation owns HTTP response validation, NDJSON
parsing, invocation of the decryption delegate, batch-write result validation,
contiguous checkpoint advancement, finite-page completion, and classified
failures. It does not know about the Vault, setup dialogues, flag files, or
LiveSync settings.
### Rebuilder
The Commonlib rebuilder owns the local database lifecycle, checkpoint storage,
retry policy, marking a completed database as resolved, optional resumption of
reflection, and final checkpoint removal. It does not parse changes-feed rows or
interpret user-interface choices.
### LiveSync Fast Setup
LiveSync owns the setup choices, suspension of initial Vault reflection,
invocation of Fast Fetch, the success-only offline scan and rebuild finalisation,
and cleanup of flag files and remembered choices. It does not reinterpret
document, encryption, or storage failures as successful setup.
## Non-Goals
This decision does not:
- change the Metadata and Chunks formats, encryption scheme, security seed, or
path obfuscation;
- change ordinary PouchDB replication, Standard Fetch, or offline-scan
semantics;
- provide a transaction spanning CouchDB and the local database;
- skip corrupt or unreadable documents and continue with a partial database;
- add an automatic fallback from Fast Fetch to Standard Fetch;
- define the detailed failure dialogue or other setup user-interface changes;
or
- provide a transaction or locked snapshot across the normal probe and the
following continuous page.
An explicit Standard Fetch choice remains available when a user needs the
ordinary replication path. Any automatic fallback or richer recovery dialogue
requires a separate decision because it changes user-visible setup behaviour.
## Verification
The implementation is verified primarily with London School interaction tests,
using mocks at each owned boundary to prove collaboration and call order.
### Streaming Fetch unit tests
Inject the HTTP stream, decryption delegate, local batch writer, and checkpoint
writer. Verify that:
- the order is decrypt, persist, inspect results, then checkpoint;
- parsing, decryption, and batch-result failures prevent checkpoint advancement
and completion;
- a partly failed batch leaves the checkpoint unchanged and reports a storage
failure;
- rows without a local write flush earlier buffered documents before advancing;
- a returned probe row is counted in addition to `pending`, including when
`pending` is zero;
- every probe uses `limit=1`, excludes document bodies, and is repeated from the
previous page's opaque terminator;
- each bounded page omits `heartbeat`, uses `timeout=1000`, and can complete
under CouchDB 3.2 after its current rows have been delivered;
- deletion and document-less rows consume a page slot;
- a row count cannot complete a page without its `last_seq` terminator;
- a page terminator cannot advance the checkpoint before its batch is durable;
- a final row and `last_seq` with different opaque representations complete
normally without a token comparison;
- a shorter valid page is persisted and followed by another probe, while a
zero-row page after a positive probe fails without looping;
- workloads over 10,000 rows resume from each durable finite-page checkpoint;
- authentication and malformed-protocol responses are terminal;
- recognised transient transport failures are classified as retryable; and
- diagnostics do not log the raw changes-feed line.
### Rebuilder unit tests
Inject the streaming operation and lifecycle collaborators. Verify that:
- transient failures retry from the latest durable checkpoint;
- terminal failures are attempted once;
- success marks the database as resolved, resumes reflection when requested, and
clears the checkpoint in that order; and
- failure does not mark the database as resolved, resume reflection, or clear
the checkpoint.
### LiveSync orchestration unit tests
Inject Fast Fetch, the offline scanner, rebuild finalisation, and cleanup
collaborators. Verify that failure performs none of the success-only actions and
leaves initial Vault reflection suspended. Verify that success retains the
existing setup sequence and cleanup.
### Integration and E2E tests
Commonlib's CouchDB integration test remains responsible for the real HTTP
changes feed, opaque sequence tokens, deletion rows, and local batch
persistence. It should use the maintained CI CouchDB release, a two-shard
database, and a data set large enough to cross a local batch boundary, and
confirm that the final checkpoint can be passed back to CouchDB as `since` with
no result rows or pending changes. The test must not compare that token's
representation with a separately requested target or changes-row token. The
focused regression test covers CouchDB 3.2's page-tail behaviour; compatibility
with a real CouchDB 3.2 server can be confirmed manually without expanding the
permanent CI matrix.
LiveSync's real Obsidian Setup URI workflow remains responsible for the actual
Fast Fetch selection, E2EE passphrase, Vault reflection, ordinary file round
trip, and hidden-file synchronisation. Injected parsing, decryption, and
persistence failures remain unit-test responsibilities; repeating them through
the real Obsidian E2E does not add coverage for an unchanged framework boundary.
This follows [Real Obsidian E2E](2026_06_real_obsidian_e2e.md).
## Consequences
- A deterministic document failure which previously appeared to be skipped now
fails Fast Fetch. This is an intentional safety improvement because the local
database is known to be incomplete.
- Partial durable work and its contiguous checkpoint can be reused by a later
attempt without exposing the partial database to the Vault.
- Retry delays are no longer spent on authentication, corrupt content, protocol,
or local persistence failures which cannot repair themselves.
- Progress totals remain approximate and may grow when a later probe observes
new work, without affecting correctness.
- A completed page can spend up to one second waiting for its terminator before
Fast Fetch probes and reconnects. Active page transfer is not constrained to
one second.
- The implementation requires coordinated changes in Commonlib and LiveSync.
Commonlib remains the authoritative package for streaming and rebuilder
behaviour; LiveSync consumes an immutable Commonlib release and owns its setup
orchestration.
- Ordinary replication remains unchanged and continues to provide the reference
correctness contract for decrypting, persisting, and checkpointing replicated
documents.
## References
- [Apache CouchDB changes-feed API](https://docs.couchdb.org/en/stable/api/database/changes.html)
- [Apache CouchDB 2.0 upgrade notes for opaque update sequences](https://docs.couchdb.org/en/stable/whatsnew/2.0.html#upgrade-notes)
- [Apache CouchDB replication protocol](https://docs.couchdb.org/en/stable/replication/protocol.html)
@@ -0,0 +1,78 @@
# Architectural Decision Record: Fast Fetch Transport Eligibility
## Status
Accepted
## Context
Fast Fetch accelerates Fast Setup (Simple Fetch) by reading bounded pages from
CouchDB's continuous changes feed. It consumes each response incrementally,
persists documents while the page is still arriving, and cancels the underlying
request when the page completes or fails.
The CouchDB setting `useRequestAPI`, labelled 'Use Internal API', routes ordinary
replication through Obsidian's `requestUrl` API to avoid browser CORS
restrictions. This API exposes a completed response as text, JSON, or an
`ArrayBuffer`; it does not expose the network response progressively or accept
the Fetch API's `AbortSignal`. Wrapping its result in a `Response` does not
restore those transport properties.
Custom headers can cause a browser preflight, and an authenticating proxy may
reject that preflight before the requested header values are sent. Custom
headers do not, however, make Fast Fetch intrinsically incompatible. A server
with correctly configured CORS can accept the same headers through the ordinary
Fetch API and retain streaming behaviour.
Ordinary PouchDB replication has a different response contract. Standard Fetch
uses finite batches, while LiveSync uses long-poll responses whose change
payload is bounded by the replication batch size. Both can process each response
after it has completed and do not depend on progressively reading a
document-bearing continuous feed.
## Decision
Fast Fetch requires a Fetch-compatible transport which exposes the response
body progressively and honours request cancellation.
When `useRequestAPI` is enabled for a CouchDB remote, Fast Fetch falls back to
Standard Fetch before entering the Fast Fetch activity or resetting the local
database through the Fast Fetch path. The presence of custom headers alone does
not disable Fast Fetch. Once Standard Fetch resets the local database, it
invalidates any retained Fast Fetch checkpoint for that database.
Commonlib's Rebuilder owns this eligibility decision because it owns both Fast
Fetch and the existing Standard Fetch fallback. The streaming implementation
does not receive Obsidian's buffered request adapter, and LiveSync does not add
proxy-specific or Cloudflare-specific policy.
## Consequences
- Initial retrieval through Standard Fetch may be slower and issue more HTTP
requests because PouchDB uses the configured batch size, document retrieval,
and checkpoint operations. The decision does not assume that `requestUrl` is
faster; its benefit here is compatibility with connections which browser CORS
would otherwise reject.
- LiveSync remains supported with `useRequestAPI`. Its HTTP adapter uses
long-poll responses whose change payload is bounded by the replication batch
size, rather than the document-bearing stream required by Fast Fetch.
- A user whose server accepts the configured custom headers through correct
CORS handling can leave `useRequestAPI` disabled and continue to use Fast
Fetch.
- The decision can be revisited if Obsidian provides a progressively readable,
cancellable internal request API, or if a separately designed buffered
transport establishes explicit payload bounds and equivalent cancellation
semantics.
## Verification
Commonlib unit tests verify that `useRequestAPI` selects only the existing
Standard Fetch activity, does not invoke Streaming Fetch, and invalidates any
retained Fast Fetch checkpoint after the local database is reset. Existing
tests continue to verify that custom headers are passed to Fast Fetch when
`useRequestAPI` is disabled.
## References
- [Fast Fetch Persistence and Completion Semantics](2026_08_fast_fetch_persistence_and_completion.md)
- [Apache CouchDB changes-feed API](https://docs.couchdb.org/en/stable/api/database/changes.html)
@@ -0,0 +1,179 @@
# Architectural Decision Record: P2P Transport Compatibility Controls
## Status
Accepted — the user-facing controls will be introduced in stages. This record defines their boundaries before Commonlib settings and LiveSync interfaces are changed.
## Context
WebRTC connectivity depends on both devices, their browsers or embedded WebViews, NAT behaviour, carrier networks, VPNs, firewalls, and the path between them. A configuration which works on desktop Wi-Fi may fail on a mobile carrier, and moving the same devices through a mesh VPN may change the result without changing LiveSync.
The current P2P transport has several relevant properties:
- Trystero supplies the Nostr signalling strategy and the browser-owned WebRTC connection.
- Commonlib limits one RPC wire payload to 15,360 bytes so it remains below Trystero's own action-chunk boundary.
- Trystero supplies ordinary STUN servers and accepts an optional TURN server list with one username and credential.
- ICE chooses a direct, server-reflexive, or TURN-relayed path automatically.
- Commonlib can collect raw WebRTC statistics, but LiveSync does not yet present the selected candidate route in a concise diagnostic result.
Issue reports suggest that reducing the application payload may improve some mobile and constrained-network paths. A VPN such as Tailscale may also turn an unreliable route into a reliable one. These observations are consistent with NAT, path-MTU, fragmentation, or intermediary behaviour, but they do not prove one universal cause. Browser WebRTC implementations retain responsibility for SCTP, DTLS, ICE, packetisation, congestion control, and retransmission.
One low-level number cannot represent all of these concerns. Users need a small set of meaningful compatibility choices, while transport-internal controls which cannot be selected safely should remain implementation details.
## Decision
### Message-size presets
LiveSync will expose a `P2P message size` choice with four presets:
| Label | Maximum RPC wire payload | Intended use |
| ----------------------- | -----------------------: | ------------------------------------------------------------------------------- |
| `Standard` | 15,360 bytes | Existing default and best throughput. |
| `Reduced` | 2,048 bytes | First compatibility step for an unreliable path. |
| `Conservative` | 1,024 bytes | Stronger compatibility at greater framing and processing cost. |
| `Maximum compatibility` | 800 bytes | Most conservative offered value for paths suspected of dropping larger packets. |
This value limits Commonlib RPC wire payloads before Trystero applies its own framing. It is not a LiveSync file Chunk size, an IP MTU, an SCTP fragment size, or a guarantee that lower layers will avoid fragmentation. The smaller presets reduce the amount presented to the transport at once and trade throughput for compatibility.
The bound applies to outgoing messages. A device which only lowers its own value still receives messages produced under the sender's value. The selected preset therefore belongs to the P2P profile and is included in an encrypted Setup URI for additional devices. A device which was configured earlier must be changed separately; the interface and troubleshooting guidance must state that the same conservative preset should be selected on every participating device. An absent key preserves the current 15,360-byte default.
Automatic negotiation or fallback between presets is deferred. A failed ordered data channel may require connection replacement before a smaller retry can prove anything, and changing transport parameters during a replication session would broaden the lifecycle contract considerably. The first implementation remains explicit, stable for one room lifetime, and inspectable.
### Connection path
LiveSync will expose a separate `Connection path` choice:
- `Automatic` retains normal ICE selection and is the default.
- `TURN relay only` supplies `iceTransportPolicy: 'relay'` and prevents direct or server-reflexive candidates from being selected.
`TURN relay only` is enabled only when at least one syntactically valid `turn:` or `turns:` URL is configured. If the last valid TURN URL is removed while relay-only mode is selected, the dialogue restores `Automatic` and displays a concise explanation.
The route policy is an ordinary P2P profile property. It is retained in P2P connection strings and encrypted Setup URIs so that an imported compatibility profile has reproducible transport behaviour.
Multiple P2P profiles may intentionally use the same Group ID, passphrase, and relay list while selecting different compatibility settings. For example, one profile may use `Standard` and `Automatic`, while another uses `Maximum compatibility` and `TURN relay only`. Only the selected P2P profile joins the group, so each device can select the profile appropriate to its current network without a separate device-local override system.
No `Direct only` choice will be added. `Automatic` already prefers viable non-relayed candidates, and preventing TURN fallback would mainly create another failure mode.
### TURN server presentation
The first settings revision retains the existing storage and dialogue contract of one comma-separated TURN URL list, one username, and one credential. The connection-path choice is presented separately under `Connection compatibility`, while the TURN values remain under `Advanced Settings`.
A future interface may present the existing comma-separated value as ordered `turn:` and `turns:` URL rows without changing its serialised representation. A structured list of multiple credential profiles is deferred until a provider or self-hosted use case requires different credentials in the same P2P profile.
Static long-term credentials are the supported first stage. Managed providers may return short-lived credentials, but LiveSync must not store a provider API token or a Coturn shared authentication secret. A future managed-credential design needs a separately trusted HTTPS endpoint, expiry handling, refresh behaviour, failure reporting, and a clear Setup URI policy. It is not represented as another static password field.
### TURN allocation check and route diagnostics
A future `Test TURN server` action should create a disposable WebRTC check with `iceTransportPolicy: 'relay'`, request candidate gathering, and require at least one relay candidate. It must not read a Vault, join a LiveSync P2P room, or claim that document synchronisation has succeeded.
Where the browser exposes the evidence, the result should report:
- whether a relay candidate was gathered;
- the TURN URL used for that candidate;
- UDP, TCP, or TLS transport; and
- a bounded failure or inconclusive result.
Ordinary P2P diagnostics should later summarise the selected candidate pair as direct, server-reflexive, or relayed, with its transport. Raw `getStats()` output remains supporting evidence rather than the primary interface.
### Placement and defaults
These controls belong inside `P2P Configuration` under a `Connection compatibility` section. They do not require the repository-wide Advanced, Power User, or Edge Case modes. P2P itself remains a supported opt-in feature.
Existing profiles retain the following defaults:
- `P2P message size`: `Standard`;
- `Connection path`: `Automatic`; and
- TURN credentials and URLs: unchanged.
Settings which replace a room continue to use the established P2P room and transport lifecycle. No new reconnect interval, handshake timeout, keepalive interval, trickle-ICE, candidate-pool, data-channel reliability, or backpressure setting is exposed.
## Self-hosted TURN example
The repository supplies an optional Coturn Compose example under `docker/coturn/`. It uses a versioned upstream `coturn/coturn` image rather than maintaining another LiveSync Dockerfile.
The example deliberately covers one small static-credential deployment:
- Linux host networking, which avoids Docker's large port-range forwarding cost;
- TURN over UDP and TCP on port 3478;
- a bounded UDP relay port range;
- explicit long-term credentials;
- an explicit public IPv4 address;
- no TLS or DTLS in the starter configuration; and
- restrictions which prevent relaying to common private IPv4 ranges.
The starter does not recommend `turns:` on port 443. It conflicts with an HTTPS entry point which already owns the same IP address and TCP port, including the bundled CouchDB Caddy profile. When a restrictive network requires this path, the preferred deployment uses a separate TURN host or public IP address.
An outbound tunnel used for CouchDB may leave the host's public port 443 available when TURN uses a separate DNS record which resolves directly to that host, but the tunnel itself cannot carry TURN traffic. A layer-4 TLS router can also own the shared port and select separate CouchDB and TURN backends by SNI. That alternative adds certificate and routing responsibilities, depends on the intended TURN clients supplying usable SNI, and is outside the supplied Compose example. The standard Caddy image used by the CouchDB profile does not provide that layer-4 routing.
TURN over TLS is not HTTP and must reach Coturn directly or through a compatible layer-4 proxy. Supporting it also adds private-key, renewal, privileged-port, and real-network verification responsibilities.
The Compose example is not a hosted service supplied by the project, an availability guarantee, or a substitute for firewall and abuse controls. Operators remain responsible for DNS, certificates when enabled, port forwarding, bandwidth, quotas, monitoring, software updates, credential rotation, and legal or provider constraints.
## Security and privacy
TURN relays the already encrypted WebRTC connection. A TURN operator cannot read LiveSync's end-to-end encrypted Vault contents, but can observe endpoint addresses, timing, traffic volume, and service credentials.
Static credentials allow use of the operator's bandwidth until they are changed. They should be unique, high entropy, and limited to the intended deployment. Setup URIs are encrypted but still contain the P2P connection profile; they and their separate passphrases must be protected.
The Coturn Docker example uses environment interpolation for its static credential. A local Docker administrator can inspect the resulting container arguments and already has equivalent control of that host. The `.env` file remains untracked and should be readable only by the operator.
## Alternatives rejected
### Expose a free-form byte field
Most users cannot infer a safe application payload from a network MTU, and an arbitrary value makes reports difficult to compare. Four named presets provide a bounded troubleshooting ladder.
### Apply the smaller payload only on the affected mobile device
The bound controls outgoing messages. This would leave larger messages from another sender unchanged and could fail during the direction which matters most for an initial fetch.
### Force TURN whenever a TURN server is configured
TURN is normally a fallback. Forcing it by default adds latency and bandwidth cost, and exposes more connection metadata even when a direct path works.
### Store the connection path in a device-local overlay
A second layer of device-specific profile overrides would make imported profile behaviour less reproducible and add another identity, mapping, and lifecycle contract. Separate named P2P profiles already let each device select an explicit transport policy, including when those profiles share the same Group ID and credentials.
### Automatically decrease the payload after a transfer failure
A transfer failure does not identify message size as the cause. Reusing a possibly wedged ordered channel would also make the retry inconclusive, while rebuilding the connection expands the lifecycle and user-notification design.
### Add browser-specific defaults
Safari, mobile Safari, Chrome, and Chrome on Android use different platform WebRTC implementations and lifecycle policies, but the failing route also depends on both networks and the remote peer. There is not enough stable evidence for a browser-name heuristic. Explicit cross-platform presets are more predictable.
### Build and maintain a LiveSync Coturn image
The upstream project already publishes a multi-platform image and documents its configuration contract. A local Dockerfile would duplicate security updates and release work without adding a LiveSync-specific server component.
### Bundle a shared port-443 router
A layer-4 TLS router could share one public address between distinct CouchDB and TURN hostnames by inspecting SNI. Bundling that topology would replace the current Caddy ownership of port 443, add another certificate and routing lifecycle, and rely on the intended TURN clients presenting usable SNI. A separate TURN host or public IP address keeps those failure and ownership boundaries explicit.
## Verification
The first implementation stage must add focused tests before production changes:
- settings-schema defaults for absent keys;
- P2P connection-string and Setup URI round trips which retain both transport compatibility settings;
- compatibility parsing and serialisation of the existing TURN URL string;
- mapping each message-size preset to the exact Commonlib wire bound;
- mapping relay-only mode to `iceTransportPolicy: 'relay'`;
- rejection or automatic reset of relay-only mode without a valid TURN URL;
- room replacement after either effective transport setting changes; and
- the real Obsidian dialogue, profile, and connection-string round trip.
The future TURN allocation action requires its own focused tests using injected WebRTC boundaries, followed by a real transport test only for the device- or network-owned behaviour which deterministic injection cannot prove.
The Coturn example is checked independently with `docker compose config`. Runtime verification uses a real Coturn allocation from outside the server network and confirms both UDP and TCP client paths before it is presented as a known-working deployment.
## Consequences
- Users gain a small compatibility ladder without learning WebRTC internals.
- A conservative message size affects throughput wherever it is selected or imported, and must be applied to every participating device to protect all transfer directions.
- Profiles may intentionally share the same P2P group identity while offering different transport compatibility choices; the selected profile determines the active connection behaviour.
- TURN can be forced for diagnosis or hostile networks without making relay use the global default.
- Static and managed TURN credentials have separate, explicit responsibility boundaries.
- Browser-specific heuristics, automatic payload fallback, and low-level transport knobs remain out of scope.
- A reproducible self-hosted starter is available without making LiveSync responsible for a separate TURN image.
+4
View File
@@ -141,6 +141,10 @@ This field stores an array of Chunk Document IDs.
\_id is generated based on the path of the Obsidian note.
The validation and explicit repair contract for normal-file Metadata whose
actual ID does not match the ID derived from its stored path is defined in
[Normal-file Metadata Document ID Validation and Repair](design_docs/metadata_document_id_validation_and_repair.md).
- If the path starts with `_`, it is converted to `/_` for convenience.
- If Case Sensitive is disabled, it is converted to lowercase.
@@ -0,0 +1,214 @@
# Document History Revision Restoration
## Status
Accepted for a limited implementation.
## Problem and scope
This document uses the independent revision properties defined under
[Revision](../terms.md#revision) and the general state model in
[Conflict resolution and revision provenance](../specs_conflict_resolution.md).
Document History can reconstruct an available historical revision from its
Chunks and write that content to the Vault through **Back to this revision**.
The current action writes directly through the storage adapter. It does not
create a new Metadata revision, clear a logical deletion through a successor,
or record which database revision produced the restored Vault file.
This leaves a logically deleted Metadata document at `deleted: true` after the
file has returned to the Vault. A later ordinary Vault save may create a
non-deleted successor, but restoration must not depend on an unrelated later
file event.
The same action does not inspect or preserve revision-tree intent explicitly
when conflicts exist. Document History currently displays the ancestry of the
PouchDB winner. It does not display the complete revision tree or the ancestry
of every conflict leaf.
This design covers restoration of one readable historical revision of a normal
Vault file. It creates a new non-deleted successor revision, reflects that exact
revision to the Vault, and leaves every other conflict branch available for the
existing conflict workflow.
## Evidence
A real-Obsidian exercise created a Markdown document, removed it through the
Vault API, and waited for LiveSync to store a logical-deletion successor. The
Metadata retained all referenced Chunks, and Document History could reconstruct
the deleted content.
Selecting **Back to this revision** restored the file and its exact bytes to the
Vault. The local database nevertheless retained the same deleted current
revision after file processing had settled. An additional ordinary Vault save
then created a new non-deleted successor. This demonstrates that content
reconstruction works and that the missing operation is the database-aware
restoration step.
## Revision-tree decision
The selected historical revision is the content source. It is not necessarily
a current leaf and is not used as the parent of the new write.
At the time of the restoration operation, LiveSync reads the current PouchDB
winner. The new revision is written as a child of that exact winner revision
and contains the selected historical content with no logical-deletion marker.
For an unconflicted logical deletion:
```text
A -- D (logically deleted winner) -- R (non-deleted restored successor)
```
For an existing conflict:
```text
A -- W (winner) -- R (restored successor)
\
C (existing conflict remains current)
```
Advancing the winner branch is the ordinary meaning of reverting its content.
The previous winner remains in revision history, while every other conflict
leaf remains available for conflict resolution. Restoration does not
manufacture an additional independent branch merely to retain the previous
winner as another current conflict.
The existing **Inspect conflicts and file/database differences** workflow owns
subsequent comparison and resolution of the restored revision and the remaining
conflict leaves. Document History supplies content which may no longer be a
current leaf; the Inspector operates on the current revision tree after that
content has been restored. Neither interface replaces the other.
## Persistence and reflection order
Restoration performs these steps in order:
1. read and reconstruct the exact selected historical revision;
2. read the current winner and use its exact revision as the write base;
3. create Chunks and conditionally write a new non-deleted Metadata revision
containing the selected content below that exact winner;
4. obtain the exact created revision from the database write;
5. reflect that exact revision to the Vault; and
6. record the reflected revision as the device-local file provenance.
The database write precedes Vault reflection. If the database write fails, the
Vault remains unchanged. If the new revision is stored but Vault reflection
fails, the restored revision remains in database history and the operation
reports that persistence completed without successful reflection. It does not
remove the new revision in an attempted rollback. Concurrent database activity
may subsequently change whether that stored revision is a current leaf or the
winner.
The new Metadata keeps the current document's creation time, records the
restoration as a new modification, and derives its size and type from the
reconstructed bytes. Reusing the historical modification time would make an
explicit present-day restoration appear older than concurrent changes and
would interact poorly with modification-time policies.
### Restoration state transition
The selected historical revision `H` supplies content, the winner `W` supplies
the conditional write base, and `R` is the non-deleted restored revision. These
are operation roles; winner, Vault-matching, and displayed remain independent
properties.
| Stage | Content source | Winner | Vault relationship |
| ---------------------------- | -------------- | -------------------------------------------- | -------------------------------------------------------------------- |
| Before restoration | `H` | current winner `W` | unchanged |
| After the conditional write | `H` | `R` immediately after writing | Vault state and displayed provenance remain unchanged |
| After exact Vault reflection | `H` | normally `R`; concurrent activity may differ | `R` is Vault-matching and displayed |
| After a reflection failure | `H` | depends on subsequent database activity | Vault state and displayed provenance remain unchanged; `R` is stored |
## Conflicts and concurrent changes
Existing conflict leaves are never deleted by restoration. When conflicts
remain after the new revision is written, LiveSync keeps the ordinary conflict
indicator and conflict-resolution workflow available. The interface explains
that restoration advances the currently shown branch and does not resolve the
other versions.
Document History does not perform a separate comparison with the winner which
was current when the dialogue opened. The conditional exact-base write uses an
ordinary PouchDB new edit. If that base is no longer a writable current leaf,
PouchDB rejects the write and the dialogue reports that the revision tree
changed and the operation should be retried. If the base remains current while
another conflict leaf appears, the write may succeed and both leaves remain
available.
Commonlib's existing `storeWithBaseRevision` operation cannot provide this
condition. Its force-write behaviour intentionally uses `new_edits: false` so
that conflict-preservation workflows can create a branch from a supplied
ancestor. The restoration path therefore uses a separate
`storeWithLiveBaseRevision` operation. That operation writes below the supplied
current leaf with ordinary PouchDB revision checking and never falls back to
the force path.
The action must not silently fall back to an unbased write after an exact-base
failure.
## History presentation boundary
The current slider continues to represent the available ancestry of the
PouchDB winner. Building a complete branch-aware history viewer would require
loading the ancestry of every current leaf, joining shared ancestors,
representing missing or compacted revisions, and adding an explicit
branch-selection interface. That work is not required to restore the history
currently shown.
When the document has conflicts, the dialogue may state that restoration will
create a new revision on the winner branch which is current when the action
runs, and leave the other versions unresolved. Detailed branch comparison
remains in the Inspector.
## Ownership
LiveSync owns:
- selection and reconstruction of the historical revision;
- the Document History user interaction and result messages;
- orchestration of the exact-base write and exact-revision reflection; and
- presentation of any remaining conflict state.
Commonlib owns:
- Chunk creation and Metadata persistence;
- `storeWithLiveBaseRevision`, which writes content as a normal child of an
exact current leaf and returns the created revision;
- rejecting an unavailable or stale base revision;
- reflecting an exact current leaf revision to storage; and
- recording device-local file-reflection provenance.
The implementation retains `storeWithBaseRevision` for the conflict workflows
which deliberately create branches. The new conditional operation is narrower
and is not a replacement for that existing behaviour.
## Non-goals
This change does not:
- identify the origin of malformed or doubled Metadata paths;
- turn Document History into a complete revision-tree viewer;
- select, merge, or discard existing conflict leaves;
- restore unavailable content whose Chunks cannot be reconstructed;
- mutate an old revision or clear its deletion marker in place;
- rebuild a local or remote database; or
- change automatic conflict-resolution policy.
## Verification
Focused tests cover:
- restoring readable content as a non-deleted child of a deleted winner;
- returning and reflecting the exact created revision;
- retaining every existing conflict leaf while advancing the winner branch;
- refusing an exact-base write when the base is no longer a current leaf;
- retaining the existing force-write behaviour for callers which deliberately
create a conflict branch;
- leaving the Vault unchanged when database persistence fails; and
- retaining the stored revision when subsequent Vault reflection fails.
The real-Obsidian regression exercise removes the additional normal Vault save
from the earlier reproduction. **Back to this revision** must itself produce a
new non-deleted successor revision, restore the exact content to the Vault, and
reopen Document History at that successor.
@@ -0,0 +1,187 @@
# Normal-file Metadata Document ID Validation and Repair
## Status
Accepted
## Problem and scope
A normal-file Metadata document is addressed by an ID derived from its recorded
Vault-relative path. Historical data can contain a readable Metadata document
whose actual local database ID no longer matches that derivation. An ordinary
path-based read then looks up a different ID. It may reach a separate,
consistently addressed Metadata document, or it may find no document at all;
it cannot reach the mismatched document which the Offline Scanner enumerated.
The mismatch can repeatedly produce failed reflection, and an offline-deletion
decision can be made before that failure. The scanner must therefore recognise
the mismatch before any file reflection, database deletion, expired-history
cleanup, or last-seen update.
This design covers ordinary Vault files. Hidden File Sync, Customisation Sync,
and the obsolete plug-in storage namespace retain their feature-specific
processing. A disagreement between the document-ID namespace and recorded-path
namespace is reported, but is not repaired by this workflow.
## Evidence and cause boundary
The reported data included readable paths which could be enumerated from
Metadata but could not be fetched again through the path-derived lookup. The
Vault also had a history of case changes in folder names. This is consistent
with an ID/path mismatch, but it does not prove whether a historical rename,
interrupted migration, or earlier path-setting change created it.
The repair workflow must not infer that the current path is authoritative merely
because it is readable. It is available only when the local evidence is
unambiguous and current.
## Identity invariant
For normal-file Metadata:
actualDocumentId === path2id(declaredPath)
The active path service owns the derivation. In particular,
handleFilenameCaseSensitive, usePathObfuscation, and the path-obfuscation
passphrase can change the expected ID. The E2EE Security Seed and Chunk settings
do not directly participate in this ID.
Inspection and repair use the current local path service. They do not query the
remote or decide whether this device's settings should become authoritative.
Commonlib recalculates the expected ID during its pre-mutation inspection, so a
local ID-derivation setting change makes an earlier approval stale. An
intentional whole-database change to ID-derivation settings requires the
established rebuild workflow, not this one-entry repair.
## Offline Scanner decision
The Offline Scanner validates each decoded Metadata document while its actual ID
is still available. It does this before target-file policy and path-keyed pair
construction.
- Consistent normal-file Metadata continues through the existing scan.
- Consistent special-namespace Metadata remains owned by its feature.
- An ID/path or namespace mismatch is left unchanged and does not enter pair
processing.
- If consistently addressed Metadata is selected for the same case-normalised
path, that Metadata and its storage file continue through the established
path-based scan. A stale enumerated document must not suppress this flow.
- If no consistently addressed Metadata is selected for that logical path, its
storage entry is also withheld. No storage write, database deletion, or
last-seen update is performed for that withheld path.
- Expired logical deletion history with an inconsistent identity is left
unchanged.
The ordinary scan still returns its established Boolean execution result. A
recognised mismatch is left unchanged and omitted before file-pair processing.
It therefore does not add a `FilePairProcessResult`, change the ordinary Boolean
scan contract, or change Fast Setup or CLI completion policy. Detailed
inspection remains separate.
## Inspection decision
`inspectMetadataDocumentIdentities` is read-only and enumerates the local
database by actual document ID. This is necessary because inspection through a
path-derived lookup cannot discover the mismatched source.
The existing **Inspect conflicts and file/database differences** interface shows
one card for each mismatch. It excludes that card's path from ordinary
path-based repair only when no consistently addressed Metadata document can be
resolved for the same logical path. A stale entry does not hide the normal
inspection of a resolvable entry. Multiple affected files are presented
separately; there is no batch repair.
A one-entry repair is offered only when all of these checks pass:
- the mismatch is within the normal-file namespace;
- the source is the current winner and has no conflict leaves;
- the recorded path is valid and selected by current synchronisation policy;
- one case-normalised path maps to one source under the active filename setting;
- only one mismatched source expects the target ID; and
- the target ID is absent, or contains an exact structural copy left by an
earlier attempt.
An exact structural copy has the same path, timestamps, size, type, Chunk
references, Eden data, and logical-deletion state. Inspection does not fetch
Chunk content or query the remote. Missing content remains the responsibility
of the existing file and Chunk repair tools.
## Repair decision
The user explicitly confirms one actual ID, expected ID, and source revision.
Commonlib then:
1. acquires the existing ordered document locks for the source and target IDs;
2. reruns the complete inspection and rejects stale or unsafe input;
3. reads the exact approved source revision;
4. removes the path from the Offline Scanner's durable last-seen map;
5. writes the expected target ID when it is absent;
6. reads the target back and verifies the exact structural copy;
7. writes a deletion revision for the source against the approved source
revision; and
8. returns control to LiveSync, which requests an ordinary Vault scan.
The repair result and the follow-up scan result remain separate. If the scan is
suspended, returns false, or raises an error after the source has been removed,
LiveSync reports that the identity repair completed and directs the operator to
run the ordinary scan separately. It does not describe the completed mutation
as a failed or rolled-back repair.
The target is always verified before the source is removed. If target creation
fails, the source remains. If source removal fails, the exact target remains and
the same one-entry action can finish the operation after a new inspection. This
retry property is an implementation safety guarantee, not a separate public
repair mode.
The target receives new CouchDB revision ancestry because ancestry cannot move
between document IDs. Users are told to back up the device, pause editing and
synchronisation on other devices, allow the change to replicate, and inspect
again.
## Case handling
The active handleFilenameCaseSensitive setting defines whether path claims are
folded before ambiguity is assessed. When case-insensitive handling is active,
a consistently addressed entry may continue through the existing path-based
flow even if a stale case variant is also reported. The stale entry is not
automatically selected or removed unless the one-entry repair preconditions
hold. When case-sensitive handling is active, intentional variants remain
distinct.
This workflow does not rename Vault files or folders, infer a preferred folder
name from one device, or coordinate a repair across devices. The repair changes
one local database and relies on ordinary replication afterwards. Other devices
must remain paused until that result has replicated and a new inspection is
clean.
For widespread cross-device naming differences, the operator must choose an
authoritative Vault, stop every participating device, correct its storage names
outside Obsidian, rebuild the central remote from that Vault, and reset the
other devices from the verified remote. During Fast Setup on an empty Vault,
there are no storage names to correct: the scanner reflects every consistently
addressable Metadata entry and reports only the references which remain
unresolved.
## Non-goals
This change does not:
- repair several entries automatically or in a batch;
- choose between competing case variants;
- rename storage files or folders;
- coordinate a distributed repair across devices;
- migrate an entire database after path-obfuscation or case-setting changes;
- repair special-namespace Metadata;
- reconstruct unavailable Chunk content;
- query or modify the remote directly; or
- change Fast Setup, daemon, or CLI completion policy.
## Verification
Focused Commonlib tests cover unresolved-identity exclusion before pair
construction, continued processing of a resolvable same-path entry, expired
logical-deletion retention, namespace routing, read-only actual-ID inspection,
repair preconditions, target-first ordering, stale approval, exact-target retry,
source preservation on failure, and last-seen clearing. LiveSync tests cover
selective presentation-path withholding, separate confirmation, cancellation,
and the ordinary scan request after a completed repair.
+11
View File
@@ -44,6 +44,17 @@ Both settings contain server addresses, but they are not interchangeable.
A TURN provider cannot read LiveSync's encrypted Vault contents, but it can observe connection metadata and traffic volume. Use a provider you trust. The project does not operate an official TURN service.
## Connection compatibility profiles
`P2P Configuration` includes a separate `Connection compatibility` section. Its defaults preserve the existing transport behaviour:
- **P2P message size** defaults to **Standard**. **Reduced**, **Conservative**, and **Maximum compatibility** progressively limit outgoing P2P messages when a network path appears to drop larger WebRTC messages. This is not a Vault Chunk size or an IP MTU. Smaller values add framing and processing overhead.
- **Connection path** defaults to **Automatic**, which lets WebRTC select a viable direct or TURN-relayed path. **TURN relay only** forces the encrypted connection through TURN and is available only when the profile contains at least one valid `turn:` or `turns:` URL.
The sending device controls its outgoing message size. Select the same conservative preset on every device which may send across the constrained path. Existing devices do not receive the choice retrospectively merely because another device changed it.
Both compatibility choices belong to the saved P2P profile and are retained in P2P connection strings and encrypted Setup URIs. Separate profiles may use the same Group ID, passphrase, and relay list while selecting different compatibility choices. Only the selected P2P profile joins the group.
## P2P Status
The **P2P Status** pane is the current Obsidian interface for P2P connections.
+2 -2
View File
@@ -39,7 +39,7 @@ Use this path only when the remote database is new, or when this device is inten
![Final server overwrite warning](../images/quick-setup/guide-quick-setup-first-rebuild-confirmation.png)
8. A newly provisioned database may show `Fetch Remote Configuration Failed` because it does not contain a saved preferred configuration yet. If this is a genuinely new setup, select `Skip and proceed`. Otherwise, stop and investigate before continuing.
8. A newly provisioned database may show `No Synchronisation Settings Found`. Select `Use this device's settings` for the intended new database. If you expected existing settings, cancel and check the selected remote. Cancellation restarts with Vault and database reflection paused; keep LiveSync disabled until the remote has been checked. If `Could Not Read Synchronisation Settings` appears, retry, or use this device's settings to continue the overwrite. The overwrite still requires a working connection.
![Expected missing remote configuration choice for a new database](../images/quick-setup/guide-quick-setup-missing-remote-configuration.png)
@@ -134,7 +134,7 @@ Use this path when CouchDB is ready but a Setup URI is unavailable. It configure
8. Select `Create or connect to database and continue`. Onboarding requires this connection test to succeed.
9. Review `Setup Complete: Preparing to Initialise Server`, then select `Restart and Initialise Server`.
10. Read the final overwrite warning. Select `I Understand, Overwrite Server` only when this device is intentionally the source of truth and a current backup exists.
11. A newly created database can show `Fetch Remote Configuration Failed` because it does not yet contain a saved preferred configuration. Select `Skip and proceed` only for this known new database.
11. A newly created database can show `No Synchronisation Settings Found`. Select `Use this device's settings` for this known new database. If you expected existing settings, cancel and check the selected remote. Cancellation restarts with Vault and database reflection paused; keep LiveSync disabled until the remote has been checked. If `Could Not Read Synchronisation Settings` appears, retry, or use this device's settings to continue the overwrite. The overwrite still requires a working connection.
12. Acknowledge `All optional features are disabled`, then keep Obsidian open until the initialisation progress has cleared.
Create and synchronise an ordinary test note. Once it has reached CouchDB, follow [Create a Setup URI for another device](#create-a-setup-uri-for-another-device), then [Add another device](#add-another-device). This keeps the second device aligned with the remote profile and encryption settings which the first device actually applied.
+25 -4
View File
@@ -36,7 +36,7 @@ The flag deliberately enables file logging, which may affect performance. Remove
Use this workflow when one file, or a small number of known files, has conflicts, missing chunks, or a difference between the current Vault file and the local LiveSync database. The inspection is device-local: it does not query a remote database or prove that another device has the same chunks.
The `Hatch` recovery controls are ordered by escalation. Running **Recreate chunks for current Vault files** again with unchanged chunk settings and file contents produces the same chunks, and does not alter the revision tree. **Inspect conflicts and file/database differences** then provides actions for exact revisions. **Resolve All conflicted files by the newer one** is last because it applies a modification-time policy in bulk and logically deletes every other live version.
The `Hatch` recovery controls are ordered by escalation. Running **Recreate chunks for current Vault files** again with unchanged chunk settings and file contents produces the same chunks, and does not alter the revision tree. **Inspect conflicts and file/database differences** then provides actions for exact revisions. **Resolve All conflicted files by the newer one** is last because it applies a modification-time policy in bulk and logically deletes every other current version.
1. Stop editing the affected file, pause replication on the participating devices, and keep a separate copy of every readable version.
2. If another device or backup has the intended content, preserve that copy before changing any revision.
@@ -52,7 +52,26 @@ The `Hatch` recovery controls are ordered by escalation. Running **Recreate chun
- **Apply logical deletion to Vault**, **Discard this branch**, and **Discard unreadable revision** are destructive decisions. Use them only after preserving every version which may still be needed.
7. Synchronise the healthy source if chunks were restored, scan again, and confirm that the expected conflict or difference has disappeared before resuming ordinary editing.
An absent Vault file and a logical-deletion winner already agree and do not require a repair card unless another live branch remains. If the scan reports many unrelated files, or the local database itself is incomplete or corrupt, stop the per-file workflow and use [Reset synchronisation on this device](#reset-synchronisation-on-this-device) from a trusted remote. If the central remote must instead be reconstructed from an authoritative Vault, use [Overwrite server data with this device's files](#overwrite-server-data-with-this-devices-files).
An absent Vault file and a logical-deletion winner already agree and do not require a repair card unless another conflict branch remains. If the scan reports many unrelated files, or the local database itself is incomplete or corrupt, stop the per-file workflow and use [Reset synchronisation on this device](#reset-synchronisation-on-this-device) from a trusted remote. If the central remote must instead be reconstructed from an authoritative Vault, use [Overwrite server data with this device's files](#overwrite-server-data-with-this-devices-files).
Metadata document-ID mismatches use a separate action in the same Inspector. Follow [Repair a Metadata document ID mismatch](#repair-a-metadata-document-id-mismatch) rather than applying a file revision by path.
## Repair a Metadata document ID mismatch
Use this workflow when **Inspect conflicts and file/database differences** reports `Metadata entry requires review and was left unchanged`. The Inspector found local Metadata whose stored document ID no longer represents its recorded path. It leaves the entry unchanged, while any consistently addressed Metadata for the same logical path remains available to ordinary inspection and Vault reflection. This inspection does not query the remote.
1. Back up this device. If other devices share the database, stop editing and pause synchronisation on them.
2. Confirm that the current file-name case and path obfuscation settings are intended for this database. If either setting was deliberately changed for the whole database, stop this workflow and use Rebuild instead.
3. Open **Self-hosted LiveSync settings****Hatch****Inspect conflicts and file/database differences**, then select **Begin inspection**.
4. Find the affected Metadata card and review its recorded path, stored document ID, expected document ID, and source revision.
5. Continue only when the card says `Repair is available for this entry.` Open its wrench menu and select **Repair this Metadata document ID**. If the action is unavailable, do not force an ID: the entry is ambiguous, conflicted, deleted, outside the normal-file namespace, or otherwise unsafe for one-entry repair.
6. Review the warning and select **Repair Metadata ID**. LiveSync rechecks the source revision and expected ID, writes and verifies the target, then removes the obsolete ID.
7. Wait for the ordinary Vault scan to complete. If LiveSync reports that the repair completed but the scan did not run, keep synchronisation paused, resolve the reported scan condition, then run the **Scan storage and database again** command.
8. Allow this device to upload the repair. Resume the other devices one at a time, then run the inspection again and confirm that the Metadata card no longer appears and the Vault file has the intended content.
This action changes one local database entry. It does not rename Vault files or folders, repair several entries at once, coordinate other devices, or preserve CouchDB revision ancestry across the two document IDs.
If many entries reflect folder-name differences across devices, stop every device, choose the authoritative Vault, close Obsidian, correct the actual storage names with operating-system tools, then rebuild the central remote from that Vault and reset the other devices. During Fast Setup on an empty Vault, there are no storage names to correct: allow consistently addressable Metadata to be reflected, then inspect any remaining unresolved references.
## Reset synchronisation on this device
@@ -68,6 +87,8 @@ On the next start, LiveSync:
4. discards and reconstructs the local LiveSync database from the selected remote; and
5. resumes only after the scheduled operation has completed or been cancelled safely.
Fast Setup retains its fetch flag, last successfully stored remote position, and selected data-processing method when an error while decrypting data, reading the remote response, or writing to the local database stops the reconstruction. File watching and database reflection remain suspended. Review the first specific error in **Show log**, correct its cause, then restart Obsidian to retry from the retained state. Do not remove the fetch flag or resume the Scram switches while you intend to continue the operation. If the same error remains, leave LiveSync suspended and [collect a report](troubleshooting.md#collect-a-report).
For P2P, a source peer must be online, discovered, and selected in `P2P Rebuild`. Merely opening an empty signalling room does not complete Fetch. Closing the rebuild dialogue without selecting a peer reports failure and does not treat the local database as restored.
Review the [Fast Setup guide](tips/fast-setup.md) before using this operation on a Vault which contains unsynchronised local work.
@@ -91,7 +112,7 @@ Garbage Collection removes unreferenced chunks while preserving the current data
- all relevant devices have synchronised; and
- the remaining historical and deletion state is understood.
Deleted documents, tombstones, live conflicts, and retained metadata are not free. Live conflict branches keep the chunks needed for review, while an ordinary superseded linear revision does not protect its former chunks. Garbage Collection can therefore make old content unreadable and cannot promise the smallest possible remote. Review the [Garbage Collection V3 specification](specs_garbage_collection.md) before using it.
Deleted documents, tombstones, unresolved conflicts, and retained metadata are not free. Conflict branches keep the chunks needed for review, while an ordinary superseded linear revision does not protect its former chunks. Garbage Collection can therefore make old content unreadable and cannot promise the smallest possible remote. Review the [Garbage Collection V3 specification](specs_garbage_collection.md) before using it.
Rebuild is a different operation. It reconstructs the database from a chosen authoritative state and is the more certain way to remove unwanted history or repair a damaged remote, but it is also more disruptive and can discard changes which exist only elsewhere.
@@ -105,7 +126,7 @@ Create only the flag required for the chosen operation.
| `flag_fetch.md` or `redflag3.md` | Schedule **Reset Synchronisation on This Device** from the selected remote. |
| `flag_rebuild.md` or `redflag2.md` | Schedule **Overwrite Server Data with This Device's Files**, or local P2P preparation when no central remote exists. |
Flag files themselves are excluded from synchronisation. Fetch and rebuild flags are removed by the scheduled workflow after completion or cancellation; `redflag.md` is a manual emergency stop.
Flag files themselves are excluded from synchronisation. Fetch and rebuild flags are removed by the scheduled workflow after completion or safe cancellation. A failed Fast Setup retains its fetch flag so that a later start can retry it; `redflag.md` is a manual emergency stop.
## When the warning continues
+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.
+317
View File
@@ -0,0 +1,317 @@
# 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.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
### Conflict handling and recovery
#### Improved
- **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 the current winner has no conflict leaves and 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
11th August, 2026
### Synchronisation and storage
#### Fixed
- One-shot CouchDB replication now closes its temporary remote database after each run and before retrying, preventing inactive PouchDB instances from accumulating during long-running periodic synchronisation (Commonlib PR #75). Thank you to @apple-ouyang for the contribution!
- Start-up and recovery scans now keep failed database-to-Vault writes retryable instead of recording them as successful and later mistaking the still-missing file for a local deletion (Commonlib PR #106).
- Files over the size limit or in conflict remain deliberately skipped, while actual write failures are reported to Fast Setup, CLI mirror, and daemon callers.
## 1.0.11
9th August, 2026
### Setup and compatibility
#### Fixed
- Fast Setup now uses Standard Fetch when CouchDB's 'Use Internal API' setting is enabled, avoiding a streaming request path which Obsidian's buffered API cannot support (#1020).
- Custom headers alone continue to use Fast Fetch when browser CORS permits them; Standard Fetch clears any obsolete Fast Fetch checkpoint after resetting the local database.
### Synchronisation and storage
#### Fixed
- Fractional file timestamps no longer cause affected mobile clients to crash after synchronisation (#1087, PR #1039). Thank you to @andrewleech for the contribution!
- Timestamps are now normalised in the command-line tool and before Obsidian's native file-system writes.
## 1.0.10
9th August, 2026
### Setup and compatibility
#### 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 -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
+86 -22
View File
@@ -4,6 +4,19 @@ NOTE: This document not completed. I'll improve this doc in a while. but your co
There are many settings in Self-hosted LiveSync. This document describes each setting in detail (not how-to). Configuration and settings are divided into several categories and indicated by icons. The icon is as follows:
On Obsidian 1.13 or later, the root settings page is organised by task. When synchronisation is inactive, **Quick Setup** appears first. Once any synchronisation mode is active, **Synchronisation** and **General Settings** move ahead of **Quick Setup**. **Set up other devices** appears after this plug-in has been configured. Earlier supported Obsidian versions retain a pane-based interface with the same controls.
| Icon | Root group | Contents or availability |
| :--: | ------------------------ | ------------------------------------------------------------- |
| 🧙‍♂️ | Quick Setup | Setup URI, onboarding, and enable actions |
| 🔄 | Synchronisation | Remote Configuration and Sync Settings |
| ⚙️ | General Settings | Appearance, Logging, and Extra menus |
| 📲 | Set up other devices | Copy a Setup URI or show its QR code after configuration |
| 🛠️ | Maintenance and recovery | Maintenance and Hatch |
| 🧩 | Extra features | Selector and Customisation sync when advanced features appear |
| 🔧 | Advanced settings | Advanced, Power users, and Patches when their modes appear |
| ️ | Help and information | Help and troubleshooting, and Change Log |
## Feature maturity for 1.0
The following status applies to optional and compatibility features in the 1.0 line:
@@ -15,10 +28,32 @@ The following status applies to optional and compatibility features in the 1.0 l
| Beta or experimental | JWT authentication, ignore files, automatic newer-file conflict resolution, and Garbage Collection V3 for CouchDB | Retained for explicit testing and specialised use. They remain disabled by default and are not part of the minimum supported setup. |
| Compatibility only | V1 dynamic iteration counts, the old IndexedDB adapter, non-current hash algorithms, Eden chunks, and the stored `doNotUseFixedRevisionForChunks` key | Existing settings and data remain readable. New Vaults use the current defaults, and compatibility controls are shown only where a migration or recovery path still needs them. |
### Apply changes which require initialisation
Some compatibility settings are not saved immediately. They remain pending
until **Apply** is selected. The Apply action remains visible on the root
settings page and on the relevant child page. The following dialogue asks which
existing data should be used after restarting:
- **Reset Synchronisation on This Device** reconstructs this device's local
database from the configured remote. For P2P, an online source device is
selected after restart.
- **Overwrite Server Data with This Device's Files** reconstructs the local and
remote databases from this Vault. The P2P equivalent prepares only this
device from its current Vault files.
- **Review another way to apply these settings** returns to a separate choice
between keeping the changes pending and applying them without initialisation.
Applying them alone is an advanced compatibility fallback and can make the
device incompatible with its existing synchronisation data.
LiveSync reserves the selected next-start operation before saving the pending
settings. If validation or that reservation fails, the settings remain
unapplied and the settings-only fallback is not offered.
| Icon | Description |
| :--: | ------------------------------------------------------------------ |
| 💬 | [0. Change Log](#0-change-log) |
| 🧙‍♂️ | [1. Setup](#1-setup) |
| 🧙‍♂️ | [1. Quick Setup and Extra menus](#1-quick-setup-and-extra-menus) |
| ⚙️ | [2. General Settings](#2-general-settings) |
| 🛰️ | [3. Remote Configuration](#3-remote-configuration) |
| 🔄 | [4. Sync Settings](#4-sync-settings) |
@@ -34,17 +69,19 @@ The following status applies to optional and compatibility features in the 1.0 l
This pane always shows the current release history. It does not track whether a particular plug-in version has been read and does not open automatically after an ordinary update.
Internal database or settings compatibility reviews use a separate safety dialogue, not this pane. The dialogue explains why remote synchronisation has been paused and preserves the automatic synchronisation choices which were configured before the update. A configured Vault which was copied, restored, or opened in a new Obsidian profile can require this review because its device-local acknowledgement is not part of the Vault data. An empty local database is not accepted as evidence that it is safe to continue. An existing unconfigured Vault remains in onboarding without this synchronisation warning; its missing acknowledgement is not filled in automatically, so it is evaluated if the Vault is configured later. Closing the dialogue keeps synchronisation paused. When the detected state can be handled by the running version, the explicit resume action records the current internal database version and restores the configured behaviour. A persistent Notice and the `Review why synchronisation is paused` command reopen the review. An older installation cannot dismiss a pause caused by a newer database or settings version.
Internal database or settings compatibility reviews use a separate safety dialogue, not this pane. After the Obsidian layout is ready, a pending review opens as **Synchronisation paused for compatibility review**. The dialogue explains why remote synchronisation has been paused and preserves the automatic synchronisation choices which were configured before the update. Closing it or selecting **Keep synchronisation paused** leaves synchronisation paused. Use the persistent Notice's **Review why** link, or run the `Review why synchronisation is paused` command, to reopen it. Opening **Change Log** does not acknowledge the review.
## 1. Setup
A configured Vault which was copied, restored, or opened in a new Obsidian profile can require this review because its device-local acknowledgement is not part of the Vault data. An empty local database is not accepted as evidence that it is safe to continue. An existing unconfigured Vault remains in onboarding without this synchronisation warning; its missing acknowledgement is not filled in automatically, so it is evaluated if the Vault is configured later. When the detected state can be handled by the running version, **Resume synchronisation** records the current internal database version and restores the configured behaviour. An older installation cannot dismiss a pause caused by a newer database or settings version.
This pane is used for setting up Self-hosted LiveSync. There are several options to set up Self-hosted LiveSync.
## 1. Quick Setup and Extra menus
An unconfigured installation does not open the onboarding dialogue automatically or scan the Vault into the local database. A long-lived Notice offers the onboarding action. If the Notice is dismissed, open **Self-hosted LiveSync settings****Setup****Rerun Onboarding Wizard**.
Quick Setup contains the actions used to configure Self-hosted LiveSync. On Obsidian 1.13 or later these actions appear on the root settings page. In the pane-based interface, they remain available together on the **Quick Setup** pane.
An unconfigured installation does not open the onboarding dialogue automatically or scan the Vault into the local database. A long-lived Notice offers the onboarding action. If the Notice is dismissed, use **Rerun Onboarding Wizard** in the root **Quick Setup** group on Obsidian 1.13 or later. On earlier supported Obsidian versions, open **Self-hosted LiveSync settings****Quick Setup****Rerun Onboarding Wizard**.
Choose the new-device path when this device owns the files which should initialise synchronisation. Choose the existing-device path when it should receive an established remote state. The wizard reserves Rebuild or Fetch respectively before enabling the settings and requesting a restart, so the selected initialisation runs before the ordinary start-up scan.
### 1. Quick Setup
### 1. Setup actions
Most preferred method to setup Self-hosted LiveSync. You can setup Self-hosted LiveSync with a few clicks.
@@ -64,22 +101,15 @@ Completing manual CouchDB, Object Storage, or P2P setup creates the correspondin
This button only appears when the setup was not completed. If you have completed the setup manually, you can enable LiveSync on this device by this button.
### 2. To setup other devices
### 2. Set up other devices
#### Copy the current settings to a Setup URI
You can copy the current settings as a new setup URI. And this URI can be used to setup the other devices as [Use the copied setup URI](#use-the-copied-setup-uri).
### 3. Reset
### 3. Extra menus
#### Discard existing settings and databases
Reset the Self-hosted LiveSync settings and databases.
**Hazardous operation. Please be careful when using this.**
### 4. Enable extra and advanced features
To keep the set-up dialogue simple, some panes are hidden in default. You can enable them here.
To keep the settings dialogue concise, some menus and features are hidden by default. On Obsidian 1.13 or later, enable them through **General Settings****Extra menus**. In the pane-based interface, the same controls appear in General Settings.
#### Enable advanced features
@@ -465,6 +495,22 @@ Setting key: P2P_turnCredential
The password or credential for authentication with the TURN server.
#### P2P message size
Setting key: P2P_maxWirePayloadBytes
This profile setting limits each outgoing Commonlib RPC message before Trystero applies its own framing. It is not a Vault Chunk size, an IP MTU, or an SCTP fragment size. The available presets are **Standard** (15,360 bytes), **Reduced** (2,048 bytes), **Conservative** (1,024 bytes), and **Maximum compatibility** (800 bytes). Smaller values trade throughput for compatibility on paths which appear to drop larger WebRTC messages.
The sender controls the size of its outgoing messages. Select the same conservative preset on every device which may send across the constrained path. Existing profiles without this key use **Standard**. P2P connection strings and encrypted Setup URIs retain the selected preset.
#### Connection path
Setting key: P2P_connectionPath
**Automatic** lets WebRTC select a viable direct or TURN-relayed path and is the default. **TURN relay only** forces `iceTransportPolicy: 'relay'` and is available only when the profile contains at least one valid `turn:` or `turns:` URL. Removing the last valid TURN URL while relay-only mode is selected restores **Automatic** and displays a Notice.
This choice belongs to the P2P profile and is retained in P2P connection strings and encrypted Setup URIs. Separate profiles may use the same Group ID and credentials with different compatibility choices; only the selected P2P profile is active.
## 4. Sync Settings
### 1. Synchronisation Preset
@@ -528,7 +574,19 @@ Sync automatically after merging files
Setting key: keepReplicationActiveInBackground
Desktop only; uses more battery and network. This setting applies to continuous and periodic replication.
Finite remote operations, including one-shot replication, P2P peer discovery and selection, rebuilds, fetches, and remote chunk fetching, request best-effort screen-awake protection automatically and do not require this setting. That protection does not guarantee execution while Obsidian is hidden or while the operating system suspends the device.
#### Allow sleep during synchronisation
Setting key: allowSleepDuringSynchronisation
Allow the operating system to sleep while finite synchronisation operations are in progress. This option applies on every platform and is disabled by default. When it is disabled, finite operations request best-effort screen-awake protection.
#### Allow sleep during synchronisation on the desktop
Setting key: allowSleepDuringSynchronisationOnDesktop
Desktop only. Allow the operating system to sleep during finite synchronisation operations even when the general option is disabled. This option is enabled by default, so periodic or event-driven synchronisation does not repeatedly prevent automatic desktop sleep. Disable it to retain best-effort screen-awake protection on desktop.
Setup URIs preserve both sleep preferences. Older Setup URIs which do not contain them use the defaults described above. The preferences cover finite remote operations, including one-shot replication, P2P peer discovery and selection, rebuilds, fetches, remote chunk fetching, and applying downloaded documents to the Vault. They do not control whether continuous replication remains active while Obsidian is hidden, and allowing sleep does not force the operating system to suspend the device.
### 3. Update thinning
@@ -561,7 +619,7 @@ Should we keep folders that do not have any files inside?
### 5. Conflict resolution (Advanced)
Conflict resolution preserves unknown local content and automatically merges only when the available revision history supplies a safe shared base. See [Conflict resolution and revision provenance](specs_conflict_resolution.md) for the revision-tree rules, stale and concurrent resolutions, binary-file limitation, and the device-local provenance used for operations while a conflict is live.
Conflict resolution preserves unknown local content and automatically merges only when the available revision history supplies a safe shared base. See [Conflict resolution and revision provenance](specs_conflict_resolution.md) for the revision-tree rules, stale and concurrent resolutions, binary-file limitation, and the device-local provenance used while a conflict remains unresolved.
#### (BETA) Always overwrite with a newer file
@@ -733,9 +791,11 @@ Recreate chunks from files currently present in the Vault. This can repair missi
#### Inspect conflicts and file/database differences
Compare each Vault file with every current live revision in the local database. Each winner and conflict revision is shown separately with its exact revision identifier, local chunk availability, and relationship to the current Vault file. Unavailable shared ancestors are reported separately because they prevent conservative three-way merging but are not live revisions which can be discarded.
Compare each Vault file with every current leaf revision in the local database. Each winner and conflict revision is shown separately with its exact revision identifier, local chunk availability, and relationship to the current Vault file. Unavailable shared ancestors are reported separately because they prevent conservative three-way merging but are not current leaves which can be discarded.
Select **Begin inspection** to run the inspection. Each reported file and live revision has a wrench menu for read-only comparison, applying an exact database revision to the Vault, recording an exact byte match, preserving the Vault file as a child of a selected branch, retrying chunk retrieval, or explicitly discarding a branch. Destructive actions require confirmation. Follow [Recover a conflicted or mismatched file](recovery.md#recover-a-conflicted-or-mismatched-file) before changing revision history.
Select **Begin inspection** to run the inspection. Each reported file and current leaf revision has a wrench menu for read-only comparison, applying an exact database revision to the Vault, recording an exact byte match, preserving the Vault file as a child of a selected branch, retrying chunk retrieval, or explicitly discarding a branch. Destructive actions require confirmation. Follow [Recover a conflicted or mismatched file](recovery.md#recover-a-conflicted-or-mismatched-file) before changing revision history.
The same inspection also reports local Metadata whose stored document ID does not agree with its recorded path. A stale entry does not suppress ordinary inspection when consistently addressed Metadata can still be resolved for that logical path; otherwise, the unresolved path is excluded from ordinary file-repair actions. When the current winner has no conflict leaves and has an unambiguous target, its wrench menu offers a separately confirmed, one-entry repair. The target is derived from the current local file-name case and path obfuscation settings, then written and verified before the obsolete ID is removed. Ambiguous, conflicted, deleted, excluded, or otherwise unsafe entries remain read-only. This action does not rename Vault files or folders. Follow [Repair a Metadata document ID mismatch](recovery.md#repair-a-metadata-document-id-mismatch) for the complete backup, repair, propagation, and verification procedure. For widespread naming differences across devices, use that guide to choose an authoritative Vault, correct its storage names while Obsidian is closed, rebuild the central remote, and reset the other devices.
#### Resolve All conflicted files by the newer one
@@ -1049,10 +1109,14 @@ Delete all data on the remote server.
### 6. Garbage Collection V3 (CouchDB only)
Garbage Collection V3 identifies chunk documents which are not reachable from any current file or live conflict branch, creates logical deletions for those chunks locally, propagates the deletions to CouchDB, and requests remote compaction.
Garbage Collection V3 identifies Chunk documents which are not reachable from any current file or conflict branch, creates logical deletions for those Chunks locally, propagates the deletions to CouchDB, and requests remote compaction.
Use it only when the Vault, local database, and remote are healthy, and every relevant device has synchronised. It can make an ordinary superseded file revision unreadable when no live state still needs its chunks. It does not repair corruption or replace a deliberate rebuild. See the [Garbage Collection V3 specification](specs_garbage_collection.md).
Use it only when the Vault, local database, and remote are healthy, and every relevant device has synchronised. It can make an ordinary superseded file revision unreadable when no current state still needs its Chunks. It does not repair corruption or replace a deliberate rebuild. See the [Garbage Collection V3 specification](specs_garbage_collection.md).
### 7. Reset
#### Discard existing settings and databases
Reset the Self-hosted LiveSync settings and local database. This is a hazardous operation; make a backup before using it.
#### Delete local database to reset or uninstall Self-hosted LiveSync
+1 -1
View File
@@ -50,7 +50,7 @@ Use a new bucket prefix, or a prefix whose contents you deliberately intend to r
![Final Object Storage overwrite confirmation](../images/object-storage-setup/guide-object-storage-setup-first-rebuild-confirmation.png)
6. A new prefix may show `Fetch Remote Configuration Failed` because it has no saved configuration. Select `Skip and proceed` only for a genuinely new prefix. Otherwise, stop and check the endpoint, credentials, bucket, and prefix.
6. A new prefix may show `No Synchronisation Settings Found`. Select `Use this device's settings` for a genuinely new prefix. If you expected existing settings, cancel and check the endpoint, credentials, bucket, and prefix. Cancellation restarts with Vault and database reflection paused; keep LiveSync disabled until the remote has been checked. If `Could Not Read Synchronisation Settings` appears, retry, or use this device's settings to continue the overwrite. The overwrite still requires a working connection.
![Expected missing remote configuration for a new Object Storage prefix](../images/object-storage-setup/guide-object-storage-setup-missing-remote-configuration.png)
+9 -4
View File
@@ -118,10 +118,12 @@ Please refer to the [official document](https://docs.couchdb.org/en/stable/insta
Deno 2 is required. Export the CouchDB connection and database details, then run the provisioning wrapper:
The `username` and `password` in this step are CouchDB administrator credentials. For Docker or Docker Compose, use the same values supplied as `COUCHDB_USER` and `COUCHDB_PASSWORD`. For a direct installation, use the administrator configured during CouchDB setup. The wrapper does not create a separate non-administrator synchronisation account.
```
export hostname=http://localhost:5984
export username=<INSERT USERNAME HERE>
export password=<INSERT PASSWORD HERE>
export username=<INSERT COUCHDB ADMINISTRATOR USERNAME HERE>
export password=<INSERT COUCHDB ADMINISTRATOR PASSWORD HERE>
export database=obsidiannotes
curl -s https://raw.githubusercontent.com/vrtmrz/obsidian-livesync/main/utils/couchdb/couchdb-init.sh | bash
```
@@ -135,7 +137,7 @@ The wrapper runs the exact registry-pinned Commonlib consumer. When `database` i
If you are using Docker Compose and the above command does not work or displays `ERROR: Hostname missing`, you can try running the following command, replacing the placeholders with your own values:
```
curl -s https://raw.githubusercontent.com/vrtmrz/obsidian-livesync/main/utils/couchdb/couchdb-init.sh | hostname=http://<YOUR SERVER IP>:5984 username=<INSERT USERNAME HERE> password=<INSERT PASSWORD HERE> database=obsidiannotes bash
curl -s https://raw.githubusercontent.com/vrtmrz/obsidian-livesync/main/utils/couchdb/couchdb-init.sh | hostname=http://<YOUR SERVER IP>:5984 username=<INSERT COUCHDB ADMINISTRATOR USERNAME HERE> password=<INSERT COUCHDB ADMINISTRATOR PASSWORD HERE> database=obsidiannotes bash
```
## 3. Expose CouchDB to the Internet
@@ -170,10 +172,13 @@ Now `https://tiles-photograph-routine-groundwater.trycloudflare.com` is our serv
> A generated Setup URI is the recommended path because it carries the current defaults for a new Vault and the selected remote profile. If a Setup URI cannot be generated, follow [Configure CouchDB manually on the first device](./quick_setup.md#configure-couchdb-manually-on-the-first-device), then generate a new Setup URI from that working device for every additional device.
### 1. Generate the setup URI on a desktop device or server
The `username` and `password` here are the credentials which Self-hosted LiveSync will store for routine access to this database. They may be the administrator credentials from step 2. If you have separately configured a CouchDB account with the required access to this database, use that account instead. Neither the provisioning wrapper nor the Setup URI generator creates that separate account.
```bash
export hostname=https://tiles-photograph-routine-groundwater.trycloudflare.com
export database=obsidiannotes
export username=johndoe
export username=<INSERT COUCHDB USERNAME FOR LIVESYNC>
export password=<INSERT THE COUCHDB PASSWORD>
export passphrase=<INSERT A STRONG VAULT ENCRYPTION PASSPHRASE>
export uri_passphrase=<INSERT A SEPARATE SETUP URI PASSPHRASE> # Optional
+46 -30
View File
@@ -4,7 +4,7 @@ This document describes the conflict-resolution and file-reflection guarantees u
## Revision-tree model
PouchDB stores a document as a revision tree. It selects one live leaf as the deterministic winner and reports the other live leaves as conflicts. That winner is not proof that its content is newer, safer, or the version currently shown in the Vault.
PouchDB stores a document as a revision tree. It selects one current leaf as the deterministic winner and reports the other current leaves as conflicts. That winner is not proof that its content is newer, safer, or the version currently shown in the Vault.
For example:
@@ -14,9 +14,25 @@ A1
└── B2 ── C2
```
The two live leaves are `D1` and `C2`. Their nearest shared ancestor is `A1`; neither `B1` nor `B2` is shared. A conservative three-way merge therefore compares the changes from `A1` to each leaf. Matching generation numbers, or selecting the first older revision from one branch, does not prove shared ancestry.
The two current leaves are `D1` and `C2`. Their nearest shared ancestor is `A1`; neither `B1` nor `B2` is shared. A conservative three-way merge therefore compares the changes from `A1` to each leaf. Matching generation numbers, or selecting the first older revision from one branch, does not prove shared ancestry.
Resolving a conflict writes the selected or merged result on one observed branch and deletes the other observed live leaf. A stale device may still have the deleted leaf's content in its Vault when it receives the resolution.
Resolving a conflict writes the selected or merged result on one observed branch and deletes the other observed conflict leaf. A stale device may still have the deleted leaf's content in its Vault when it receives the resolution.
### Independent revision properties
The modifiers defined under [Revision](terms.md#revision) describe independent properties, rather than exclusive revision types. The winner is a database-tree role, Vault-matching describes current file-state equality, and displayed identifies the device-local branch recorded for the Vault. The same revision commonly has all three properties, but synchronisation, conflicts, local edits, and missing provenance can separate them.
| Situation | Winner | Vault-matching | Displayed |
| ---------------------------------------------------- | ------------------ | --------------------------------------------------- | ---------------------------------------------------- |
| A present Vault file is fully converged | `R` | `R` | `R` |
| The Vault displays conflict leaf `C` | `W` | `C` | `C` |
| The database advances before Vault reflection | new winner `W2` | previous revision `R`, while the Vault is unchanged | `R` |
| A local edit of displayed revision `R` is pending | independent | none, or a coincidental content match | `R`, as the branch which the edit must extend |
| Provenance is missing and exactly one revision fits | independent | `M` | none, then `M` after safe reconstruction |
| Provenance is missing and several revisions fit | independent | every matching revision | none |
| A logical-deletion winner agrees with an absent file | deleted winner `D` | `D`, and possibly other logical-deletion revisions | none; an absent file retains no displayed provenance |
At most one revision is the winner, more than one revision can be Vault-matching, and at most one revision can be displayed for a path on one device. A displayed revision may stop matching the Vault while a local edit is pending, but its branch identity remains authoritative until that edit is stored or the relationship is safely reconstructed.
## Implemented 1.0 guarantees
@@ -25,7 +41,7 @@ Resolving a conflict writes the selected or merged result on one observed branch
- A receiving Vault file which exactly matches any available revision in the document tree is treated as previously synchronised content. This includes an ancestor below a deleted losing leaf.
- A receiving Vault file whose bytes do not match any available revision is preserved as an unsynchronised local change.
- File bytes, rather than path, size, modification time, or revision generation, determine whether content is known.
- Three or more live versions are reviewed one pair at a time in a deterministic order, with each completed pair committed before the next live pair is read.
- Three or more current versions are reviewed one pair at a time in a deterministic order, with each completed pair committed before the next pair is read.
- Each device records the exact revision most recently reflected in each Vault file. An edit, deletion, or case-only rename made while a conflict is active extends that displayed branch rather than the deterministic database winner.
- A cross-path rename stores the target before logically deleting only the displayed source branch.
@@ -50,32 +66,32 @@ The compatibility implementation currently selects the newer modification time f
A document revision can remain in the PouchDB tree while one or more chunks needed to reconstruct its content are unavailable. Missing content is not evidence that the revision is obsolete. LiveSync therefore leaves an unreadable winner or conflict revision in the tree instead of deleting it during automatic conflict processing.
**Hatch** → **Inspect conflicts and file/database differences** inspects the current winner, every current conflict revision, and the nearest shared ancestor for each conflict. A logical-deletion winner and an absent Vault file already agree, so that state is not reported unless another live branch still requires attention. When the Vault already matches the winner but conflict branches remain, the card shows the compact status `✅ Vault matches winner · ⚠️ Conflicts: N`; matching the winner does not mean that the conflict has been resolved.
**Hatch** → **Inspect conflicts and file/database differences** inspects the current winner, every current conflict revision, and the nearest shared ancestor for each conflict. A logical-deletion winner and an absent Vault file already agree, so that state is not reported unless another conflict branch still requires attention. When the Vault already matches the winner but conflict branches remain, the card shows the compact status `✅ Vault matches winner · ⚠️ Conflicts: N`; matching the winner does not mean that the conflict has been resolved.
Each reported live revision has a compact wrench menu. The available actions depend on the exact revision and current Vault state:
Each reported current leaf revision has a compact wrench menu. The available actions depend on the exact revision and current Vault state:
- **Compare with Vault** opens the existing difference dialogue in read-only mode for differing text files.
- **Apply this revision to Vault** writes the selected readable revision, even when it is not the database winner. Replacing an existing file requires confirmation.
- **Mark this revision as the Vault version** is offered when the bytes already match. It records exact device-local provenance without creating a child revision, and refuses the operation if the file changed after inspection.
- **Store Vault file as a child of this revision** preserves the current Vault bytes on the explicitly selected live branch.
- **Store Vault file as a child of this revision** preserves the current Vault bytes on the explicitly selected current branch.
- **Apply logical deletion to Vault** removes an existing Vault file after confirmation. An absent file needs no retained deletion provenance.
- **Retry reading revision** attempts the configured chunk-retrieval path again. It does not change the revision tree.
- **Discard this branch** is available for each exact live revision while at least one other live branch remains. It requires confirmation and creates a logical deletion on only the selected branch without changing the current Vault file.
- **Discard unreadable revision** remains available as a recovery action when an unreadable revision is the only live leaf. It requires confirmation because no other database branch remains.
- **Discard this branch** is available for each exact current leaf while at least one other current leaf remains. It requires confirmation and creates a logical deletion on only the selected branch without changing the current Vault file.
- **Discard unreadable revision** remains available as a recovery action when an unreadable revision is the only current leaf. It requires confirmation because no other database branch remains.
Every mutating action rechecks that the selected revision is still a current live leaf. If another operation resolved or replaced it, the action fails and the card is refreshed instead of extending an obsolete branch.
Every mutating action rechecks that the selected revision is still a current leaf. If another operation resolved or replaced it, the action fails and the card is refreshed instead of extending an obsolete branch.
The card uses compact, mobile-friendly diagnostic rows with an emoji and a text label. `🧩 Missing chunks: N` identifies an unreadable revision. In the database row, `Δsize` is decoded size minus recorded size; in the Vault row, `Δsize vs DB` is Vault size minus decoded database size. `Δtime` is Vault modification time minus database modification time. The ordinary two-second comparison window still labels which side is newer. These values help diagnose a mismatch; path, size, and modification time do not prove revision identity or decide which content should win.
A shared ancestor is informational. An ancestor which is no longer a live revision cannot be discarded independently through this workflow. If its body is unavailable, conservative three-way merge remains disabled, although readable live revisions can still be selected manually.
A shared ancestor is informational. An ancestor which is no longer a current leaf cannot be discarded independently through this workflow. If its body is unavailable, conservative three-way merge remains disabled, although readable current leaf revisions can still be selected manually.
Logical deletion does not recreate missing bytes, purge the document history, or prove that the deleted version was unimportant. Another replica or backup may still contain the missing chunks. Recover from that source before discarding a revision whenever possible.
**Recreate chunks for current Vault files** can recreate chunks only from files which are readable in the current Vault. It cannot reconstruct unique bytes from an unavailable historical or conflict revision.
Garbage Collection V3 treats every live conflict revision and its nearest available shared ancestor as reachable. Their locally available chunks are retained until the conflict is resolved. After resolution, chunks used only by the discarded branch or no-longer-needed merge ancestry can become eligible for collection. See the [Garbage Collection V3 specification](specs_garbage_collection.md).
Garbage Collection V3 treats every conflict leaf and its nearest available shared ancestor as reachable. Their locally available chunks are retained until the conflict is resolved. After resolution, chunks used only by the discarded branch or no-longer-needed merge ancestry can become eligible for collection. See the [Garbage Collection V3 specification](specs_garbage_collection.md).
A generation-one revision has no parent. When its body is unavailable, LiveSync cannot preserve a changed Vault file as a sibling branch without inventing ancestry. It leaves the operation unresolved. Recover the missing chunks from another replica or backup, or explicitly discard that live revision. If the current Vault file is the intended replacement, it can be stored after the unreadable revision has been logically deleted.
A generation-one revision has no parent. When its body is unavailable, LiveSync cannot preserve a changed Vault file as a sibling branch without inventing ancestry. It leaves the operation unresolved. Recover the missing chunks from another replica or backup, or explicitly discard that current leaf. If the current Vault file is the intended replacement, it can be stored after the unreadable revision has been logically deleted.
### Two devices independently create the same path
@@ -100,19 +116,19 @@ and otherwise asks the user.
## Stale and concurrent resolutions
A device can resolve only the leaves which it has observed. If another device has already extended a branch, later replication can reveal another live leaf and require another resolution. Two devices can also produce different resolutions concurrently, leaving multiple live leaves after their trees meet.
A device can resolve only the leaves which it has observed. If another device has already extended a branch, later replication can reveal another current leaf and require another resolution. Two devices can also produce different resolutions concurrently, leaving multiple current leaves after their trees meet.
A higher revision generation or modification time does not make either result authoritative. The resolver must examine every current live leaf again until one result remains or user action is required. This is continued conflict processing, not a reset of the synchronisation checkpoint.
A higher revision generation or modification time does not make either result authoritative. The resolver must examine every current leaf again until one result remains or user action is required. This is continued conflict processing, not a reset of the synchronisation checkpoint.
## More than two live versions
## More than two current versions
When three or more versions remain, LiveSync compares the current PouchDB winner with one conflict leaf at a time. Commonlib orders the remaining candidates by revision generation ascending, original leaf modification time ascending, then the complete revision ID in code-unit lexical order. A missing or non-finite modification time is ordered before a finite value. Modification time makes pair selection reproducible here; it does not decide which content wins.
For each pair, LiveSync first collapses identical content, then attempts a conservative sensible merge, and finally asks the user when neither automatic action is safe. A completed action is written to the ordinary revision tree and its losing observed leaf is deleted before LiveSync reads the remaining live leaves again. There is no separate persistent merge accumulator.
For each pair, LiveSync first collapses identical content, then attempts a conservative sensible merge, and finally asks the user when neither automatic action is safe. A completed action is written to the ordinary revision tree and its losing observed leaf is deleted before LiveSync reads the remaining current leaves again. There is no separate persistent merge accumulator.
**Concat both** writes the concatenated result as a new child of the displayed PouchDB winner, then deletes only the other leaf shown in that dialogue. With two live versions, that action resolves the conflict. With three or more, the new child remains live against every untouched leaf and becomes part of the next pairwise review; it does not create an unrelated root or consume an unseen branch.
**Concat both** writes the concatenated result as a new child of the displayed PouchDB winner, then deletes only the other leaf shown in that dialogue. With two current versions, that action resolves the conflict. With three or more, the new child remains a current leaf against every untouched leaf and becomes part of the next pairwise review; it does not create an unrelated root or consume an unseen branch.
Consequently, choosing **Not now** or closing Obsidian cannot undo a completed pair. After restart, LiveSync reconstructs the next pair from the live tree. If replication changes either revision while a dialogue is open, LiveSync discards the stale selection, refreshes the live count, and rechecks the path rather than deleting a revision which was not the one shown.
Consequently, choosing **Not now** or closing Obsidian cannot undo a completed pair. After restart, LiveSync reconstructs the next pair from the current revision tree. If replication changes either revision while a dialogue is open, LiveSync discards the stale selection, refreshes the current-version count, and rechecks the path rather than deleting a revision which was not the one shown.
## Device-local file provenance
@@ -133,7 +149,7 @@ When no record exists, LiveSync may reconstruct the displayed revision only if t
## Operations while a conflict exists
- Editing a file writes a child of its recorded or uniquely reconstructed displayed revision.
- Deleting a file writes a logical-deletion child of that revision. It uses LiveSync's `deleted` marker, rather than a PouchDB `_deleted` tombstone, so the deletion remains a live branch which can replicate and be resolved against the other branch.
- Deleting a file writes a logical-deletion child of that revision. It uses LiveSync's `deleted` marker, rather than a PouchDB `_deleted` tombstone, so the deletion remains a current branch which can replicate and be resolved against the other branch.
- A case-only rename writes the new path as a child in the same document tree.
- A cross-path rename stores the target document first, then writes a logical-deletion child on the displayed source branch.
@@ -146,7 +162,7 @@ uninterrupted conflict episode in the current plug-in session. Ordinary file
checks and replication do not reopen the dialogue while at least one conflict
leaf remains. If the in-editor status display is enabled, the active file shows
**This file has 3 unresolved versions. They will be reviewed one pair at a
time.** for three or more live versions, using the current count, and **This
time.** for three or more current versions, using the current count, and **This
file has unresolved conflicts.** for two. Postponement therefore does not make
the conflict invisible.
@@ -163,8 +179,8 @@ When synchronisation supplies a resolved document, the existing incoming-file
processing event closes an open conflict dialogue for that path. The same event
rechecks the local revision tree: if no conflict leaf remains, it ends any
postponed episode and removes the active-file warning. If conflict leaves still
exist, the stale dialogue closes and the warning changes to the current live
version count. A postponed episode stays postponed; otherwise, subsequent
exist, the stale dialogue closes and the warning changes to the current-version
count. A postponed episode stays postponed; otherwise, subsequent
conflict processing may open a fresh dialogue for the current revision tree.
Each dialogue owns its completion result, so a prompt which is answered or
closed immediately still completes the waiting conflict operation; the result
@@ -193,7 +209,7 @@ A1
└── B2 ── C2 ── D2 Android edit
```
After synchronisation, both devices receive `C1` and `D2` as the live branches. The edit is not moved silently onto `C1`, and ordinary conflict resolution can compare the real descendants.
After synchronisation, both devices receive `C1` and `D2` as the current branches. The edit is not moved silently onto `C1`, and ordinary conflict resolution can compare the real descendants.
### A user deletes the branch shown on one device
@@ -205,13 +221,13 @@ A1
└── B2 ── C2 ── D2 (deleted: true)
```
The deletion remains one side of the live conflict. The user can still choose between the content at `C1` and deleting the file. LiveSync does not delete `C1` merely because PouchDB selected it as the winner.
The deletion remains one current leaf of the conflict. The user can still choose between the content at `C1` and deleting the file. LiveSync does not delete `C1` merely because PouchDB selected it as the winner.
### A user renames a conflicted file
If the user changes only the spelling case, such as `Note.md` to `note.md`, LiveSync keeps the rename in the same revision tree and extends the revision displayed on that device.
If the user renames `draft.md` to `published.md`, LiveSync stores `published.md` before it marks the displayed `draft.md` branch as logically deleted. If an interruption occurs between those operations, the recoverable result is a duplicate which can be reviewed, rather than loss of the only copy. Any other live branch of `draft.md` remains available for conflict resolution.
If the user renames `draft.md` to `published.md`, LiveSync stores `published.md` before it marks the displayed `draft.md` branch as logically deleted. If an interruption occurs between those operations, the recoverable result is a duplicate which can be reviewed, rather than loss of the only copy. Any other conflict branch of `draft.md` remains available for conflict resolution.
### A remote resolution reaches a device which still shows the losing content
@@ -221,7 +237,7 @@ If the user edited the file on Mac before the resolution arrived, the bytes no l
### A three-version review is interrupted
Mac receives three live versions of `shared.md`. The active-file status reports three unresolved versions, and the first dialogue compares the deterministic winner with the first ordered conflict leaf. The user completes that pair, leaving two live versions, then chooses **Not now** on the next dialogue and closes Obsidian.
Mac receives three current versions of `shared.md`. The active-file status reports three unresolved versions, and the first dialogue compares the deterministic winner with the first ordered conflict leaf. The user completes that pair, leaving two current versions, then chooses **Not now** on the next dialogue and closes Obsidian.
The first decision has already changed the ordinary revision tree. On restart, LiveSync reads the two surviving versions and presents only that remaining pair; it does not reconstruct the original three-version state. If another device resolves the remaining pair before or while the dialogue is open, the warning disappears and the stale dialogue closes.
@@ -251,8 +267,8 @@ Do not:
## Verification
Commonlib's real-PouchDB and injected-boundary unit tests cover unequal branch lengths, exact shared ancestry, deterministic ordering of multiple live leaves, a sensible stage followed by reconstruction of a manual pair, content below a deleted losing leaf, recorded and reconstructed branch identity, ambiguous matches, conflict-time editing, missing-body preservation when parent metadata is available, refusal to invent a parent for a generation-one revision, logical deletion, case-only rename, cross-path rename, and safe unproven fallbacks.
Commonlib's real-PouchDB and injected-boundary unit tests cover unequal branch lengths, exact shared ancestry, deterministic ordering of multiple current leaves, a sensible stage followed by reconstruction of a manual pair, content below a deleted losing leaf, recorded and reconstructed branch identity, ambiguous matches, conflict-time editing, missing-body preservation when parent metadata is available, refusal to invent a parent for a generation-one revision, logical deletion, case-only rename, cross-path rename, and safe unproven fallbacks.
LiveSync's optional real-Obsidian two-Vault checks have two scopes. `E2E_OBSIDIAN_INCLUDE_MARKDOWN_CONFLICT=true` resolves and edits a Markdown conflict, propagates it to a Vault which still displays the deleted losing content, and requires one live result to remain. `E2E_OBSIDIAN_INCLUDE_CONFLICT_OPERATIONS=true` edits, deletes, case-renames, and cross-path-renames files while conflicts remain active; it verifies the parent revision of each resulting branch, replicates those exact trees, and confirms that the other live branches remain intact.
LiveSync's optional real-Obsidian two-Vault checks have two scopes. `E2E_OBSIDIAN_INCLUDE_MARKDOWN_CONFLICT=true` resolves and edits a Markdown conflict, propagates it to a Vault which still displays the deleted losing content, and requires one current result to remain. `E2E_OBSIDIAN_INCLUDE_CONFLICT_OPERATIONS=true` edits, deletes, case-renames, and cross-path-renames files while conflicts remain active; it verifies the parent revision of each resulting branch, replicates those exact trees, and confirms that the other conflict branches remain intact.
The focused `test:e2e:obsidian:conflict-dialog-policy` scenario creates three live versions in one real Obsidian Vault. It verifies the count warning, commits a concatenated child of the displayed winner, confirms that the untouched leaf remains as one conflict, postpones that remaining pair, restarts the isolated Obsidian profile, and confirms that only the live pair is reconstructed. It also verifies that an incoming resolution closes a stale dialogue, completes the waiting conflict operation, and clears the warning. The repair scenario removes a referenced local chunk, confirms that the exact unreadable live revision remains in the tree, and exercises explicit retry and discard controls without deleting another live revision.
The focused `test:e2e:obsidian:conflict-dialog-policy` scenario creates three current versions in one real Obsidian Vault. It verifies the count warning, commits a concatenated child of the displayed winner, confirms that the untouched leaf remains as one conflict, postpones that remaining pair, restarts the isolated Obsidian profile, and confirms that only the current pair is reconstructed. It also verifies that an incoming resolution closes a stale dialogue, completes the waiting conflict operation, and clears the warning. The repair scenario removes a referenced local chunk, confirms that the exact unreadable current leaf remains in the tree, and exercises explicit retry and discard controls without deleting another current leaf.
+6 -6
View File
@@ -33,19 +33,19 @@ If the initial synchronisation, device inspection, or confirmation fails, the wo
A chunk remains reachable when it is referenced by any of the following:
- the current database winner for a file;
- any other live conflict revision for that file;
- an available revision on either side of a live conflict which is required to describe the divergence; or
- the nearest available revision shared by both live conflict branches.
- any conflict leaf for that file;
- an available revision on either side of a current conflict which is required to describe the divergence; or
- the nearest available revision shared by both conflict branches.
Chunk identifiers are content-derived and shared between files. Reachability is therefore collected into one set across the database. A chunk used by two or more current files remains protected even when one file is updated or deleted.
An ordinary superseded linear revision does not protect its former chunks. Once no current file or live conflict branch references a chunk, it can be collected. After a conflict is resolved, chunks unique to the discarded branch and to no-longer-needed merge ancestry can also become eligible.
An ordinary superseded linear revision does not protect its former chunks. Once no current file or conflict leaf references a chunk, it can be collected. After a conflict is resolved, chunks unique to the discarded branch and to no-longer-needed merge ancestry can also become eligible.
## Consequences
Garbage Collection deliberately trades historical recoverability for storage. A metadata revision may remain in the revision tree after a chunk which only that superseded revision used has been collected, so that historical body can become unreadable. Remote compaction can then discard old CouchDB revision bodies. Tombstones and retained metadata also consume storage, so the operation does not promise the smallest possible database.
Writing the same bytes again produces the same content-derived chunk identifier. If that chunk was collected previously, the normal chunk-writing path creates a new live revision for it, and ordinary replication can transfer it again. This does not recover an older file revision automatically; it only makes the newly written content available.
Writing the same bytes again produces the same content-derived chunk identifier. If that chunk was collected previously, the normal chunk-writing path creates a new non-deleted revision of the Chunk document, and ordinary replication can transfer it again. This does not recover an older file revision automatically; it only makes the newly written content available.
Garbage Collection does not reconstruct a chunk which is already missing, determine whether an unreadable revision is important, or repair a damaged local database. Use **Inspect conflicts and file/database differences**, another healthy replica, or a backup for those cases. Use **Overwrite Server Data with This Device's Files** only when a chosen Vault is authoritative and a deliberate remote rebuild is required.
@@ -55,7 +55,7 @@ Commonlib tests use real in-memory PouchDB revision trees to verify:
- collection eligibility after a normal file update;
- protection of chunks shared by multiple current files;
- protection of all live conflict branches and their nearest available shared ancestor;
- protection of all conflict branches and their nearest available shared ancestor;
- eligibility of losing-branch and ancestor-only chunks after conflict resolution;
- propagation of chunk deletion to another PouchDB database; and
- recreation and propagation when the same content is written again.
+19 -1
View File
@@ -43,7 +43,7 @@ All guidelines and conventions listed below are disclosed and maintained solely
- Database Suffix (additionalSuffixOfDatabaseName)
- A unique suffix appended to the database name to allow synchronising multiple vaults with the same name on the same remote server.
- E2EE Algorithm
- The cryptographic algorithm version used for end-to-end encryption. All devices in the synchronisation group must be configured with a compatible version (such as `V2` or `V1`).
- The cryptographic algorithm version used for end-to-end encryption. All synchronising devices must be configured with a compatible version (such as `V2` or `V1`).
- Eden (Eden Chunks)
- A performance optimisation where newly created chunks are held within the document until they stabilise, before graduating to independent chunks.
- Fast Setup (Simple Fetch)
@@ -80,6 +80,22 @@ All guidelines and conventions listed below are disclosed and maintained solely
- A recovery setting that restricts the propagation of changes from the database to local storage, ignoring any file events (such as accidental mass deletions) that occurred after a specified date and time.
- Reset Synchronisation on This Device
- A maintenance operation (formerly known as `Fetch everything`) that discards the local database and reconstructs it by downloading all data from the remote server.
#### Revision
A revision is a version of one PouchDB/CouchDB document. Concurrent changes can form a revision tree with more than one current branch.
Revision modifiers describe independent properties. More than one may apply to the same revision:
- **leaf**: Has no known child revision.
- **winner**: Is the leaf selected by PouchDB/CouchDB as the current document.
- **conflict**: Is another current leaf which was not selected as the winner.
- **Vault-matching**: Represents the same file contents, or the same absent-file state, as the current Vault. More than one revision may match.
- **displayed**: Is recorded by valid device-local file provenance as the branch represented in the Vault. A pending local edit may no longer match its bytes, but still extends this recorded branch.
- **logically deleted**: Represents the absence of the file through a deletion marker. A logically deleted revision may also be a leaf, winner, conflict, or Vault-matching revision. An absent file retains no displayed provenance.
Avoid **live revision** in prose because it can ambiguously mean either a current leaf or a non-deleted revision. See [Independent revision properties](specs_conflict_resolution.md#independent-revision-properties) for the relationship between revision-tree roles, Vault state, and device-local provenance.
- Scram (Scram Switches)
- Emergency controls in the settings that allow users to suspend file watching or database writes to prevent corruption.
- Segmenter (Segmented-splitter)
@@ -94,6 +110,8 @@ All guidelines and conventions listed below are disclosed and maintained solely
- A data transfer method that downloads database documents as a continuous stream of events. It is significantly faster than traditional chunk-by-chunk HTTP requests and is used during Fast Setup to retrieve remote metadata quickly.
- Sync Mode
- The replication trigger mechanism. Users can select from `On Events` (synchronising on local file changes), `Periodic and Events` (synchronising at fixed intervals as well as on events), or `LiveSync` (continuous, real-time synchronisation).
- Synchronising devices
- Devices which participate in the same synchronisation for a Vault. The term describes membership rather than current activity, so it includes offline and idle devices.
- TURN Server (WebRTC P2P)
- A Traversal Using Relays around NAT server used as an optional fallback to relay encrypted WebRTC traffic when strict NAT or firewall rules block a direct peer connection. It is distinct from the signalling relay.
- Update Thinning (Batch database update)
+8
View File
@@ -63,3 +63,11 @@ Once you confirm your choices:
1. The plug-in performs a fast download of the remote database (`fetchLocalDBFast`).
2. It automatically runs a full scan (`synchroniseAllFilesBetweenDBandStorage`) in the foreground to reflect database changes in your local vault files immediately.
3. The plug-in finalises the process and resumes normal operational status.
### If Fast Setup Stops
Fast Setup records the last successfully stored remote position as it saves documents. A transient connection interruption is retried automatically from that position. The operation reports completion only after the captured remote state has been stored successfully.
If an error while decrypting data, reading the remote response, or writing to the local database stops the operation, LiveSync does not run the Vault scan or finalise the reconstructed local database. It retains the saved position, the selected data-processing method, and the fetch flag. File watching and database reflection also remain suspended so that a partly reconstructed database cannot be applied to the Vault or combined with new local changes.
Review the first specific error in **Show log**, correct its cause, then restart Obsidian to retry from the retained state. While you intend to continue the operation, do not remove the fetch flag or manually resume the Scram switches. If the same error remains after a restart, leave LiveSync suspended, preserve the available data, and [collect a report](../troubleshooting.md#collect-a-report).
+11
View File
@@ -36,9 +36,20 @@ Try these in order:
1. Put both devices on the same ordinary network and retry.
2. Remove a VPN temporarily if it blocks peer traffic, or use a trusted VPN such as Tailscale when it provides a reachable path between the devices.
3. In `P2P Configuration` -> `Advanced Settings`, configure a trusted TURN service.
4. Under `Connection compatibility`, select `TURN relay only` to test the configured TURN path without direct ICE candidates.
TURN is a fallback for encrypted WebRTC traffic. It is different from the required signalling relay. The project does not operate an official TURN service. A TURN provider cannot read encrypted Vault contents, but it can observe connection metadata and traffic volume.
For a small self-hosted deployment, the repository includes an optional [Coturn Compose starter](../../docker/coturn/README.md). It uses static credentials and does not include TLS or a managed credential service; review its network and security boundaries before exposing it.
## A connection opens but a transfer stalls
If peers can connect but a transfer repeatedly stalls on one network path, try the `P2P message size` presets under `Connection compatibility`. Start with `Reduced`, then try `Conservative` and `Maximum compatibility` only if needed.
The preset limits outgoing messages, so select the same value on every device which may send across the affected path. Smaller values add overhead and do not prove that packet fragmentation was the cause. Return to `Standard` when the path works reliably without the compatibility setting.
Compatibility choices are saved with the P2P profile. You may keep separate standard and compatibility profiles with the same Group ID and credentials, then select the profile appropriate to the current network.
## A connected peer does not receive later edits
An open signalling connection does not automatically move every change.
+35 -115
View File
@@ -1,145 +1,65 @@
# How to report an issue
Thank you for helping improve Self-hosted LiveSync!
Thank you for helping improve Self-hosted LiveSync. A concise report with the right evidence is more useful than trying several recovery operations before reporting the original symptom.
This document explains how to collect the information needed for an issue report. Issues with sufficient information will be prioritised.
Use the [issue report template](https://github.com/vrtmrz/obsidian-livesync/issues/new?template=issue-report.md) for the report itself. Use [Troubleshooting](troubleshooting.md) to diagnose a symptom or choose a recovery action.
---
## Preserve the original symptom
## Filled example
Do not reset a database, rebuild a remote, change transport, or enable P2P merely to see whether the problem disappears. These actions can change the evidence and may make the original cause harder to identify.
Here is an example of a well-filled report for reference.
If the problem may involve data loss, corruption, or unexpected deletion, preserve a copy of every readable affected file and stop editing it on other devices before changing settings.
### Abstract
Include when the problem began, whether it followed an update or restart, how often it occurs, and which device and remote type were involved.
The synchronisation hung up immediately after connecting.
## Required information
### Expected behaviour
### Describe the behaviour
- Synchronisation ends with the message `Replication completed`
- Everything synchronised
Complete the issue template with:
### Actually happened
- a one- or two-sentence summary;
- the expected and actual behaviour;
- repeatable steps, or the frequency and timing when reliable reproduction is not available; and
- the role of each relevant device, such as the device where the change originated and the device where the failure appeared.
- Synchronisation was cancelled with the message `TypeError: Failed to fetch` (visible in the plug-in log around lines 1012)
- No files synchronised
### Obsidian debug information
### Reproducing procedure
Open the command palette with `Ctrl`+`P` or `Command`+`P`, run `Show debug info`, and include its output for each relevant device. The device where the problem appeared is required. Information from the other participating devices is particularly useful for synchronisation problems.
1. Configure LiveSync with the settings shown in the attached report.
2. Click the sync button on the ribbon.
3. Synchronisation begins.
4. About two or three seconds later, the error `TypeError: Failed to fetch` appears.
5. Replication stops. No files synchronised.
### Full LiveSync report
### Obsidian debug info (Device 1 — Windows desktop)
Run `Generate full report for opening the issue with debug info` on the device where the problem appeared. For a synchronisation problem, also collect a report from another participating device when its settings or logs are relevant. The command copies the current LiveSync settings summary and up to 1,000 recent log lines. It collects verbose log lines even when `Verbose Log` is disabled, so you do not need to enable that setting before reproducing the problem.
```
SYSTEM INFO:
Obsidian version: v1.2.8
Installer version: v1.1.15
Operating system: Windows 10 Pro 10.0.19044
Login status: logged in
Catalyst license: supporter
Insider build toggle: off
Community theme: Minimal v6.1.11
Snippets enabled: 3
Restricted mode: off
Plugins installed: 35
Plugins enabled: 11
1: Self-hosted LiveSync v0.19.4
...
```
The command automatically redacts known credential fields in the settings summary. It cannot guarantee that private text in log messages or unrecognised configuration fields is removed. Review the complete output before sharing it. Remove or replace:
### Report from LiveSync
- usernames, passwords, passphrases, tokens, keys, and custom headers;
- private server URLs, network addresses, database names, bucket names, room identifiers, and relay details;
- Vault names, device names, and file paths; and
- file contents or other private text which appears in a log message.
```
----remote config----
cors:
credentials: "true"
...
---- Plug-in config ---
couchDB_URI: self-hosted
couchDB_USER: 𝑅𝐸𝐷𝐴𝐶𝑇𝐸𝐷
...
```
Document and chunk identifiers can also be private metadata, but they may be necessary for diagnosing file reconstruction and chunk availability. Decide deliberately whether to share them. If you remove them, state that the report was redacted and that this may limit the diagnosis.
### Plug-in log
For a large report, you may share a GitHub Gist after reviewing and redacting it. Deleting a Gist later cannot undo information which has already been disclosed.
```
2023/5/24 10:50:33->HTTP:GET to:/ -> failed
2023/5/24 10:50:33->TypeError:Failed to fetch
2023/5/24 10:50:33->could not connect to https://example.com/ : your vault
(TypeError:Failed to fetch)
```
## Additional evidence when relevant
---
### A problem involving one file
## How to collect each piece of information
Run `Copy database information for the active file`, or use **Hatch****Copy database information for a file** to select another file.
### Obsidian debug info
This report describes only the local database on that device. It includes the Vault-relative path, document and chunk identifiers, local revisions, conflicts, and local chunk availability. It does not query the remote or include file contents. Review paths and identifiers as private metadata before sharing them.
Open the command palette (`Ctrl/Cmd + P`) and run **"Show debug info"**. Copy the output and paste it into the issue.
### A problem which crosses a restart
If multiple devices are involved in the problem (e.g., sync between a phone and a desktop), please provide the debug info for each device. The device where the issue occurred is required; information from other devices is strongly recommended.
Use `Write logs into the file` under **Hatch** only when the in-memory report cannot cover the restart. Persistent logging affects performance and can record private information. Disable it after reproducing the problem, review the log before sharing it, and remove the log file when it is no longer needed.
### Report from LiveSync (hatch report)
### A connection, authentication, or CORS problem
1. Open LiveSync settings.
2. Go to the **Hatch** pane.
3. Press the **Make report** button.
Include network evidence only when the ordinary LiveSync log cannot show the rejected response. Follow [Inspect a network failure](troubleshooting.md#inspect-a-network-failure), and remove request paths, remote addresses, authority and authorisation values, cookies, credentials, payload identifiers, and response secrets before sharing screenshots or copied data.
The report will be copied to your clipboard. It contains your LiveSync configuration and the remote server configuration, with credentials automatically redacted.
## Sharing the report
**Tip:** For large reports, consider uploading to [GitHub Gist](https://gist.github.com/) and sharing the link instead of pasting directly into the issue. This makes it easier to manage, and if you accidentally leave sensitive data in, a Gist can be deleted.
Paste reports into the matching collapsible sections in the issue template, or provide a link to an already-redacted Gist. A separate plug-in log is normally unnecessary because the full LiveSync report already contains the recent verbose log history.
If you paste directly, wrap it in a `<details>` tag to keep the issue readable:
```
<details>
<summary>Report from hatch</summary>
```
----remote config----
:
```
</details>
```
### Plug-in log
The plug-in log is volatile by default (not saved to disk) and shown only in the log dialogue, which can be opened by tapping the **document box icon** in the ribbon.
#### Enable verbose log
Before reproducing the issue, enable **Verbose Log** in LiveSync's **General Settings** pane. Without this, many diagnostic messages will be suppressed.
#### Persist the log to a file (optional)
If you need to capture a log across a restart, enable **"Write logs into the file"** in General Settings. Note that log files may contain sensitive information — use this option only for troubleshooting, and disable it afterwards.
As with the hatch report, consider uploading large logs to [GitHub Gist](https://gist.github.com/).
### Network log (for connection-related issues only)
If the issue is related to network connectivity (e.g., cannot connect to the server, authentication errors), a network log captured from browser DevTools can be very helpful. You do not need to include this for non-connection issues.
#### Opening DevTools
| Platform | Shortcut |
|----------|----------|
| Windows / Linux | `Ctrl + Shift + I` |
| macOS | `Cmd + Shift + I` |
| Android | Use [Chrome remote debugging](https://developer.chrome.com/docs/devtools/remote-debugging/) |
| iOS | Use [Safari Web Inspector](https://developer.apple.com/documentation/safari-developer-tools/inspecting-ios) on a Mac |
#### What to capture
1. Open the **Network** pane in DevTools.
2. Reproduce the issue.
3. Look for requests marked in red.
4. Capture screenshots of the **Headers**, **Payload**, and **Response** tabs for those requests.
**Important — redact before sharing:**
- Headers: conceal the request URL path, Remote Address, `authority`, and `authorisation` values.
- Payload / Response: the `_id` field contains your file paths — redact if needed.
If a maintainer asks for a more specialised diagnostic, collect only that additional evidence and review it again before publishing it.
+22 -4
View File
@@ -47,6 +47,14 @@ Do not switch to P2P or reset the database as the first response. Check:
If the remote is healthy but one device's local database is not, use [Reset Synchronisation on This Device](recovery.md#reset-synchronisation-on-this-device) only after backing up unsynchronised local files.
## Synchronisation is paused for compatibility review
A compatibility review is separate from the Change Log. It can appear after an internal database or settings-format change, or when a configured Vault is copied, restored, or opened in a new Obsidian profile without its device-local acknowledgement.
The **Synchronisation paused for compatibility review** dialogue opens after the Obsidian layout is ready. If it has been closed, use the persistent Notice's **Review why** link, or run `Review why synchronisation is paused` from the command palette. Opening **Change Log** does not clear the pause.
Review the stated reason before continuing. When **Resume synchronisation** is available, first update every synchronising device, then use that action to record the current internal database version and restore the configured synchronisation behaviour. If the action is unavailable, the running installation is older than the recorded database or settings format. Update that installation instead of resetting the database merely to remove the warning.
## Files are missing or excluded
Check Obsidian's `Detect all file extensions`, LiveSync selectors, ignore files, file-size limits, modification-time limits, and Hidden File Sync rules. A filtered file is different from a file which reached the database but could not be reconstructed from its chunks.
@@ -58,14 +66,22 @@ If the log reports missing chunks or a size mismatch:
3. synchronise a device or restore a backup which still has the correct content;
4. on that healthy device, run `Recreate chunks for current Vault files`, then synchronise;
5. follow [Recover a conflicted or mismatched file](recovery.md#recover-a-conflicted-or-mismatched-file); run `Inspect conflicts and file/database differences` from `Hatch`, then use each revision's wrench menu to review and act on that exact branch; and
6. use `Discard this branch` only after confirming that the exact live branch is no longer wanted. Use the separate `Discard unreadable revision` recovery action only when an unreadable revision is the sole live leaf.
6. use `Discard this branch` only after confirming that the exact current branch is no longer wanted. Use the separate `Discard unreadable revision` recovery action only when an unreadable revision is the sole current leaf.
The repair card uses compact diagnostic rows which remain readable in a narrow mobile settings pane. `🧩 Missing chunks: N` marks an unreadable revision. In the database row, `Δsize` means decoded size minus recorded size; `Δsize vs DB` means Vault size minus decoded database size; and `Δtime` means Vault modification time minus database modification time. These are diagnostic values, not a rule for deciding which revision is correct. `✅ Vault matches winner · ⚠️ Conflicts: N` means that the current Vault bytes agree with the database winner while other live branches still need a decision. Every mutating action rechecks that its selected revision is still live. Applying a logical deletion to an existing Vault file requires confirmation; a logical-deletion winner with no Vault file already agrees and is omitted.
The repair card uses compact diagnostic rows which remain readable in a narrow mobile settings pane. `🧩 Missing chunks: N` marks an unreadable revision. In the database row, `Δsize` means decoded size minus recorded size; `Δsize vs DB` means Vault size minus decoded database size; and `Δtime` means Vault modification time minus database modification time. These are diagnostic values, not a rule for deciding which revision is correct. `✅ Vault matches winner · ⚠️ Conflicts: N` means that the current Vault bytes agree with the database winner while other conflict branches still need a decision. Every mutating action rechecks that its selected revision is still a current leaf. Applying a logical deletion to an existing Vault file requires confirmation; a logical-deletion winner with no Vault file already agrees and is omitted.
`Retry reading revision` does not change the revision tree. `Discard this branch` creates a logical deletion on one exact live revision while another live branch remains and leaves the current Vault file unchanged. If the discarded revision was recorded as the Vault's exact source, that stale device-local provenance is removed. `Discard unreadable revision` provides the corresponding explicit escape hatch for a sole unreadable live leaf. Neither action purges history or reconstructs missing content. An unavailable non-live ancestor cannot be deleted through this workflow; it disables conservative three-way merge but does not prevent explicit selection between readable live revisions.
`Retry reading revision` does not change the revision tree. `Discard this branch` creates a logical deletion on one exact current leaf while another current leaf remains and leaves the current Vault file unchanged. If the discarded revision was recorded as the Vault's exact source, that stale device-local provenance is removed. `Discard unreadable revision` provides the corresponding explicit escape hatch for a sole unreadable current leaf. Neither action purges history or reconstructs missing content. An unavailable ancestor which is not a current leaf cannot be deleted through this workflow; it disables conservative three-way merge but does not prevent explicit selection between readable current leaves.
`Recreate chunks for current Vault files` uses current Vault content. It cannot recreate unique bytes which exist only in an unreadable historical or conflict revision.
## A Metadata entry requires review
When **Inspect conflicts and file/database differences** reports `Metadata entry requires review and was left unchanged`, the local database contains Metadata whose stored document ID does not agree with the ID derived from its recorded path. LiveSync withholds that entry from ordinary file reflection and deletion rather than guessing which identity is intended. The inspection is local and does not query the remote.
Do not change file-name case handling or path obfuscation merely to make the displayed IDs agree. Follow [Repair a Metadata document ID mismatch](recovery.md#repair-a-metadata-document-id-mismatch) when the card offers **Repair this Metadata document ID**. If no repair action is offered, the entry is ambiguous, conflicted, deleted, outside the normal-file namespace, or otherwise unsafe for one-entry repair. Preserve the evidence and use the wider recovery guidance instead of forcing a target ID.
If many entries reflect deliberate folder-name or ID-derivation differences across devices, choose an authoritative Vault and use the established Rebuild workflow. A one-entry repair is not a distributed rename or database migration.
## A configuration mismatch dialogue blocks synchronisation
Some settings must match across devices. LiveSync pauses synchronisation when the local and remote values differ rather than propagating an unexpected change silently.
@@ -117,6 +133,8 @@ Enable Obsidian's `Detect all file extensions`, then check LiveSync selectors, i
## Collect a report
Follow [How to report an issue](to_issue_reporting.md) for the complete reporting checklist, including Obsidian debug information and the privacy review required before sharing evidence.
Run `Generate full report for opening the issue with debug info` to copy the current settings summary and recent verbose log lines. Remove credentials, remote URLs, Vault names, file contents, and other private information before sharing it.
When a problem concerns one file, run **Copy database information for the active file**, or use **Hatch****Copy database information for a file** to select another file. The report describes this device's local database view, including the Vault-relative path, document and chunk identifiers, local database revisions, conflicts, and local chunk availability. It does not query the remote server or include file contents. Treat paths and identifiers as private metadata before sharing.
@@ -131,7 +149,7 @@ Browser security errors, particularly CORS failures, may reach the plug-in only
LiveSync stores file metadata, chunks, revision history, conflicts, deletions, and tombstones. Deleting or shortening a file therefore does not immediately remove every object which once represented it.
Garbage Collection V3 can remove unreferenced chunks from a healthy CouchDB setup, but it is appropriate only when the Vault and local database are healthy and all relevant devices have synchronised. Current files and live conflict branches protect their required chunks; an ordinary superseded revision does not. Tombstones and retained metadata are not free, so Garbage Collection does not guarantee a minimal database. Review the [Garbage Collection V3 specification](specs_garbage_collection.md) before using it.
Garbage Collection V3 can remove unreferenced chunks from a healthy CouchDB setup, but it is appropriate only when the Vault and local database are healthy and all relevant devices have synchronised. Current files and conflict branches protect their required chunks; an ordinary superseded revision does not. Tombstones and retained metadata are not free, so Garbage Collection does not guarantee a minimal database. Review the [Garbage Collection V3 specification](specs_garbage_collection.md) before using it.
`Overwrite Server Data with This Device's Files` is a separate rebuild operation and is the more certain way to reconstruct a central remote from a chosen authoritative Vault. It is also destructive and may discard changes which exist only on another device. Review [Recovery and flag files](recovery.md#garbage-collection-is-not-rebuild) before choosing between them.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 19 KiB

+1 -1
View File
@@ -1,7 +1,7 @@
{
"id": "obsidian-livesync",
"name": "Self-hosted LiveSync",
"version": "1.0.3",
"version": "1.0.18",
"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",
+237 -348
View File
File diff suppressed because it is too large Load Diff
+5 -4
View File
@@ -1,6 +1,6 @@
{
"name": "obsidian-livesync",
"version": "1.0.3",
"version": "1.0.18",
"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",
@@ -59,6 +59,7 @@
"test:e2e:obsidian:conflict-dialog-policy": "tsx test/e2e-obsidian/scripts/conflict-dialog-policy.ts",
"test:e2e:obsidian:revision-repair": "tsx test/e2e-obsidian/scripts/revision-repair.ts",
"test:e2e:obsidian:document-history-nav": "tsx test/e2e-obsidian/scripts/document-history-nav.ts",
"test:e2e:obsidian:document-history-restore": "tsx test/e2e-obsidian/scripts/document-history-restore.ts",
"test:e2e:obsidian:settings-ui": "tsx test/e2e-obsidian/scripts/settings-ui.ts",
"test:e2e:obsidian:review-harness": "tsx test/e2e-obsidian/scripts/review-harness.ts",
"test:e2e:obsidian:p2p-pane": "tsx test/e2e-obsidian/scripts/p2p-pane.ts",
@@ -177,8 +178,8 @@
"@smithy/types": "^4.14.3",
"@smithy/util-retry": "^4.4.5",
"@vrtmrz/browser-ui-kit": "0.1.0",
"@vrtmrz/livesync-commonlib": "0.1.2",
"@vrtmrz/obsidian-plugin-kit": "0.1.3",
"@vrtmrz/livesync-commonlib": "0.1.19",
"@vrtmrz/obsidian-plugin-kit": "0.1.4",
"@vrtmrz/ui-interactions": "0.1.2",
"diff-match-patch": "^1.0.5",
"fflate": "^0.8.2",
@@ -186,7 +187,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
View File
@@ -67,6 +67,10 @@ livesync-cli [database-path] [command] [args...]
- `--vault <path>` / `-V <path>`: (daemon/mirror only) Path to the vault directory containing `.md` files.
- Allows the PouchDB database directory and the actual vault directory to be different locations.
- For `mirror` command, the positional `[vault-path]` argument takes precedence over `--vault`.
- `--write-settings`: Write setting migrations and other lasting changes after the command succeeds.
- `init-settings` writes its target file. `setup`, `remote-add`, `remote-rm`, `remote-set`, and `remote-activate` write their settings changes without this option.
- All remaining commands leave the settings file unchanged by default.
- Temporary values used to suspend synchronisation or select a remote for one command are never written.
### Commands
@@ -333,6 +337,7 @@ Options:
--debug, -d Enable debug logging (includes verbose)
--interval <N>, -i <N> (daemon only) Poll CouchDB every N seconds instead of using the _changes feed
--vault <path>, -V <path> (daemon/mirror) Path to vault directory, decoupled from database-path
--write-settings Write setting changes after a successful command
--help, -h Show this help message
Commands:
+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 () => {
+1
View File
@@ -40,6 +40,7 @@ export interface CLIOptions {
verbose?: boolean;
debug?: boolean;
force?: boolean;
writeSettings?: boolean;
command: CLICommand;
commandArgs: string[];
interval?: number;
+116 -29
View File
@@ -25,10 +25,20 @@ import { stripAllPrefixes } from "@vrtmrz/livesync-commonlib/compat/string_and_b
import { IgnoreRules } from "./serviceModules/IgnoreRules";
import { useP2PReplicatorFeature } from "@vrtmrz/livesync-commonlib/compat/replication/trystero/useP2PReplicatorFeature";
import type { UseP2PReplicatorResult } from "@vrtmrz/livesync-commonlib/compat/replication/trystero/UseP2PReplicatorResult";
import { createNodeStandardIo, fsPromises as fs, path, fs as fsSync } from "@vrtmrz/livesync-commonlib/node";
import { createNodeStandardIo, fsPromises as fs, path } from "@vrtmrz/livesync-commonlib/node";
import type { StandardIo } from "@vrtmrz/livesync-commonlib/context";
import { writeStderrLine, writeStdoutLine } from "./cliOutput";
import { createDefaultCliSettings } from "./cliSettingsDefaults";
import {
applyStoredSetting,
changedSettingKeys,
CLI_RUNTIME_ONLY_SETTING_KEYS,
cloneSettings,
isSettingsWriteCommand,
preserveStoredSetting,
reconcileDurableSettings,
settingsEqual,
} from "./settingsPersistence";
const SETTINGS_FILE = ".livesync/settings.json";
ensureGlobalNodeLocalStorage();
@@ -92,6 +102,7 @@ Options:
--vault <path>, -V <path> (daemon/mirror) Path to the vault directory containing .md files
(defaults to database-path; allows separate PouchDB and vault dirs)
--interval <N>, -i <N> (daemon only) Poll CouchDB every N seconds instead of using the _changes feed
--write-settings Write setting changes after a successful command
Examples:
livesync-cli ./my-database Run daemon (LiveSync mode)
@@ -141,6 +152,7 @@ export function parseArgs(standardIo: StandardIo = createNodeStandardIo()): CLIO
let verbose = false;
let debug = false;
let force = false;
let writeSettings = false;
let interval: number | undefined;
let command: CLICommand = "daemon";
const commandArgs: string[] = [];
@@ -197,6 +209,9 @@ export function parseArgs(standardIo: StandardIo = createNodeStandardIo()): CLIO
case "-f":
force = true;
break;
case "--write-settings":
writeSettings = true;
break;
default: {
if (!databasePath) {
if (command === "daemon" && isCLICommand(token)) {
@@ -237,6 +252,7 @@ export function parseArgs(standardIo: StandardIo = createNodeStandardIo()): CLIO
verbose,
debug,
force,
writeSettings,
command,
commandArgs,
interval,
@@ -411,16 +427,26 @@ export async function main(
// Setup settings handlers
const settingService = serviceHubInstance.setting;
const originalSettingsText = await fs.readFile(settingsPath, "utf-8").catch(() => undefined);
let latestPreparedSettingsText: string | undefined;
let preparedSettingsRevision = 0;
let commandIsRunning = false;
let commandPreparedSettingsTexts: string[] = [];
(settingService as InjectableSettingService<NodeServiceContext>).saveData.setHandler(
async (data: ObsidianLiveSyncSettings) => {
try {
await fs.writeFile(settingsPath, JSON.stringify(data, null, 2), "utf-8");
latestPreparedSettingsText = JSON.stringify(data, null, 2);
preparedSettingsRevision++;
if (commandIsRunning) {
commandPreparedSettingsTexts.push(latestPreparedSettingsText);
}
if (options.verbose) {
writeStderrLine(standardIo, `[Settings] Saved to ${settingsPath}`);
writeStderrLine(standardIo, `[Settings] Prepared an update for ${settingsPath}`);
}
} catch (error) {
writeStderrLine(standardIo, `[Settings] Failed to save:`, error);
writeStderrLine(standardIo, `[Settings] Failed to prepare an update:`, error);
throw error;
}
}
);
@@ -503,24 +529,21 @@ export async function main(
process.on("SIGINT", () => void shutdown("SIGINT"));
process.on("SIGTERM", () => void shutdown("SIGTERM"));
// Save the settings file before any lifecycle events can mutate and persist them.
// suspendAllSync and other lifecycle hooks clobber sync settings in memory, and
// various code paths persist the clobbered state to disk. We restore on shutdown.
const settingsBackup = await fs.readFile(settingsPath, "utf-8").catch(() => null!);
// Restore settings file on any exit to undo lifecycle mutations.
// Write to a temp path first so a crash mid-write doesn't leave a truncated file.
process.on("exit", () => {
if (settingsBackup) {
const tmpPath = settingsPath + ".tmp";
try {
fsSync.writeFileSync(tmpPath, settingsBackup, "utf-8");
fsSync.renameSync(tmpPath, settingsPath);
} catch (err) {
writeStderrLine(standardIo, "[Settings] Failed to restore settings on exit:", err);
const writeSettingsAtomically = async (content: string | undefined): Promise<void> => {
if (content === undefined || content === originalSettingsText) return;
const temporaryPath = `${settingsPath}.${process.pid}.tmp`;
await fs.mkdir(path.dirname(settingsPath), { recursive: true });
try {
await fs.writeFile(temporaryPath, content, "utf-8");
await fs.rename(temporaryPath, settingsPath);
if (options.verbose) {
writeStderrLine(standardIo, `[Settings] Saved to ${settingsPath}`);
}
} catch (error) {
await fs.unlink(temporaryPath).catch(() => {});
throw error;
}
});
};
// Start the core
try {
@@ -531,9 +554,15 @@ export async function main(
writeStderrLine(standardIo, `[Error] Failed to initialize LiveSync`);
process.exit(1);
}
const settingsAfterLoadText = latestPreparedSettingsText
? preserveStoredSetting(latestPreparedSettingsText, originalSettingsText, "useIndexedDBAdapter")
: originalSettingsText;
// Capture sync settings before suspendAllSync() clobbers them.
// Used by daemon mode to restore the correct sync behaviour after the mirror scan.
const settingsBeforeSuspend = core.services.setting.currentSettings();
const settingsBeforeSuspend = cloneSettings(core.services.setting.currentSettings());
const durableSettingsBeforeSuspend = cloneSettings(settingsBeforeSuspend);
applyStoredSetting(durableSettingsBeforeSuspend, settingsAfterLoadText, "useIndexedDBAdapter");
const originalSyncSettings = {
liveSync: settingsBeforeSuspend.liveSync,
syncOnStart: settingsBeforeSuspend.syncOnStart,
@@ -544,7 +573,19 @@ export async function main(
syncAfterMerge: settingsBeforeSuspend.syncAfterMerge,
};
await core.services.setting.suspendAllSync();
const settingsAfterSuspend = cloneSettings(core.services.setting.currentSettings());
await core.services.control.onReady();
const settingsBeforeCommand = cloneSettings(core.services.setting.currentSettings());
const transientSettingKeys = changedSettingKeys(settingsBeforeSuspend, settingsAfterSuspend);
for (const key of CLI_RUNTIME_ONLY_SETTING_KEYS) {
transientSettingKeys.add(key);
}
const durableSettingsBeforeCommand = reconcileDurableSettings({
durableBase: durableSettingsBeforeSuspend,
runtimeBaseline: settingsAfterSuspend,
runtimeCurrent: settingsBeforeCommand,
preserveKeys: transientSettingKeys,
});
infoLog(`[Ready] LiveSync is running`);
infoLog(`[Ready] Press Ctrl+C to stop`);
@@ -568,14 +609,58 @@ export async function main(
infoLog("");
}
const result = await commandRunner(options, {
databasePath,
vaultPath,
core,
p2pReplicator,
settingsPath,
originalSyncSettings,
});
commandPreparedSettingsTexts = [];
let result: boolean;
try {
commandIsRunning = true;
result = await commandRunner(options, {
databasePath,
vaultPath,
core,
p2pReplicator,
settingsPath,
originalSyncSettings,
});
} finally {
commandIsRunning = false;
}
let settingsTextToCommit: string | undefined;
if (result && options.command === "setup") {
settingsTextToCommit = commandPreparedSettingsTexts[0];
if (settingsTextToCommit === undefined) {
throw new Error("The setup command completed without preparing its settings update.");
}
} else if (result && (isSettingsWriteCommand(options.command) || options.writeSettings)) {
const runtimeSettingsAfterCommand = cloneSettings(core.services.setting.currentSettings());
const durableSettingsAfterCommand = reconcileDurableSettings({
durableBase: durableSettingsBeforeCommand,
runtimeBaseline: settingsBeforeCommand,
runtimeCurrent: runtimeSettingsAfterCommand,
preserveKeys: transientSettingKeys,
command: options.command,
});
if (
isSettingsWriteCommand(options.command) ||
!settingsEqual(durableSettingsAfterCommand, durableSettingsBeforeSuspend)
) {
const runtimeSettings = cloneSettings(core.services.setting.currentSettings());
const revisionBeforeSave = preparedSettingsRevision;
try {
await core.services.setting.updateSettings(() => cloneSettings(durableSettingsAfterCommand), true);
if (preparedSettingsRevision === revisionBeforeSave || latestPreparedSettingsText === undefined) {
throw new Error("The setting service did not prepare the requested settings update.");
}
settingsTextToCommit = latestPreparedSettingsText;
} finally {
await core.services.setting.updateSettings(() => runtimeSettings, false);
}
} else {
settingsTextToCommit = settingsAfterLoadText;
}
}
if (!result) {
writeStderrLine(standardIo, `[Error] Command '${options.command}' failed`);
process.exitCode = 1;
@@ -584,10 +669,12 @@ export async function main(
}
if (options.command === "daemon" && result) {
await writeSettingsAtomically(settingsTextToCommit);
// Keep the process running
await new Promise(() => {});
} else {
await core.services.control.onUnload();
await writeSettingsAtomically(settingsTextToCommit);
}
} catch (error) {
writeStderrLine(standardIo, `[Error] Failed to start:`, error);
+9
View File
@@ -206,4 +206,13 @@ describe("CLI parseArgs", () => {
expect(parsed.command).toBe("daemon");
expect(parsed.interval).toBe(30);
});
it("parses --write-settings as a global option", () => {
process.argv = ["node", "livesync-cli", "./vault", "--write-settings", "ls"];
const parsed = parseArgs();
expect(parsed.command).toBe("ls");
expect(parsed.writeSettings).toBe(true);
expect(parsed.commandArgs).toEqual([]);
});
});
@@ -121,8 +121,10 @@ class CLIWatchAdapter implements IStorageEventWatchAdapter {
return {
path: path.relative(this.basePath, filePath).replace(/\\/g, "/") as FilePath,
stat: {
ctime: stats?.ctimeMs ?? Date.now(),
mtime: stats?.mtimeMs ?? Date.now(),
// Floor to integer milliseconds; Linux fs.Stats.*Ms carry sub-millisecond
// precision, and timestamps are stored as integer ms everywhere else.
ctime: Math.floor(stats?.ctimeMs ?? Date.now()),
mtime: Math.floor(stats?.mtimeMs ?? Date.now()),
size: stats?.size ?? 0,
type: "file",
},
@@ -84,6 +84,27 @@ describe("CLIStorageEventManagerAdapter", () => {
expect(created.stat?.size).toBe(42);
});
it("floors sub-millisecond stat timestamps so mobile clients do not receive floats", async () => {
const basePath = "/vault/base";
const adapter = new CLIStorageEventManagerAdapter(basePath, undefined, true);
const handlers = makeHandlers();
await adapter.watch.beginWatch(handlers);
const addCallback = mockWatcher.on.mock.calls.find(([event]) => event === "add")![1] as (
filePath: string,
stats: any
) => void;
// Linux fs.Stats carry nanosecond-derived sub-millisecond precision.
const floatStats = { ctimeMs: 1778511180024.462, mtimeMs: 1778511180999.913, size: 7 };
addCallback(`${basePath}/note.md`, floatStats);
const created = (handlers.onCreate as ReturnType<typeof vi.fn>).mock.calls[0][0] as NodeFile;
expect(created.stat?.ctime).toBe(1778511180024);
expect(created.stat?.mtime).toBe(1778511180999);
});
it("close() calls watcher.close()", async () => {
const adapter = new CLIStorageEventManagerAdapter("/base", undefined, true);
const handlers = makeHandlers();
+4 -4
View File
@@ -1,7 +1,7 @@
{
"name": "self-hosted-livesync-cli",
"private": true,
"version": "1.0.3-cli",
"version": "1.0.18-cli",
"main": "dist/index.cjs",
"type": "module",
"scripts": {
@@ -12,7 +12,7 @@
"buildRun": "npm run build && npm run cli --",
"build:docker": "docker build -f Dockerfile -t livesync-cli ../../..",
"check": "tsc -p tsconfig.json",
"test:unit": "cd ../../.. && npx vitest run --config vitest.config.unit.ts src/apps/cli/main.unit.spec.ts src/apps/cli/commands/utils.unit.spec.ts src/apps/cli/commands/runCommand.unit.spec.ts src/apps/cli/commands/p2p.unit.spec.ts",
"test:unit": "cd ../../.. && npx vitest run --config vitest.config.unit.ts src/apps/cli/main.unit.spec.ts src/apps/cli/settingsPersistence.unit.spec.ts src/apps/cli/commands/utils.unit.spec.ts src/apps/cli/commands/runCommand.unit.spec.ts src/apps/cli/commands/p2p.unit.spec.ts",
"test:e2e:two-vaults": "bash test/test-e2e-two-vaults-with-docker-linux.sh",
"test:e2e:two-vaults:common": "bash test/test-e2e-two-vaults-common.sh",
"test:e2e:two-vaults:matrix": "bash test/test-e2e-two-vaults-matrix.sh",
@@ -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",
@@ -48,7 +48,7 @@
"pouchdb-replication": "^9.0.0",
"pouchdb-utils": "^9.0.0",
"transform-pouch": "^2.0.0",
"werift": "^0.23.0"
"werift": "^0.24.4"
},
"devDependencies": {
"typescript": "5.9.3",
@@ -111,6 +111,7 @@ export function initialiseServiceModulesCLI(
UI: services.UI,
vault: services.vault,
fileHandler: fileHandler,
fileProcessing: services.fileProcessing,
storageAccess: storageAccess,
control: services.control,
});
+173
View File
@@ -0,0 +1,173 @@
import type { ObsidianLiveSyncSettings } from "@vrtmrz/livesync-commonlib/compat/common/types";
import { ConnectionStringParser } from "@vrtmrz/livesync-commonlib/compat/common/ConnectionString";
import { activateRemoteConfiguration } from "@vrtmrz/livesync-commonlib/remote-configurations";
import type { CLICommand } from "./commands/types";
const SETTINGS_WRITE_COMMANDS = new Set<CLICommand>([
"setup",
"remote-add",
"remote-rm",
"remote-set",
"remote-activate",
]);
const REMOTE_SETTINGS_WRITE_COMMANDS = new Set<CLICommand>([
"remote-add",
"remote-rm",
"remote-set",
"remote-activate",
]);
export const CLI_RUNTIME_ONLY_SETTING_KEYS = new Set<keyof ObsidianLiveSyncSettings>([
"disableCheckingConfigMismatch",
"suspendFileWatching",
"suspendParseReplicationResult",
]);
function cloneJsonValue<T>(value: T): T {
if (value === undefined) return value;
return JSON.parse(JSON.stringify(value)) as T;
}
export function cloneSettings(settings: ObsidianLiveSyncSettings): ObsidianLiveSyncSettings {
return cloneJsonValue(settings);
}
function settingValuesEqual(left: unknown, right: unknown): boolean {
return JSON.stringify(left) === JSON.stringify(right);
}
export function settingsEqual(left: ObsidianLiveSyncSettings, right: ObsidianLiveSyncSettings): boolean {
return settingValuesEqual(left, right);
}
function settingsKeys(...settings: ObsidianLiveSyncSettings[]): Set<keyof ObsidianLiveSyncSettings> {
return new Set(settings.flatMap((value) => Object.keys(value) as Array<keyof ObsidianLiveSyncSettings>));
}
function copySetting(
target: ObsidianLiveSyncSettings,
source: ObsidianLiveSyncSettings,
key: keyof ObsidianLiveSyncSettings
): void {
const targetRecord = target as unknown as Record<string, unknown>;
const sourceRecord = source as unknown as Record<string, unknown>;
if (Object.prototype.hasOwnProperty.call(sourceRecord, key)) {
targetRecord[key] = cloneJsonValue(sourceRecord[key]);
} else {
delete targetRecord[key];
}
}
function remoteSettingKeys(...settings: ObsidianLiveSyncSettings[]): Set<keyof ObsidianLiveSyncSettings> {
const keys = new Set<keyof ObsidianLiveSyncSettings>([
"remoteConfigurations",
"activeConfigurationId",
"P2P_ActiveRemoteConfigurationId",
"remoteType",
]);
for (const current of settings) {
for (const configuration of Object.values(current.remoteConfigurations ?? {})) {
try {
const parsed = ConnectionStringParser.parse(configuration.uri);
for (const key of Object.keys(parsed.settings) as Array<keyof ObsidianLiveSyncSettings>) {
keys.add(key);
}
} catch {
// The setting service reports invalid remote configurations when loading them.
}
}
}
return keys;
}
export function changedSettingKeys(
before: ObsidianLiveSyncSettings,
after: ObsidianLiveSyncSettings
): Set<keyof ObsidianLiveSyncSettings> {
const changed = new Set<keyof ObsidianLiveSyncSettings>();
for (const key of settingsKeys(before, after)) {
if (!settingValuesEqual(before[key], after[key])) {
changed.add(key);
}
}
return changed;
}
export function isSettingsWriteCommand(command: CLICommand): boolean {
return SETTINGS_WRITE_COMMANDS.has(command);
}
export function reconcileDurableSettings(options: {
durableBase: ObsidianLiveSyncSettings;
runtimeBaseline: ObsidianLiveSyncSettings;
runtimeCurrent: ObsidianLiveSyncSettings;
preserveKeys: ReadonlySet<keyof ObsidianLiveSyncSettings>;
command?: CLICommand;
}): ObsidianLiveSyncSettings {
const durable = cloneSettings(options.durableBase);
for (const key of settingsKeys(options.runtimeBaseline, options.runtimeCurrent)) {
if (options.preserveKeys.has(key)) continue;
if (!settingValuesEqual(options.runtimeBaseline[key], options.runtimeCurrent[key])) {
copySetting(durable, options.runtimeCurrent, key);
}
}
if (options.command) {
if (REMOTE_SETTINGS_WRITE_COMMANDS.has(options.command)) {
copySetting(durable, options.runtimeCurrent, "remoteConfigurations");
copySetting(durable, options.runtimeCurrent, "activeConfigurationId");
copySetting(durable, options.runtimeCurrent, "P2P_ActiveRemoteConfigurationId");
if (durable.activeConfigurationId) {
activateRemoteConfiguration(durable, durable.activeConfigurationId);
}
} else {
// Commands such as remote-status may activate a profile temporarily. Only
// the dedicated remote settings commands are allowed to retain that switch.
for (const key of remoteSettingKeys(options.durableBase, options.runtimeBaseline, options.runtimeCurrent)) {
copySetting(durable, options.durableBase, key);
}
}
}
return durable;
}
export function preserveStoredSetting(
candidateText: string,
originalText: string | undefined,
key: keyof ObsidianLiveSyncSettings
): string {
if (originalText === undefined) return candidateText;
try {
const candidate = JSON.parse(candidateText) as ObsidianLiveSyncSettings;
const original = JSON.parse(originalText) as ObsidianLiveSyncSettings;
if (Object.prototype.hasOwnProperty.call(original, key)) {
copySetting(candidate, original, key);
} else {
delete (candidate as unknown as Record<string, unknown>)[key];
}
return JSON.stringify(candidate, null, 2);
} catch {
return candidateText;
}
}
export function applyStoredSetting(
target: ObsidianLiveSyncSettings,
storedText: string | undefined,
key: keyof ObsidianLiveSyncSettings
): void {
if (storedText === undefined) return;
try {
const stored = JSON.parse(storedText) as ObsidianLiveSyncSettings;
if (Object.prototype.hasOwnProperty.call(stored, key)) {
copySetting(target, stored, key);
} else {
delete (target as unknown as Record<string, unknown>)[key];
}
} catch {
// The setting service owns validation and recovery of malformed files.
}
}
@@ -0,0 +1,162 @@
import { describe, expect, it } from "vitest";
import { DEFAULT_SETTINGS, type ObsidianLiveSyncSettings } from "@vrtmrz/livesync-commonlib/compat/common/types";
import { activateRemoteConfiguration } from "@vrtmrz/livesync-commonlib/remote-configurations";
import {
applyStoredSetting,
changedSettingKeys,
cloneSettings,
isSettingsWriteCommand,
preserveStoredSetting,
reconcileDurableSettings,
} from "./settingsPersistence";
function settings(overrides: Partial<ObsidianLiveSyncSettings> = {}): ObsidianLiveSyncSettings {
return Object.assign(cloneSettings(DEFAULT_SETTINGS), overrides);
}
describe("CLI settings persistence", () => {
it("identifies commands which change the settings file automatically", () => {
expect(isSettingsWriteCommand("setup")).toBe(true);
expect(isSettingsWriteCommand("remote-add")).toBe(true);
expect(isSettingsWriteCommand("remote-rm")).toBe(true);
expect(isSettingsWriteCommand("remote-set")).toBe(true);
expect(isSettingsWriteCommand("remote-activate")).toBe(true);
expect(isSettingsWriteCommand("ls")).toBe(false);
expect(isSettingsWriteCommand("remote-status")).toBe(false);
});
it("retains lasting changes without retaining CLI suspension values", () => {
const durableBase = settings({
liveSync: true,
syncOnStart: true,
periodicReplication: true,
P2P_AutoStart: true,
settingVersion: 9,
customChunkSize: 40,
});
const runtimeBaseline = settings({
...durableBase,
liveSync: false,
syncOnStart: false,
periodicReplication: false,
P2P_AutoStart: false,
});
const runtimeCurrent = settings({
...runtimeBaseline,
settingVersion: 10,
customChunkSize: 60,
});
const reconciled = reconcileDurableSettings({
durableBase,
runtimeBaseline,
runtimeCurrent,
preserveKeys: changedSettingKeys(durableBase, runtimeBaseline),
command: "ls",
});
expect(reconciled.liveSync).toBe(true);
expect(reconciled.syncOnStart).toBe(true);
expect(reconciled.periodicReplication).toBe(true);
expect(reconciled.P2P_AutoStart).toBe(true);
expect(reconciled.settingVersion).toBe(10);
expect(reconciled.customChunkSize).toBe(60);
});
it("retains a remote profile change and restores the durable sync values", () => {
const durableBase = settings({
liveSync: true,
remoteConfigurations: {},
activeConfigurationId: "",
});
const runtimeBaseline = settings({ ...durableBase, liveSync: false });
const runtimeCurrent = settings({
...runtimeBaseline,
remoteConfigurations: {
main: {
id: "main",
name: "Main",
uri: "sls+https://user:pass@example.com/?db=notes",
isEncrypted: false,
},
},
activeConfigurationId: "main",
});
activateRemoteConfiguration(runtimeCurrent, "main");
const reconciled = reconcileDurableSettings({
durableBase,
runtimeBaseline,
runtimeCurrent,
preserveKeys: changedSettingKeys(durableBase, runtimeBaseline),
command: "remote-add",
});
expect(reconciled.liveSync).toBe(true);
expect(reconciled.activeConfigurationId).toBe("main");
expect(reconciled.remoteConfigurations.main?.name).toBe("Main");
expect(reconciled.couchDB_URI).toBe("https://example.com");
expect(reconciled.couchDB_DBNAME).toBe("notes");
});
it("does not retain a remote profile selected temporarily by an operational command", () => {
const durableBase = settings({
remoteConfigurations: {
first: {
id: "first",
name: "First",
uri: "sls+https://first:pass@example.com/?db=first",
isEncrypted: false,
},
second: {
id: "second",
name: "Second",
uri: "sls+https://second:pass@example.net/?db=second",
isEncrypted: false,
},
},
activeConfigurationId: "first",
});
activateRemoteConfiguration(durableBase, "first");
const runtimeBaseline = cloneSettings(durableBase);
const runtimeCurrent = cloneSettings(durableBase);
activateRemoteConfiguration(runtimeCurrent, "second");
const reconciled = reconcileDurableSettings({
durableBase,
runtimeBaseline,
runtimeCurrent,
preserveKeys: new Set(),
command: "remote-status",
});
expect(reconciled.activeConfigurationId).toBe("first");
expect(reconciled.couchDB_URI).toBe("https://example.com");
expect(reconciled.couchDB_USER).toBe("first");
expect(reconciled.couchDB_DBNAME).toBe("first");
});
it("preserves the stored adapter choice while the CLI uses its Node.js adapter", () => {
const original = JSON.stringify({ useIndexedDBAdapter: true, settingVersion: 9 });
const prepared = JSON.stringify({ useIndexedDBAdapter: false, settingVersion: 10 });
const preserved = preserveStoredSetting(prepared, original, "useIndexedDBAdapter");
const target = settings({ useIndexedDBAdapter: false });
applyStoredSetting(target, preserved, "useIndexedDBAdapter");
expect(JSON.parse(preserved).useIndexedDBAdapter).toBe(true);
expect(target.useIndexedDBAdapter).toBe(true);
});
it("does not add the CLI adapter override to an older settings file", () => {
const original = JSON.stringify({ settingVersion: 9 });
const prepared = JSON.stringify({ useIndexedDBAdapter: false, settingVersion: 10 });
const preserved = preserveStoredSetting(prepared, original, "useIndexedDBAdapter");
const target = settings({ useIndexedDBAdapter: false });
applyStoredSetting(target, preserved, "useIndexedDBAdapter");
expect(JSON.parse(preserved)).not.toHaveProperty("useIndexedDBAdapter");
expect(target).not.toHaveProperty("useIndexedDBAdapter");
});
});
+1
View File
@@ -7,6 +7,7 @@
"test:daemon": "deno test --env-file=.test.env -A --no-check test-daemon.ts",
"test:decoupled-vault": "deno test --env-file=.test.env -A --no-check test-decoupled-vault.ts",
"test:remote-commands": "deno test --env-file=.test.env -A --no-check test-remote-commands.ts",
"test:settings-writeback": "deno test -A --no-check test-settings-writeback.ts",
"test:push-pull": "deno test --env-file=.test.env -A --no-check test-push-pull.ts",
"test:setup-put-cat": "deno test --env-file=.test.env -A --no-check test-setup-put-cat.ts",
"test:mirror": "deno test --env-file=.test.env -A --no-check test-mirror.ts",
+1
View File
@@ -1,4 +1,5 @@
const TASKS = [
"test:settings-writeback",
"test:setup-put-cat",
"test:mirror",
"test:daemon",
@@ -0,0 +1,134 @@
import { assert, assertEquals } from "@std/assert";
import { TempDir } from "./helpers/temp.ts";
import { runCli } from "./helpers/cli.ts";
import { initSettingsFile } from "./helpers/settings.ts";
async function prepareSettingsFixture(prefix: string) {
const workDir = await TempDir.create(prefix);
const settingsFile = workDir.join("settings.json");
const databaseDir = workDir.join("database");
await Deno.mkdir(databaseDir, { recursive: true });
await initSettingsFile(settingsFile);
return { workDir, settingsFile, databaseDir };
}
Deno.test("settings-changing commands persist durable settings without CLI runtime suspension", async () => {
const fixture = await prepareSettingsFixture("livesync-cli-settings-command");
await using workDir = fixture.workDir;
const { settingsFile, databaseDir } = fixture;
const settings = JSON.parse(await Deno.readTextFile(settingsFile));
settings.liveSync = true;
settings.syncOnStart = true;
settings.periodicReplication = true;
settings.P2P_Enabled = true;
settings.P2P_AutoStart = true;
settings.P2P_AutoBroadcast = true;
await Deno.writeTextFile(settingsFile, JSON.stringify(settings, null, 2));
const result = await runCli(
databaseDir,
"--settings",
settingsFile,
"remote-add",
"test-remote",
"sls+https://user:pass@example.com/database"
);
assertEquals(result.code, 0, result.combined);
const firstRemoteId = result.stdout.trim().split("\t")[0];
assert(firstRemoteId, `remote-add did not return an ID: ${result.combined}`);
let persisted = JSON.parse(await Deno.readTextFile(settingsFile));
let remotes = Object.values(persisted.remoteConfigurations ?? {}) as Array<{ name?: string }>;
assert(
remotes.some((remote) => remote.name === "test-remote"),
"remote-add did not persist the new profile"
);
assertEquals(persisted.liveSync, true);
assertEquals(persisted.syncOnStart, true);
assertEquals(persisted.periodicReplication, true);
assertEquals(persisted.P2P_Enabled, true);
assertEquals(persisted.P2P_AutoStart, true);
assertEquals(persisted.P2P_AutoBroadcast, true);
const secondAdd = await runCli(
databaseDir,
"--settings",
settingsFile,
"remote-add",
"second-remote",
"sls+https://other:secret@example.net/second"
);
assertEquals(secondAdd.code, 0, secondAdd.combined);
const secondRemoteId = secondAdd.stdout.trim().split("\t")[0];
assert(secondRemoteId, `second remote-add did not return an ID: ${secondAdd.combined}`);
const activate = await runCli(databaseDir, "--settings", settingsFile, "remote-activate", secondRemoteId);
assertEquals(activate.code, 0, activate.combined);
persisted = JSON.parse(await Deno.readTextFile(settingsFile));
assertEquals(persisted.activeConfigurationId, secondRemoteId);
const set = await runCli(
databaseDir,
"--settings",
settingsFile,
"remote-set",
secondRemoteId,
"sls+https://replacement:secret@example.org/replaced"
);
assertEquals(set.code, 0, set.combined);
const exported = await runCli(databaseDir, "--settings", settingsFile, "remote-export", secondRemoteId);
assertEquals(exported.code, 0, exported.combined);
assert(exported.stdout.includes("replacement"), "remote-set did not persist the replacement URI");
const remove = await runCli(databaseDir, "--settings", settingsFile, "remote-rm", secondRemoteId);
assertEquals(remove.code, 0, remove.combined);
persisted = JSON.parse(await Deno.readTextFile(settingsFile));
remotes = Object.values(persisted.remoteConfigurations ?? {}) as Array<{ id?: string }>;
assert(!remotes.some((remote) => remote.id === secondRemoteId), "remote-rm did not persist the removal");
assertEquals(persisted.activeConfigurationId, firstRemoteId);
});
Deno.test("ordinary commands keep the settings file unchanged by default", async () => {
const fixture = await prepareSettingsFixture("livesync-cli-settings-readonly");
await using workDir = fixture.workDir;
const { settingsFile, databaseDir } = fixture;
const settings = JSON.parse(await Deno.readTextFile(settingsFile));
settings.settingVersion = 9;
const original = JSON.stringify(settings, null, 2);
await Deno.writeTextFile(settingsFile, original);
const result = await runCli(databaseDir, "--settings", settingsFile, "ls");
assertEquals(result.code, 0, result.combined);
assertEquals(await Deno.readTextFile(settingsFile), original);
});
Deno.test("--write-settings persists durable start-up setting changes", async () => {
const fixture = await prepareSettingsFixture("livesync-cli-settings-explicit");
await using workDir = fixture.workDir;
const { settingsFile, databaseDir } = fixture;
const settings = JSON.parse(await Deno.readTextFile(settingsFile));
settings.settingVersion = 9;
delete settings.useIndexedDBAdapter;
await Deno.writeTextFile(settingsFile, JSON.stringify(settings, null, 2));
const result = await runCli(databaseDir, "--settings", settingsFile, "--write-settings", "ls");
assertEquals(result.code, 0, result.combined);
const persisted = JSON.parse(await Deno.readTextFile(settingsFile));
assertEquals(persisted.settingVersion, 10);
assert(!("useIndexedDBAdapter" in persisted), "the CLI-only adapter override was written to the settings file");
});
Deno.test("failed settings-changing commands leave the settings file unchanged", async () => {
const fixture = await prepareSettingsFixture("livesync-cli-settings-failure");
await using workDir = fixture.workDir;
const { settingsFile, databaseDir } = fixture;
const original = await Deno.readTextFile(settingsFile);
const result = await runCli(databaseDir, "--settings", settingsFile, "remote-rm", "missing-remote");
assert(result.code !== 0, "remote-rm unexpectedly succeeded");
assertEquals(await Deno.readTextFile(settingsFile), original);
});
@@ -41,6 +41,13 @@ Deno.test("CLI file operations: push / cat / ls / info / rm / resolve / cat-rev
setupResult.combined.includes("[Command] setup ->"),
`setup command did not execute expected code path\n${setupResult.combined}`
);
const persistedSetup = JSON.parse(await Deno.readTextFile(settingsFile));
assertEquals(persistedSetup.isConfigured, true, "setup did not persist the configured state");
assert(
typeof persistedSetup.encryptedCouchDBConnection === "string" &&
persistedSetup.encryptedCouchDBConnection.length > 0,
"setup did not persist the encrypted connection settings"
);
const run = (...args: string[]) => runCliOrFail(vaultDir, "--settings", settingsFile, ...args);
+2 -2
View File
@@ -1,7 +1,7 @@
{
"name": "livesync-webapp",
"private": true,
"version": "1.0.3-webapp",
"version": "1.0.18-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",
@@ -102,6 +102,7 @@ export function initialiseServiceModulesFSAPI(
UI: services.UI,
vault: services.vault,
fileHandler: fileHandler,
fileProcessing: services.fileProcessing,
storageAccess: storageAccess,
control: services.control,
});
+2 -2
View File
@@ -1,7 +1,7 @@
{
"name": "webpeer",
"private": true,
"version": "1.0.3-webpeer",
"version": "1.0.18-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",
+32
View File
@@ -0,0 +1,32 @@
type LegacyLocalDatabaseSelection = {
useIndexedDBAdapter: boolean;
};
type LegacyBulkChunkPreSendSettings = {
sendChunksBulk: boolean;
sendChunksBulkMaxSize: number;
};
/**
* Returns whether persisted settings select the legacy PouchDB IndexedDB adapter.
*
* New local databases use IDB. Existing devices must retain this operative value until their local database has
* been explicitly migrated, so compatibility code must not treat the setting as inert.
*/
export function usesLegacyIndexedDBAdapter(settings: LegacyLocalDatabaseSelection): boolean {
return settings.useIndexedDBAdapter;
}
/**
* Disables the removed automatic bulk chunk pre-send option in persisted settings.
*
* The field remains readable only so older settings and Setup URIs can be migrated to the supported behaviour.
*
* @returns `true` when the legacy setting was enabled and has been changed.
*/
export function disableLegacyBulkChunkPreSend(settings: LegacyBulkChunkPreSendSettings): boolean {
if (!settings.sendChunksBulk) return false;
settings.sendChunksBulk = false;
settings.sendChunksBulkMaxSize = 1;
return true;
}
@@ -0,0 +1,22 @@
import { describe, expect, it } from "vitest";
import { disableLegacyBulkChunkPreSend, usesLegacyIndexedDBAdapter } from "./compatibilitySettings.ts";
describe("compatibility settings", () => {
it.each([true, false])("preserves the operative legacy adapter selection (%s)", (useIndexedDBAdapter) => {
expect(usesLegacyIndexedDBAdapter({ useIndexedDBAdapter })).toBe(useIndexedDBAdapter);
});
it("disables automatic bulk chunk pre-send and restores its inert size value", () => {
const settings = { sendChunksBulk: true, sendChunksBulkMaxSize: 16 };
expect(disableLegacyBulkChunkPreSend(settings)).toBe(true);
expect(settings).toEqual({ sendChunksBulk: false, sendChunksBulkMaxSize: 1 });
});
it("leaves an already migrated bulk chunk setting unchanged", () => {
const settings = { sendChunksBulk: false, sendChunksBulkMaxSize: 4 };
expect(disableLegacyBulkChunkPreSend(settings)).toBe(false);
expect(settings).toEqual({ sendChunksBulk: false, sendChunksBulkMaxSize: 4 });
});
});
-2
View File
@@ -7,7 +7,6 @@ export const EVENT_FILE_SAVED = "file-saved";
export const EVENT_LEAF_ACTIVE_CHANGED = "leaf-active-changed";
export const EVENT_REQUEST_OPEN_SETTINGS = "request-open-settings";
export const EVENT_REQUEST_OPEN_SETTING_WIZARD = "request-open-setting-wizard";
export const EVENT_REQUEST_OPEN_SETUP_URI = "request-open-setup-uri";
export const EVENT_REQUEST_COPY_SETUP_URI = "request-copy-setup-uri";
export const EVENT_REQUEST_SHOW_SETUP_QR = "request-show-setup-qr";
@@ -30,7 +29,6 @@ declare global {
[EVENT_PLUGIN_UNLOADED]: undefined;
[EVENT_REQUEST_OPEN_PLUGIN_SYNC_DIALOG]: undefined;
[EVENT_REQUEST_OPEN_SETTINGS]: undefined;
[EVENT_REQUEST_OPEN_SETTING_WIZARD]: undefined;
[EVENT_REQUEST_RELOAD_SETTING_TAB]: undefined;
[EVENT_LEAF_ACTIVE_CHANGED]: undefined;
[EVENT_REQUEST_OPEN_SETUP_URI]: undefined;
@@ -32,6 +32,20 @@ export const liveSyncProvisionalEnglishMessages = {
"Learn more about signalling and TURN": "Learn more about signalling and TURN",
"TURN relays the encrypted WebRTC connection only when a direct path cannot be established. A TURN provider cannot read encrypted Vault contents, but it can observe connection metadata and traffic volume. Use a provider you trust.":
"TURN relays the encrypted WebRTC connection only when a direct path cannot be established. A TURN provider cannot read encrypted Vault contents, but it can observe connection metadata and traffic volume. Use a provider you trust.",
"Connection compatibility": "Connection compatibility",
"P2P message size": "P2P message size",
Standard: "Standard",
Reduced: "Reduced",
Conservative: "Conservative",
"Maximum compatibility": "Maximum compatibility",
"Smaller messages can improve compatibility on paths which fragment or drop larger WebRTC messages. This setting limits outgoing P2P messages, so use a compatible profile on each sending device when required.":
"Smaller messages can improve compatibility on paths which fragment or drop larger WebRTC messages. This setting limits outgoing P2P messages, so use a compatible profile on each sending device when required.",
"Connection path": "Connection path",
"TURN relay only": "TURN relay only",
"TURN relay only is available when at least one valid TURN server URL is configured under Advanced Settings.":
"TURN relay only is available when at least one valid TURN server URL is configured under Advanced Settings.",
"TURN relay only requires at least one valid TURN server URL. Connection path has been restored to Automatic.":
"TURN relay only requires at least one valid TURN server URL. Connection path has been restored to Automatic.",
"Announce changes": "Announce changes",
"Announce changes automatically after connecting": "Announce changes automatically after connecting",
"When enabled, this device notifies connected peers after a local change. The notification contains no Vault data; a peer which follows this device then fetches the change through the encrypted P2P connection.":
@@ -49,6 +63,18 @@ export const liveSyncProvisionalEnglishMessages = {
"Connect to existing database and continue": "Connect to existing database and continue",
"Test connection and save": "Test connection and save",
"Save without connecting": "Save without connecting",
"Use this device's settings": "Use this device's settings",
Retry: "Retry",
"No Synchronisation Settings Found": "No Synchronisation Settings Found",
"The selected remote has no saved synchronisation settings. This is normal for a new remote. Use this device's settings, or cancel if you expected existing settings.":
"The selected remote has no saved synchronisation settings. This is normal for a new remote. Use this device's settings, or cancel if you expected existing settings.",
"Could Not Read Synchronisation Settings": "Could Not Read Synchronisation Settings",
"Could not read the remote's synchronisation settings. Check the connection and credentials, then retry.":
"Could not read the remote's synchronisation settings. Check the connection and credentials, then retry.",
"Could not read the remote's synchronisation settings. Retry, or continue the overwrite with this device's settings. A working connection is still required.":
"Could not read the remote's synchronisation settings. Retry, or continue the overwrite with this device's settings. A working connection is still required.",
"Skips checking and applying synchronisation settings from the remote.":
"Skips checking and applying synchronisation settings from the remote.",
"Enter a complete HTTP or HTTPS URL.": "Enter a complete HTTP or HTTPS URL.",
"CouchDB validates the database name when you connect. The name must not be empty.":
"CouchDB validates the database name when you connect. The name must not be empty.",
@@ -138,9 +164,38 @@ export const liveSyncProvisionalEnglishMessages = {
"Resolve every conflict by modification time? This logically deletes every version except the newest one and cannot recover content which is already unavailable.",
"Resolve all conflicts by the newest version": "Resolve all conflicts by the newest version",
"Inspect conflicts and file/database differences": "Inspect conflicts and file/database differences",
"Scan every Vault file and live local-database revision for conflicts, missing chunks, and differences. Each result provides actions for the exact revision.":
"Scan every Vault file and live local-database revision for conflicts, missing chunks, and differences. Each result provides actions for the exact revision.",
"Scan Vault files and local-database Metadata for conflicts, missing chunks, identity mismatches, and differences. Each result provides actions for one exact entry or revision.":
"Scan Vault files and local-database Metadata for conflicts, missing chunks, identity mismatches, and differences. Each result provides actions for one exact entry or revision.",
"Begin inspection": "Begin inspection",
"Metadata entry requires review and was left unchanged": "Metadata entry requires review and was left unchanged",
"The stored document ID does not match the ID derived from its recorded path.":
"The stored document ID does not match the ID derived from its recorded path.",
"The stored document ID and recorded path are handled by different synchronisation features.":
"The stored document ID and recorded path are handled by different synchronisation features.",
"Stored document ID: ${ID}": "Stored document ID: ${ID}",
"Expected document ID: ${ID}": "Expected document ID: ${ID}",
"Source revision: ${REVISION}": "Source revision: ${REVISION}",
"One-step repair is unavailable because this entry is ambiguous, no longer current, or unsafe to change.":
"One-step repair is unavailable because this entry is ambiguous, no longer current, or unsafe to change.",
"An exact target is already present; repair can remove the obsolete ID.":
"An exact target is already present; repair can remove the obsolete ID.",
"Repair is available for this entry.": "Repair is available for this entry.",
"Repair this Metadata document ID": "Repair this Metadata document ID",
"Repair Metadata ID": "Repair Metadata ID",
"Keep unchanged": "Keep unchanged",
"Repair Metadata document ID": "Repair Metadata document ID",
"This moves one local Metadata entry to the ID derived from its recorded path.\n\n**File:** `${FILE}` \n**Source:** `${SOURCE}@${REVISION}` \n**Target:** `${TARGET}`\n\nThe target is verified before the source is removed. Its CouchDB revision ancestry cannot be preserved.\n\n> [!warning] Before repairing\n> - Back up this device.\n> - If file-name case or path obfuscation was intentionally changed for the whole database, use Rebuild instead.\n> - If other devices share this database, pause them, allow this device to upload the repair, then resume them one at a time.":
"This moves one local Metadata entry to the ID derived from its recorded path.\n\n**File:** `${FILE}` \n**Source:** `${SOURCE}@${REVISION}` \n**Target:** `${TARGET}`\n\nThe target is verified before the source is removed. Its CouchDB revision ancestry cannot be preserved.\n\n> [!warning] Before repairing\n> - Back up this device.\n> - If file-name case or path obfuscation was intentionally changed for the whole database, use Rebuild instead.\n> - If other devices share this database, pause them, allow this device to upload the repair, then resume them one at a time.",
"Metadata document ID repair and the ordinary Vault scan completed. Run this inspection again after synchronisation.":
"Metadata document ID repair and the ordinary Vault scan completed. Run this inspection again after synchronisation.",
"Metadata document ID repair completed, but the ordinary Vault scan did not run. Keep synchronisation paused, resolve the scan condition, then run 'Scan storage and database again'.":
"Metadata document ID repair completed, but the ordinary Vault scan did not run. Keep synchronisation paused, resolve the scan condition, then run 'Scan storage and database again'.",
"The inspected state changed. No repair was performed; run inspection again.":
"The inspected state changed. No repair was performed; run inspection again.",
"Repair stopped after creating the target. The source was retained. Run inspection again before retrying.":
"Repair stopped after creating the target. The source was retained. Run inspection again before retrying.",
"Repair failed before the source was removed. Run inspection again before retrying.":
"Repair failed before the source was removed. Run inspection again before retrying.",
"Connection settings": "Connection settings",
"Saved connections": "Saved connections",
} as const;
File diff suppressed because it is too large Load Diff
-5
View File
@@ -112,8 +112,6 @@
"Obsidian version": "Obsidian-Version",
"obsidianLiveSyncSettingTab.btnApply": "Anwenden",
"obsidianLiveSyncSettingTab.btnDisable": "Deaktivieren",
"obsidianLiveSyncSettingTab.btnNext": "Weiter",
"obsidianLiveSyncSettingTab.buttonNext": "Weiter",
"obsidianLiveSyncSettingTab.defaultLanguage": "Standardsprache",
"obsidianLiveSyncSettingTab.labelDisabled": "⏹️ : Deaktiviert",
"obsidianLiveSyncSettingTab.labelEnabled": "🔁 : Aktiviert",
@@ -124,9 +122,7 @@
"obsidianLiveSyncSettingTab.msgConfigCheckFailed": "Die Konfigurationsprüfung ist fehlgeschlagen. Trotzdem fortfahren?",
"obsidianLiveSyncSettingTab.msgEnableEncryptionRecommendation": "Wir empfehlen, Ende-zu-Ende-Verschlüsselung und Pfadverschleierung zu aktivieren. Möchten Sie wirklich ohne Verschlüsselung fortfahren?",
"obsidianLiveSyncSettingTab.msgFetchConfigFromRemote": "Möchten Sie die Konfiguration vom Remote-Server abrufen?",
"obsidianLiveSyncSettingTab.msgGenerateSetupURI": "Alles fertig! Möchten Sie eine Setup-URI erzeugen, um andere Geräte einzurichten?",
"obsidianLiveSyncSettingTab.msgInvalidPassphrase": "Ihre Verschlüsselungs-Passphrase könnte ungültig sein. Möchten Sie wirklich fortfahren?",
"obsidianLiveSyncSettingTab.msgSelectAndApplyPreset": "Bitte wählen und übernehmen Sie eine beliebige Voreinstellung, um den Assistenten abzuschließen.",
"obsidianLiveSyncSettingTab.nameDisableHiddenFileSync": "Synchronisation versteckter Dateien deaktivieren",
"obsidianLiveSyncSettingTab.nameEnableHiddenFileSync": "Synchronisation versteckter Dateien aktivieren",
"obsidianLiveSyncSettingTab.nameHiddenFileSynchronization": "Synchronisation versteckter Dateien",
@@ -137,7 +133,6 @@
"obsidianLiveSyncSettingTab.optionPeriodicWithBatch": "Periodisch mit Stapelverarbeitung",
"obsidianLiveSyncSettingTab.titleAppearance": "Darstellung",
"obsidianLiveSyncSettingTab.titleConflictResolution": "Konfliktbehandlung",
"obsidianLiveSyncSettingTab.titleCongratulations": "Glückwunsch!",
"obsidianLiveSyncSettingTab.titleCouchDB": "CouchDB-Server",
"obsidianLiveSyncSettingTab.titleDeletionPropagation": "Weitergabe von Löschungen",
"obsidianLiveSyncSettingTab.titleEncryptionNotEnabled": "Verschlüsselung ist nicht aktiviert",
+31 -18
View File
@@ -478,7 +478,6 @@
"moduleLiveSyncMain.titleScramEnabled": "Scram Enabled",
"moduleLocalDatabase.logWaitingForReady": "Waiting for ready...",
"moduleLog.showLog": "Show Log",
"moduleMigration.docUri": "https://github.com/vrtmrz/obsidian-livesync/blob/main/README.md#how-to-use",
"moduleMigration.fix0256.buttons.checkItLater": "Check it later",
"moduleMigration.fix0256.buttons.DismissForever": "I have fixed it, and do not ask again",
"moduleMigration.fix0256.buttons.fix": "Fix",
@@ -500,26 +499,16 @@
"moduleMigration.logRemoteTweakUnavailable": "Could not get remote tweak values",
"moduleMigration.logSetupCancelled": "The setup has been cancelled, Self-hosted LiveSync waiting for your setup!",
"moduleMigration.msgFetchRemoteAgain": "As you may already know, the self-hosted LiveSync has changed its default behaviour and database structure.\n\nAnd thankfully, with your time and efforts, the remote database appears to have already been migrated. Congratulations!\n\nHowever, we need a bit more. The configuration of this device is not compatible with the remote database. We will need to fetch the remote database again. Should we fetch from the remote again now?\n\n___Note: We cannot synchronise until the configuration has been changed and the database has been fetched again.___\n___Note2: The chunks are completely immutable, we can fetch only the metadata and difference.___",
"moduleMigration.msgInitialSetup": "Your device has **not been set up yet**. Let me guide you through the setup process.\n\nPlease keep in mind that every dialogue content can be copied to the clipboard. If you need to refer to it later, you can paste it into a note in Obsidian. You can also translate it into your language using a translation tool.\n\nFirst, do you have **Setup URI**?\n\nNote: If you do not know what it is, please refer to the [documentation](${URI_DOC}).",
"moduleMigration.msgRecommendSetupUri": "We strongly recommend that you generate a set-up URI and use it.\nIf you do not have knowledge about it, please refer to the [documentation](${URI_DOC}) (Sorry again, but it is important).\n\nHow do you want to set it up manually?",
"moduleMigration.msgSinceV02321": "Since v0.23.21, the self-hosted LiveSync has changed the default behaviour and database structure. The following changes have been made:\n\n1. **Case sensitivity of filenames**\n The handling of filenames is now case-insensitive. This is a beneficial change for most platforms, other than Linux and iOS, which do not manage filename case sensitivity effectively.\n (On These, a warning will be displayed for files with the same name but different cases).\n\n2. **Revision handling of the chunks**\n Chunks are immutable, which allows their revisions to be fixed. This change will enhance the performance of file saving.\n\n___However, to enable either of these changes, both remote and local databases need to be rebuilt. This process takes a few minutes, and we recommend doing it when you have ample time.___\n\n- If you wish to maintain the previous behaviour, you can skip this process by using `${KEEP}`.\n- If you do not have enough time, please choose `${DISMISS}`. You will be prompted again later.\n- If you have rebuilt the database on another device, please select `${DISMISS}` and try synchronizing again. Since a difference has been detected, you will be prompted again.",
"moduleMigration.optionAdjustRemote": "Adjust to remote",
"moduleMigration.optionDecideLater": "Decide it later",
"moduleMigration.optionEnableBoth": "Enable both",
"moduleMigration.optionEnableFilenameCaseInsensitive": "Enable only #1",
"moduleMigration.optionEnableFixedRevisionForChunks": "Enable only #2",
"moduleMigration.optionHaveSetupUri": "Yes, I have",
"moduleMigration.optionKeepPreviousBehaviour": "Keep previous behaviour",
"moduleMigration.optionManualSetup": "Set it up all manually",
"moduleMigration.optionNoAskAgain": "No, please ask again",
"moduleMigration.optionNoSetupUri": "No, I do not have",
"moduleMigration.optionRemindNextLaunch": "Remind me at the next launch",
"moduleMigration.optionSetupViaP2P": "Use %{short_p2p_sync} to set up",
"moduleMigration.optionSetupWizard": "Take me into the setup wizard",
"moduleMigration.optionYesFetchAgain": "Yes, fetch again",
"moduleMigration.titleCaseSensitivity": "Case Sensitivity",
"moduleMigration.titleRecommendSetupUri": "Recommendation to use Setup URI",
"moduleMigration.titleWelcome": "Welcome to Self-hosted LiveSync",
"moduleObsidianMenu.replicate": "Replicate",
"More actions": "More actions",
"Mostly Complete: Decision Required": "Mostly Complete: Decision Required",
@@ -564,12 +553,10 @@
"obsidianLiveSyncSettingTab.btnEnable": "Enable",
"obsidianLiveSyncSettingTab.btnFix": "Fix",
"obsidianLiveSyncSettingTab.btnGotItAndUpdated": "I got it and updated.",
"obsidianLiveSyncSettingTab.btnNext": "Next",
"obsidianLiveSyncSettingTab.btnStart": "Start",
"obsidianLiveSyncSettingTab.btnTest": "Test",
"obsidianLiveSyncSettingTab.btnUse": "Use",
"obsidianLiveSyncSettingTab.buttonFetch": "Fetch",
"obsidianLiveSyncSettingTab.buttonNext": "Next",
"obsidianLiveSyncSettingTab.defaultLanguage": "Default",
"obsidianLiveSyncSettingTab.descConnectSetupURI": "This is the recommended method to set up Self-hosted LiveSync with a Setup URI.",
"obsidianLiveSyncSettingTab.descCopySetupURI": "Perfect for setting up a new device!",
@@ -633,7 +620,6 @@
"obsidianLiveSyncSettingTab.msgEnableCorsChttpd": "Set chttpd.enable_cors",
"obsidianLiveSyncSettingTab.msgEnableEncryptionRecommendation": "We recommend enabling End-To-End Encryption, and Path Obfuscation. Are you sure you want to continue without encryption?",
"obsidianLiveSyncSettingTab.msgFetchConfigFromRemote": "Do you want to fetch the config from the remote server?",
"obsidianLiveSyncSettingTab.msgGenerateSetupURI": "All done! Do you want to generate a setup URI to set up other devices?",
"obsidianLiveSyncSettingTab.msgIfConfigNotPersistent": "If the server configuration is not persistent (e.g., running on docker), the values here may change. Once you are able to connect, please update the settings in the server's local.ini.",
"obsidianLiveSyncSettingTab.msgInvalidPassphrase": "Your encryption passphrase might be invalid. Are you sure you want to continue?",
"obsidianLiveSyncSettingTab.msgNewVersionNote": "Here due to an upgrade notification? Please review the version history. If you're satisfied, click the button. A new update will prompt this again.",
@@ -643,7 +629,6 @@
"obsidianLiveSyncSettingTab.msgObjectStorageWarning": "WARNING: This feature is a Work In Progress, so please keep in mind the following:\n- Append only architecture. A rebuild is required to shrink the storage.\n- A bit fragile.\n- When first syncing, all history will be transferred from the remote. Be mindful of data caps and slow speeds.\n- Only differences are synced live.\n\nIf you run into any issues, or have ideas about this feature, please create a issue on GitHub.\nI appreciate you for your great dedication.",
"obsidianLiveSyncSettingTab.msgOriginCheck": "Origin check: ${org}",
"obsidianLiveSyncSettingTab.msgRebuildRequired": "Rebuilding Databases are required to apply the changes.. Please select the method to apply the changes.\n\n<details>\n<summary>Legends</summary>\n\n| Symbol | Meaning |\n|: ------ :| ------- |\n| ⇔ | Up to Date |\n| ⇄ | Synchronise to balance |\n| ⇐,⇒ | Transfer to overwrite |\n| ⇠,⇢ | Transfer to overwrite from other side |\n\n</details>\n\n## ${OPTION_REBUILD_BOTH}\nAt a glance: 📄 ⇒¹ 💻 ⇒² 🛰️ ⇢ⁿ 💻 ⇄ⁿ⁺¹ 📄\nReconstruct both the local and remote databases using existing files from this device.\nThis causes a lockout other devices, and they need to perform fetching.\n## ${OPTION_FETCH}\nAt a glance: 📄 ⇄² 💻 ⇐¹ 🛰️ ⇔ 💻 ⇔ 📄\nInitialise the local database and reconstruct it using data fetched from the remote database.\nThis case includes the case which you have rebuilt the remote database.\n## ${OPTION_ONLY_SETTING}\nStore only the settings. **Caution: This may lead to data corruption**; database reconstruction is generally necessary.",
"obsidianLiveSyncSettingTab.msgSelectAndApplyPreset": "Please select and apply any preset item to complete the wizard.",
"obsidianLiveSyncSettingTab.msgSetCorsCredentials": "Set cors.credentials",
"obsidianLiveSyncSettingTab.msgSetCorsOrigins": "Set cors.origins",
"obsidianLiveSyncSettingTab.msgSetMaxDocSize": "Set couchdb.max_document_size",
@@ -698,19 +683,24 @@
"obsidianLiveSyncSettingTab.panelSetup": "Setup",
"obsidianLiveSyncSettingTab.serverVersion": "Server info: ${info}",
"obsidianLiveSyncSettingTab.titleActiveRemoteServer": "Active Remote Server",
"obsidianLiveSyncSettingTab.titleAdvancedSettings": "Advanced settings",
"obsidianLiveSyncSettingTab.titleAppearance": "Appearance",
"obsidianLiveSyncSettingTab.titleConflictResolution": "Conflict resolution",
"obsidianLiveSyncSettingTab.titleCongratulations": "Congratulations!",
"obsidianLiveSyncSettingTab.titleCouchDB": "CouchDB",
"obsidianLiveSyncSettingTab.titleDeletionPropagation": "Deletion Propagation",
"obsidianLiveSyncSettingTab.titleEncryptionNotEnabled": "Encryption is not enabled",
"obsidianLiveSyncSettingTab.titleEncryptionPassphraseInvalid": "Encryption Passphrase Invalid",
"obsidianLiveSyncSettingTab.titleExtraFeatures": "Enable extra and advanced features",
"obsidianLiveSyncSettingTab.titleExtraFeaturesGroup": "Extra features",
"obsidianLiveSyncSettingTab.titleExtraMenus": "Extra menus",
"obsidianLiveSyncSettingTab.titleFetchConfig": "Fetch Config",
"obsidianLiveSyncSettingTab.titleFetchConfigFromRemote": "Fetch config from remote server",
"obsidianLiveSyncSettingTab.titleFetchSettings": "Fetch Settings",
"obsidianLiveSyncSettingTab.titleHelpAndInformation": "Help and information",
"obsidianLiveSyncSettingTab.titleHelpAndTroubleshooting": "Help and troubleshooting",
"obsidianLiveSyncSettingTab.titleHiddenFiles": "Hidden Files",
"obsidianLiveSyncSettingTab.titleLogging": "Logging",
"obsidianLiveSyncSettingTab.titleMaintenanceAndRecovery": "Maintenance and recovery",
"obsidianLiveSyncSettingTab.titleMinioS3R2": "Minio,S3,R2",
"obsidianLiveSyncSettingTab.titleNotification": "Notification",
"obsidianLiveSyncSettingTab.titleOnlineTips": "Online Tips",
@@ -719,7 +709,8 @@
"obsidianLiveSyncSettingTab.titleRemoteConfigCheckFailed": "Remote Configuration Check Failed",
"obsidianLiveSyncSettingTab.titleRemoteServer": "Remote Server",
"obsidianLiveSyncSettingTab.titleReset": "Reset",
"obsidianLiveSyncSettingTab.titleSetupOtherDevices": "To setup other devices",
"obsidianLiveSyncSettingTab.titleSetupOtherDevices": "Set up other devices",
"obsidianLiveSyncSettingTab.titleSynchronisation": "Synchronisation",
"obsidianLiveSyncSettingTab.titleSynchronizationMethod": "Synchronization Method",
"obsidianLiveSyncSettingTab.titleSynchronizationPreset": "Synchronization Preset",
"obsidianLiveSyncSettingTab.titleSyncSettings": "Sync Settings",
@@ -878,7 +869,7 @@
"Replicator.Message.InitialiseFatalError": "No replicator is available, this is the fatal error.",
"Replicator.Message.Pending": "Some file events are pending. Replication has been cancelled.",
"Replicator.Message.SomeModuleFailed": "Replication has been cancelled by some module failure",
"Replicator.Message.VersionUpFlash": "An update has been detected. Please open the Settings dialogue and check the Change Log. Replication has been cancelled.",
"Replicator.Message.VersionUpFlash": "Remote synchronisation is paused for compatibility review. Run the 'Review why synchronisation is paused' command for details and available actions.",
"Requires restart of Obsidian": "Requires restart of Obsidian",
"Requires restart of Obsidian.": "Requires restart of Obsidian.",
"Rerun Onboarding Wizard": "Rerun Onboarding Wizard",
@@ -1330,6 +1321,28 @@
"Ui.Settings.SyncSettings.Fetch": "Fetch",
"Ui.Settings.SyncSettings.Merge": "Merge",
"Ui.Settings.SyncSettings.Overwrite": "Overwrite",
"Ui.SetupWizard.ApplySettingsInitialisation.ApplyWithoutInitialisation": "Apply without Initialisation",
"Ui.SetupWizard.ApplySettingsInitialisation.Back": "Review another way to apply these settings",
"Ui.SetupWizard.ApplySettingsInitialisation.BypassGuidance": "Applying these settings alone can make this device incompatible with its existing synchronisation data. Use this only when you have confirmed that reconstruction is unnecessary.",
"Ui.SetupWizard.ApplySettingsInitialisation.BypassTitle": "Apply Settings without Initialisation?",
"Ui.SetupWizard.ApplySettingsInitialisation.ContinueFetch": "Continue with Fetch",
"Ui.SetupWizard.ApplySettingsInitialisation.FetchOption": "Reset Synchronisation on This Device",
"Ui.SetupWizard.ApplySettingsInitialisation.FetchOptionDesc": "After restarting, rebuild this device's local database from the current remote synchronisation data. Files in the Vault will then be reconciled with that data.",
"Ui.SetupWizard.ApplySettingsInitialisation.FetchOptionP2PDesc": "After restarting, select an online source device. This device's local LiveSync database will be rebuilt from that source.",
"Ui.SetupWizard.ApplySettingsInitialisation.Guidance": "These setting changes alter how synchronisation data is interpreted. Apply them together with an initialisation operation after restart.",
"Ui.SetupWizard.ApplySettingsInitialisation.KeepEditing": "Keep Editing",
"Ui.SetupWizard.ApplySettingsInitialisation.ProceedFetch": "Restart and Fetch Synchronisation Data",
"Ui.SetupWizard.ApplySettingsInitialisation.ProceedFetchP2P": "Restart and Select a Source Device",
"Ui.SetupWizard.ApplySettingsInitialisation.ProceedRebuild": "Restart and Overwrite Server Data",
"Ui.SetupWizard.ApplySettingsInitialisation.ProceedRebuildP2P": "Restart and Prepare This Device",
"Ui.SetupWizard.ApplySettingsInitialisation.Question": "Which existing data should be used after restart?",
"Ui.SetupWizard.ApplySettingsInitialisation.RebuildOption": "Overwrite Server Data with This Device's Files",
"Ui.SetupWizard.ApplySettingsInitialisation.RebuildOptionDesc": "Rebuild the local and remote databases from the files currently in this Vault. Other synchronising devices must reset their local synchronisation afterwards.",
"Ui.SetupWizard.ApplySettingsInitialisation.RebuildOptionP2P": "Prepare This Device from This Vault",
"Ui.SetupWizard.ApplySettingsInitialisation.RebuildOptionP2PDesc": "Rebuild this device's local LiveSync database from the files currently in this Vault. This does not overwrite another device.",
"Ui.SetupWizard.ApplySettingsInitialisation.RemoteVerificationGuidance": "The configured remote could not be verified with the current credentials and encryption settings. Continuing may make the Fetch fail after restart.",
"Ui.SetupWizard.ApplySettingsInitialisation.RemoteVerificationTitle": "Remote Synchronisation Data Could Not Be Verified",
"Ui.SetupWizard.ApplySettingsInitialisation.Title": "Apply Settings and Reinitialise Synchronisation",
"Ui.SetupWizard.Common.Back": "No, please take me back",
"Ui.SetupWizard.Common.Cancel": "Cancel",
"Ui.SetupWizard.Common.ProceedSelectOption": "Please select an option to proceed",
-16
View File
@@ -479,7 +479,6 @@
"moduleLiveSyncMain.titleScramEnabled": "Scram habilitado",
"moduleLocalDatabase.logWaitingForReady": "Esperando a que la base de datos esté lista...",
"moduleLog.showLog": "Mostrar registro",
"moduleMigration.docUri": "https://github.com/vrtmrz/obsidian-livesync/blob/main/README_ES.md#how-to-use",
"moduleMigration.fix0256.buttons.checkItLater": "Comprobarlo más tarde",
"moduleMigration.fix0256.buttons.DismissForever": "Ya lo he corregido, no volver a preguntar",
"moduleMigration.fix0256.buttons.fix": "Corregir",
@@ -501,26 +500,16 @@
"moduleMigration.logRemoteTweakUnavailable": "No se pudieron obtener los valores de ajuste remoto",
"moduleMigration.logSetupCancelled": "La configuración ha sido cancelada, ¡Self-hosted LiveSync está esperando tu configuración!",
"moduleMigration.msgFetchRemoteAgain": "Como ya sabrás, Self-hosted LiveSync ha cambiado su comportamiento predeterminado y la estructura de la base de datos.\n\nAfortunadamente, con tu tiempo y esfuerzo, la base de datos remota parece haber sido ya migrada. ¡Felicidades!\n\nSin embargo, necesitamos un poco más. La configuración de este dispositivo no es compatible con la base de datos remota. Necesitaremos volver a obtener la base de datos remota. ¿Debemos obtenerla nuevamente ahora?\n\n___Nota: No podemos sincronizar hasta que la configuración haya sido cambiada y la base de datos haya sido obtenida nuevamente.___\n___Nota2: Los fragmentos son completamente inmutables, solo podemos obtener los metadatos y diferencias.___",
"moduleMigration.msgInitialSetup": "Tu dispositivo **aún no ha sido configurado**. Permíteme guiarte a través del proceso de configuración.\n\nTen en cuenta que todo el contenido del diálogo se puede copiar al portapapeles. Si necesitas consultarlo más tarde, puedes pegarlo en una nota en Obsidian. También puedes traducirlo a tu idioma utilizando una herramienta de traducción.\n\nPrimero, ¿tienes **URI de configuración**?\n\nNota: Si no sabes qué es, consulta la [documentación](${URI_DOC}).",
"moduleMigration.msgRecommendSetupUri": "Te recomendamos encarecidamente que generes una URI de configuración y la utilices.\nSi no tienes conocimientos al respecto, consulta la [documentación](${URI_DOC}) (Lo siento de nuevo, pero es importante).\n\n¿Cómo quieres configurarlo manualmente?",
"moduleMigration.msgSinceV02321": "Desde la versión v0.23.21, Self-hosted LiveSync ha cambiado el comportamiento predeterminado y la estructura de la base de datos. Se han realizado los siguientes cambios:\n\n1. **Sensibilidad a mayúsculas de los nombres de archivo**\n El manejo de los nombres de archivo ahora no distingue entre mayúsculas y minúsculas. Este cambio es beneficioso para la mayoría de las plataformas, excepto Linux y iOS, que no gestionan efectivamente la sensibilidad a mayúsculas de los nombres de archivo.\n (En estos, se mostrará una advertencia para archivos con el mismo nombre pero diferentes mayúsculas).\n\n2. **Manejo de revisiones de los fragmentos**\n Los fragmentos son inmutables, lo que permite que sus revisiones sean fijas. Este cambio mejorará el rendimiento al guardar archivos.\n\n___Sin embargo, para habilitar cualquiera de estos cambios, es necesario reconstruir tanto las bases de datos remota como la local. Este proceso toma unos minutos, y recomendamos hacerlo cuando tengas tiempo suficiente.___\n\n- Si deseas mantener el comportamiento anterior, puedes omitir este proceso usando `${KEEP}`.\n- Si no tienes suficiente tiempo, por favor elige `${DISMISS}`. Se te pedirá nuevamente más tarde.\n- Si has reconstruido la base de datos en otro dispositivo, selecciona `${DISMISS}` e intenta sincronizar nuevamente. Dado que se ha detectado una diferencia, se te solicitará nuevamente.",
"moduleMigration.optionAdjustRemote": "Ajustar al remoto",
"moduleMigration.optionDecideLater": "Decidirlo más tarde",
"moduleMigration.optionEnableBoth": "Habilitar ambos",
"moduleMigration.optionEnableFilenameCaseInsensitive": "Habilitar solo #1",
"moduleMigration.optionEnableFixedRevisionForChunks": "Habilitar solo #2",
"moduleMigration.optionHaveSetupUri": "Sí, tengo",
"moduleMigration.optionKeepPreviousBehaviour": "Mantener comportamiento anterior",
"moduleMigration.optionManualSetup": "Configurarlo todo manualmente",
"moduleMigration.optionNoAskAgain": "No, por favor pregúntame de nuevo",
"moduleMigration.optionNoSetupUri": "No, no tengo",
"moduleMigration.optionRemindNextLaunch": "Recordármelo en el próximo inicio",
"moduleMigration.optionSetupViaP2P": "Usar %{short_p2p_sync} para configurarlo",
"moduleMigration.optionSetupWizard": "Llévame al asistente de configuración",
"moduleMigration.optionYesFetchAgain": "Sí, obtener de nuevo",
"moduleMigration.titleCaseSensitivity": "Distinción de mayúsculas y minúsculas",
"moduleMigration.titleRecommendSetupUri": "Recomendación de usar un Setup URI",
"moduleMigration.titleWelcome": "Bienvenido a Self-hosted LiveSync",
"moduleObsidianMenu.replicate": "Replicar",
"More actions": "Más acciones",
"Mostly Complete: Decision Required": "Casi terminado: se requiere una decisión",
@@ -565,12 +554,10 @@
"obsidianLiveSyncSettingTab.btnEnable": "Activar",
"obsidianLiveSyncSettingTab.btnFix": "Corregir",
"obsidianLiveSyncSettingTab.btnGotItAndUpdated": "Lo entendí y actualicé.",
"obsidianLiveSyncSettingTab.btnNext": "Siguiente",
"obsidianLiveSyncSettingTab.btnStart": "Iniciar",
"obsidianLiveSyncSettingTab.btnTest": "Probar",
"obsidianLiveSyncSettingTab.btnUse": "Usar",
"obsidianLiveSyncSettingTab.buttonFetch": "Obtener",
"obsidianLiveSyncSettingTab.buttonNext": "Siguiente",
"obsidianLiveSyncSettingTab.defaultLanguage": "Predeterminado",
"obsidianLiveSyncSettingTab.descConnectSetupURI": "Este es el método recomendado para configurar Self-hosted LiveSync con una URI de configuración.",
"obsidianLiveSyncSettingTab.descCopySetupURI": "¡Perfecto para configurar un nuevo dispositivo!",
@@ -633,7 +620,6 @@
"obsidianLiveSyncSettingTab.msgEnableCorsChttpd": "Establecer chttpd.enable_cors",
"obsidianLiveSyncSettingTab.msgEnableEncryptionRecommendation": "Recomendamos habilitar el cifrado de extremo a extremo y la obfuscación de ruta. ¿Estás seguro de querer continuar sin cifrado?",
"obsidianLiveSyncSettingTab.msgFetchConfigFromRemote": "¿Quieres obtener la configuración del servidor remoto?",
"obsidianLiveSyncSettingTab.msgGenerateSetupURI": "¡Todo listo! ¿Quieres generar un URI de configuración para configurar otros dispositivos?",
"obsidianLiveSyncSettingTab.msgIfConfigNotPersistent": "Si la configuración del servidor no es persistente (por ejemplo, ejecutándose en docker), los valores aquí pueden cambiar. Una vez que puedas conectarte, por favor actualiza las configuraciones en el local.ini del servidor.",
"obsidianLiveSyncSettingTab.msgInvalidPassphrase": "Tu frase de contraseña de cifrado podría ser inválida. ¿Estás seguro de querer continuar?",
"obsidianLiveSyncSettingTab.msgNewVersionNote": "¿Aquí debido a una notificación de actualización? Por favor, revise el historial de versiones. Si está satisfecho, haga clic en el botón. Una nueva actualización volverá a mostrar esto.",
@@ -643,7 +629,6 @@
"obsidianLiveSyncSettingTab.msgObjectStorageWarning": "ADVERTENCIA: Esta característica está en desarrollo, así que por favor ten en cuenta lo siguiente:\n- Arquitectura de solo anexado. Se requiere una reconstrucción para reducir el almacenamiento.\n- Un poco frágil.\n- Al sincronizar por primera vez, todo el historial será transferido desde el remoto. Ten en cuenta los límites de datos y las velocidades lentas.\n- Solo las diferencias se sincronizan en vivo.\n\nSi encuentras algún problema o tienes ideas sobre esta característica, por favor crea un issue en GitHub.\nAprecio mucho tu gran dedicación.",
"obsidianLiveSyncSettingTab.msgOriginCheck": "Verificación de origen: {org}",
"obsidianLiveSyncSettingTab.msgRebuildRequired": "Es necesario reconstruir las bases de datos para aplicar los cambios. Por favor selecciona el método para aplicar los cambios.\n\n<details>\n<summary>Legendas</summary>\n\n| Símbolo | Significado |\n|: ------ :| ------- |\n| ⇔ | Actualizado |\n| ⇄ | Sincronizar para equilibrar |\n| ⇐,⇒ | Transferir para sobrescribir |\n| ⇠,⇢ | Transferir para sobrescribir desde otro lado |\n\n</details>\n\n## ${OPTION_REBUILD_BOTH}\nA simple vista: 📄 ⇒¹ 💻 ⇒² 🛰️ ⇢ⁿ 💻 ⇄ⁿ⁺¹ 📄\nReconstruir tanto la base de datos local como la remota utilizando los archivos existentes de este dispositivo.\nEsto bloquea a otros dispositivos, y necesitan realizar la obtención.\n## ${OPTION_FETCH}\nA simple vista: 📄 ⇄² 💻 ⇐¹ 🛰️ ⇔ 💻 ⇔ 📄\nInicializa la base de datos local y la reconstruye utilizando los datos obtenidos de la base de datos remota.\nEste caso incluye el caso en el que has reconstruido la base de datos remota.\n## ${OPTION_ONLY_SETTING}\nAlmacena solo la configuración. **Precaución: esto puede provocar corrupción de datos**; generalmente es necesario reconstruir la base de datos.",
"obsidianLiveSyncSettingTab.msgSelectAndApplyPreset": "Por favor, selecciona y aplica cualquier elemento preestablecido para completar el asistente.",
"obsidianLiveSyncSettingTab.msgSetCorsCredentials": "Configurar cors.credentials",
"obsidianLiveSyncSettingTab.msgSetCorsOrigins": "Configurar cors.origins",
"obsidianLiveSyncSettingTab.msgSetMaxDocSize": "Configurar couchdb.max_document_size",
@@ -700,7 +685,6 @@
"obsidianLiveSyncSettingTab.titleActiveRemoteServer": "Servidor remoto activo",
"obsidianLiveSyncSettingTab.titleAppearance": "Apariencia",
"obsidianLiveSyncSettingTab.titleConflictResolution": "Resolución de conflictos",
"obsidianLiveSyncSettingTab.titleCongratulations": "¡Felicidades!",
"obsidianLiveSyncSettingTab.titleCouchDB": "Servidor CouchDB",
"obsidianLiveSyncSettingTab.titleDeletionPropagation": "Propagación de eliminación",
"obsidianLiveSyncSettingTab.titleEncryptionNotEnabled": "El cifrado no está habilitado",
-16
View File
@@ -190,7 +190,6 @@
"moduleLiveSyncMain.titleScramEnabled": "Mode Scram activé",
"moduleLocalDatabase.logWaitingForReady": "En attente de disponibilité...",
"moduleLog.showLog": "Afficher le journal",
"moduleMigration.docUri": "https://github.com/vrtmrz/obsidian-livesync/blob/main/README.md#how-to-use",
"moduleMigration.fix0256.buttons.checkItLater": "Vérifier plus tard",
"moduleMigration.fix0256.buttons.DismissForever": "J'ai corrigé, et ne plus demander",
"moduleMigration.fix0256.buttons.fix": "Corriger",
@@ -212,26 +211,16 @@
"moduleMigration.logRemoteTweakUnavailable": "Impossible d'obtenir les valeurs d'ajustement distantes",
"moduleMigration.logSetupCancelled": "La configuration a été annulée, Self-hosted LiveSync attend votre configuration !",
"moduleMigration.msgFetchRemoteAgain": "Comme vous le savez peut-être déjà, Self-hosted LiveSync a modifié son comportement par défaut et la structure de sa base de données.\n\nEt, grâce à votre temps et vos efforts, la base distante semble déjà avoir été migrée. Félicitations !\n\nCependant, il faut encore un peu plus. La configuration de cet appareil n'est pas compatible avec la base distante. Nous devrons récupérer à nouveau la base distante. Devons-nous récupérer depuis le distant maintenant ?\n\n___Note : Nous ne pouvons pas synchroniser tant que la configuration n'a pas été modifiée et que la base n'a pas été récupérée à nouveau.___\n___Note 2 : Les fragments sont complètement immuables, nous ne pouvons récupérer que les métadonnées et les différences.___",
"moduleMigration.msgInitialSetup": "Votre appareil n'a **pas encore été configuré**. Laissez-moi vous guider dans le processus de configuration.\n\nVeuillez noter que chaque contenu de boîte de dialogue peut être copié dans le presse-papiers. Si vous souhaitez vous y référer plus tard, vous pouvez le coller dans une note d'Obsidian. Vous pouvez également le traduire dans votre langue via un outil de traduction.\n\nTout d'abord, disposez-vous d'une **URI de configuration** ?\n\nNote : Si vous ne savez pas ce que c'est, consultez la [documentation](${URI_DOC}).",
"moduleMigration.msgRecommendSetupUri": "Nous recommandons vivement de générer une URI de configuration et de l'utiliser.\nSi vous ne connaissez pas, veuillez consulter la [documentation](${URI_DOC}) (Désolé encore, mais c'est important).\n\nComment souhaitez-vous effectuer la configuration manuellement ?",
"moduleMigration.msgSinceV02321": "Depuis la v0.23.21, Self-hosted LiveSync a modifié son comportement par défaut et la structure de sa base. Les changements suivants ont été effectués :\n\n1. **Sensibilité à la casse des noms de fichiers**\n La gestion des noms de fichiers est désormais insensible à la casse. C'est un changement bénéfique pour la plupart des plateformes, hormis Linux et iOS, qui ne gèrent pas efficacement la casse des noms de fichiers.\n (Sur celles-ci, un avertissement s'affichera pour les fichiers portant le même nom avec une casse différente).\n\n2. **Gestion des révisions des fragments**\n Les fragments sont immuables, ce qui permet de fixer leurs révisions. Ce changement améliore les performances d'enregistrement des fichiers.\n\n___Cependant, pour activer l'un ou l'autre de ces changements, les bases locale et distante doivent être reconstruites. Ce processus prend quelques minutes, et nous recommandons de le faire quand vous avez le temps.___\n\n- Si vous souhaitez conserver le comportement précédent, vous pouvez ignorer ce processus via `${KEEP}`.\n- Si vous n'avez pas le temps, choisissez `${DISMISS}`. Vous serez invité à nouveau plus tard.\n- Si vous avez reconstruit la base sur un autre appareil, sélectionnez `${DISMISS}` et réessayez la synchronisation. Une différence étant détectée, vous serez invité à nouveau.",
"moduleMigration.optionAdjustRemote": "Ajuster au distant",
"moduleMigration.optionDecideLater": "Décider plus tard",
"moduleMigration.optionEnableBoth": "Activer les deux",
"moduleMigration.optionEnableFilenameCaseInsensitive": "Activer seulement #1",
"moduleMigration.optionEnableFixedRevisionForChunks": "Activer seulement #2",
"moduleMigration.optionHaveSetupUri": "Oui, j'en ai une",
"moduleMigration.optionKeepPreviousBehaviour": "Conserver le comportement précédent",
"moduleMigration.optionManualSetup": "Tout configurer manuellement",
"moduleMigration.optionNoAskAgain": "Non, demandez à nouveau",
"moduleMigration.optionNoSetupUri": "Non, je n'en ai pas",
"moduleMigration.optionRemindNextLaunch": "Me rappeler au prochain lancement",
"moduleMigration.optionSetupViaP2P": "Utiliser %{short_p2p_sync} pour configurer",
"moduleMigration.optionSetupWizard": "Ouvrir l'assistant de configuration",
"moduleMigration.optionYesFetchAgain": "Oui, récupérer à nouveau",
"moduleMigration.titleCaseSensitivity": "Sensibilité à la casse",
"moduleMigration.titleRecommendSetupUri": "Recommandation d'utilisation de l'URI de configuration",
"moduleMigration.titleWelcome": "Bienvenue dans Self-hosted LiveSync",
"moduleObsidianMenu.replicate": "Répliquer",
"Move remotely deleted files to the trash, instead of deleting.": "Déplacer les fichiers supprimés à distance vers la corbeille, au lieu de les supprimer.",
"Not all messages have been translated. And, please revert to \"Default\" when reporting errors.": "Tous les messages n'ont pas été traduits. Et veuillez revenir à « Par défaut » lorsque vous signalez des erreurs.",
@@ -249,12 +238,10 @@
"obsidianLiveSyncSettingTab.btnEnable": "Activer",
"obsidianLiveSyncSettingTab.btnFix": "Corriger",
"obsidianLiveSyncSettingTab.btnGotItAndUpdated": "J'ai compris et mis à jour.",
"obsidianLiveSyncSettingTab.btnNext": "Suivant",
"obsidianLiveSyncSettingTab.btnStart": "Démarrer",
"obsidianLiveSyncSettingTab.btnTest": "Tester",
"obsidianLiveSyncSettingTab.btnUse": "Utiliser",
"obsidianLiveSyncSettingTab.buttonFetch": "Récupérer",
"obsidianLiveSyncSettingTab.buttonNext": "Suivant",
"obsidianLiveSyncSettingTab.defaultLanguage": "Par défaut",
"obsidianLiveSyncSettingTab.descConnectSetupURI": "Méthode recommandée pour configurer Self-hosted LiveSync avec une URI de configuration.",
"obsidianLiveSyncSettingTab.descCopySetupURI": "Parfait pour configurer un nouvel appareil !",
@@ -317,7 +304,6 @@
"obsidianLiveSyncSettingTab.msgEnableCorsChttpd": "Définir chttpd.enable_cors",
"obsidianLiveSyncSettingTab.msgEnableEncryptionRecommendation": "Nous recommandons d'activer le chiffrement de bout en bout et l'obfuscation des chemins. Êtes-vous sûr de vouloir continuer sans chiffrement ?",
"obsidianLiveSyncSettingTab.msgFetchConfigFromRemote": "Voulez-vous récupérer la configuration depuis le serveur distant ?",
"obsidianLiveSyncSettingTab.msgGenerateSetupURI": "Tout est prêt ! Voulez-vous générer une URI de configuration pour configurer d'autres appareils ?",
"obsidianLiveSyncSettingTab.msgIfConfigNotPersistent": "Si la configuration du serveur n'est pas persistante (par ex. fonctionnant sur Docker), les valeurs peuvent changer. Une fois la connexion établie, mettez à jour les paramètres dans le local.ini du serveur.",
"obsidianLiveSyncSettingTab.msgInvalidPassphrase": "Votre phrase secrète de chiffrement peut être invalide. Êtes-vous sûr de vouloir continuer ?",
"obsidianLiveSyncSettingTab.msgNewVersionNote": "Arrivé ici suite à une notification de mise à jour ? Consultez l'historique des versions. Si vous êtes satisfait, cliquez sur le bouton. Une nouvelle mise à jour reproposera ceci.",
@@ -327,7 +313,6 @@
"obsidianLiveSyncSettingTab.msgObjectStorageWarning": "AVERTISSEMENT : cette fonctionnalité est en cours de développement, gardez à l'esprit ce qui suit :\n- Architecture en ajout seul. Une reconstruction est nécessaire pour réduire le stockage.\n- Un peu fragile.\n- Lors de la première synchronisation, tout l'historique sera transféré depuis le distant. Attention aux limites de données et aux débits lents.\n- Seules les différences sont synchronisées en direct.\n\nSi vous rencontrez des problèmes ou avez des idées sur cette fonctionnalité, merci d'ouvrir un ticket sur GitHub.\nMerci pour votre grand dévouement.",
"obsidianLiveSyncSettingTab.msgOriginCheck": "Vérification d'origine : ${org}",
"obsidianLiveSyncSettingTab.msgRebuildRequired": "La reconstruction des bases de données est nécessaire pour appliquer les changements. Veuillez sélectionner la méthode d'application.\n\n<details>\n<summary>Légende</summary>\n\n| Symbole | Signification |\n|: ------ :| ------- |\n| ⇔ | À jour |\n| ⇄ | Synchroniser pour équilibrer |\n| ⇐,⇒ | Transférer pour écraser |\n| ⇠,⇢ | Transférer pour écraser depuis l'autre côté |\n\n</details>\n\n## ${OPTION_REBUILD_BOTH}\nEn bref : 📄 ⇒¹ 💻 ⇒² 🛰️ ⇢ⁿ 💻 ⇄ⁿ⁺¹ 📄\nReconstruit les bases locale et distante à partir des fichiers existants de cet appareil.\nCeci provoque un verrouillage des autres appareils, qui devront effectuer une récupération.\n## ${OPTION_FETCH}\nEn bref : 📄 ⇄² 💻 ⇐¹ 🛰️ ⇔ 💻 ⇔ 📄\nInitialise la base locale et la reconstruit à partir des données récupérées depuis la base distante.\nCe cas inclut également celui où vous avez reconstruit la base distante.\n## ${OPTION_ONLY_SETTING}\nNe stocker que les paramètres. **Attention : cela peut entraîner une corruption des données** ; une reconstruction de la base est généralement nécessaire.",
"obsidianLiveSyncSettingTab.msgSelectAndApplyPreset": "Veuillez sélectionner et appliquer un préréglage pour terminer l'assistant.",
"obsidianLiveSyncSettingTab.msgSetCorsCredentials": "Définir cors.credentials",
"obsidianLiveSyncSettingTab.msgSetCorsOrigins": "Définir cors.origins",
"obsidianLiveSyncSettingTab.msgSetMaxDocSize": "Définir couchdb.max_document_size",
@@ -384,7 +369,6 @@
"obsidianLiveSyncSettingTab.titleActiveRemoteServer": "Serveur distant actif",
"obsidianLiveSyncSettingTab.titleAppearance": "Apparence",
"obsidianLiveSyncSettingTab.titleConflictResolution": "Résolution des conflits",
"obsidianLiveSyncSettingTab.titleCongratulations": "Félicitations !",
"obsidianLiveSyncSettingTab.titleCouchDB": "CouchDB",
"obsidianLiveSyncSettingTab.titleDeletionPropagation": "Propagation des suppressions",
"obsidianLiveSyncSettingTab.titleEncryptionNotEnabled": "Le chiffrement n'est pas activé",
-16
View File
@@ -191,7 +191,6 @@
"moduleLiveSyncMain.titleScramEnabled": "מצב בלימה פעיל",
"moduleLocalDatabase.logWaitingForReady": "ממתין לכשירות...",
"moduleLog.showLog": "הצג יומן",
"moduleMigration.docUri": "https://github.com/vrtmrz/obsidian-livesync/blob/main/README.md#how-to-use",
"moduleMigration.fix0256.buttons.checkItLater": "בדוק מאוחר יותר",
"moduleMigration.fix0256.buttons.DismissForever": "תיקנתי, ואל תשאל שוב",
"moduleMigration.fix0256.buttons.fix": "תקן",
@@ -213,26 +212,16 @@
"moduleMigration.logRemoteTweakUnavailable": "לא ניתן לקבל ערכי כיוונון מרוחקים",
"moduleMigration.logSetupCancelled": "ההגדרה בוטלה, Self-hosted LiveSync ממתין להגדרתך!",
"moduleMigration.msgFetchRemoteAgain": "כפי שייתכן שכבר ידוע לך, Self-hosted LiveSync שינה את התנהגות ברירת המחדל ומבנה מסד הנתונים.\n\nובזכות זמנך ומאמציך, מסד הנתונים המרוחד נראה כבר הוגר. ברכות!\n\nעם זאת, נדרש עוד קצת. תצורת מכשיר זה אינה תואמת למסד הנתונים המרוחד. נצטרך למשוך את מסד הנתונים המרוחד שוב. האם למשוך מהשרת המרוחד עכשיו?\n\n___הערה: לא ניתן לסנכרן עד שהתצורה תשתנה ומסד הנתונים יימשך שוב.___\n___הערה 2: הנתחים הם בלתי-ניתנים לשינוי לחלוטין, ניתן למשוך רק את המטה-נתונים וההפרש.___",
"moduleMigration.msgInitialSetup": "המכשיר שלך **טרם הוגדר**. אנחנו כאן לעזור לך בתהליך ההגדרה.\n\nשים לב שניתן להעתיק את תוכן כל דיאלוג ללוח. אם צריך לחזור אליו מאוחר יותר, ניתן להדביק אותו כפתק ב-Obsidian. ניתן גם לתרגם לשפתך בעזרת כלי תרגום.\n\nראשית, האם יש לך **Setup URI**?\n\nהערה: אם אינך יודע מהו, אנא עיין ב[תיעוד](${URI_DOC}).",
"moduleMigration.msgRecommendSetupUri": "אנו ממליצים בחום לייצר Setup URI ולהשתמש בו.\nאם אין לך ידע בנושא, אנא עיין ב[תיעוד](${URI_DOC}) (מתנצלים שוב, אך זה חשוב).\n\nכיצד ברצונך להגדיר ידנית?",
"moduleMigration.msgSinceV02321": "מאז גרסה 0.23.21, Self-hosted LiveSync שינה את התנהגות ברירת המחדל ומבנה מסד הנתונים. השינויים הבאים בוצעו:\n\n1. **תלות רישיות בשמות קבצים**\n הטיפול בשמות קבצים הוא כעת ללא תלות רישיות. זהו שינוי מועיל לרוב הפלטפורמות,\n פרט ל-Linux ו-iOS שאינן מנהלות תלות רישיות בקבצים ביעילות.\n (בפלטפורמות אלה, תוצג אזהרה עבור קבצים עם אותו שם אך רישיות שונה).\n\n2. **טיפול בגרסאות של נתחים**\n נתחים הם בלתי-ניתנים לשינוי, מה שמאפשר גרסאות קבועות. שינוי זה ישפר את\n ביצועי שמירת הקבצים.\n\n___עם זאת, כדי להפעיל אחד מהשינויים הללו, יש לבנות מחדש גם את מסד הנתונים המרוחד וגם את המקומי. תהליך זה לוקח כמה דקות, ואנו ממליצים לעשות זאת כשיש לך זמן פנוי.___\n\n- אם ברצונך לשמור את ההתנהגות הקודמת, ניתן לדלג על תהליך זה באמצעות `${KEEP}`.\n- אם אין לך מספיק זמן, אנא בחר `${DISMISS}`. תקבל תזכורת בהמשך.\n- אם בנית מחדש את מסד הנתונים במכשיר אחר, אנא בחר `${DISMISS}` ונסה לסנכרן שוב. מאחר שזוהה הפרש, תקבל תזכורת שוב.",
"moduleMigration.optionAdjustRemote": "התאם לשרת המרוחד",
"moduleMigration.optionDecideLater": "החלט מאוחר יותר",
"moduleMigration.optionEnableBoth": "הפעל את שניהם",
"moduleMigration.optionEnableFilenameCaseInsensitive": "הפעל רק #1",
"moduleMigration.optionEnableFixedRevisionForChunks": "הפעל רק #2",
"moduleMigration.optionHaveSetupUri": "כן, יש לי",
"moduleMigration.optionKeepPreviousBehaviour": "שמור על התנהגות קודמת",
"moduleMigration.optionManualSetup": "הגדר הכל ידנית",
"moduleMigration.optionNoAskAgain": "לא, אנא שאל שוב",
"moduleMigration.optionNoSetupUri": "לא, אין לי",
"moduleMigration.optionRemindNextLaunch": "הזכר לי בהפעלה הבאה",
"moduleMigration.optionSetupViaP2P": "השתמש ב-%{short_p2p_sync} להגדרה",
"moduleMigration.optionSetupWizard": "קח אותי לאשף ההגדרה",
"moduleMigration.optionYesFetchAgain": "כן, משוך שוב",
"moduleMigration.titleCaseSensitivity": "תלות רישיות",
"moduleMigration.titleRecommendSetupUri": "המלצה לשימוש ב-Setup URI",
"moduleMigration.titleWelcome": "ברוך הבא ל-Self-hosted LiveSync",
"moduleObsidianMenu.replicate": "שכפל",
"Move remotely deleted files to the trash, instead of deleting.": "העבר קבצים שנמחקו מרחוק לאשפה, במקום למחוק.",
"Not all messages have been translated. And, please revert to \"Default\" when reporting errors.": "לא כל ההודעות תורגמו. בנוסף, אנא חזור ל\"ברירת מחדל\" בעת דיווח על שגיאות.",
@@ -250,12 +239,10 @@
"obsidianLiveSyncSettingTab.btnEnable": "הפעל",
"obsidianLiveSyncSettingTab.btnFix": "תקן",
"obsidianLiveSyncSettingTab.btnGotItAndUpdated": "הבנתי ועדכנתי.",
"obsidianLiveSyncSettingTab.btnNext": "הבא",
"obsidianLiveSyncSettingTab.btnStart": "התחל",
"obsidianLiveSyncSettingTab.btnTest": "בדוק",
"obsidianLiveSyncSettingTab.btnUse": "השתמש",
"obsidianLiveSyncSettingTab.buttonFetch": "משוך",
"obsidianLiveSyncSettingTab.buttonNext": "הבא",
"obsidianLiveSyncSettingTab.defaultLanguage": "ברירת מחדל",
"obsidianLiveSyncSettingTab.descConnectSetupURI": "זוהי השיטה המומלצת להגדרת Self-hosted LiveSync עם Setup URI.",
"obsidianLiveSyncSettingTab.descCopySetupURI": "מושלם להגדרת מכשיר חדש!",
@@ -318,7 +305,6 @@
"obsidianLiveSyncSettingTab.msgEnableCorsChttpd": "הגדר chttpd.enable_cors",
"obsidianLiveSyncSettingTab.msgEnableEncryptionRecommendation": "אנו ממליצים להפעיל הצפנה מקצה לקצה ואת ערפול הנתיב. האם אתה בטוח שברצונך להמשיך ללא הצפנה?",
"obsidianLiveSyncSettingTab.msgFetchConfigFromRemote": "האם ברצונך למשוך את התצורה מהשרת המרוחד?",
"obsidianLiveSyncSettingTab.msgGenerateSetupURI": "הכל מוכן! האם ברצונך לייצר Setup URI להגדרת מכשירים אחרים?",
"obsidianLiveSyncSettingTab.msgIfConfigNotPersistent": "אם תצורת השרת אינה קבועה (למשל, פועלת ב-docker), הערכים כאן עשויים להשתנות. לאחר שתצליח להתחבר, אנא עדכן את ההגדרות ב-local.ini של השרת.",
"obsidianLiveSyncSettingTab.msgInvalidPassphrase": "ביטוי הסיסמה להצפנה שלך עשוי להיות לא תקין. האם אתה בטוח שברצונך להמשיך?",
"obsidianLiveSyncSettingTab.msgNewVersionNote": "הגעת כאן בשל הודעת שדרוג? אנא עיין בהיסטוריית הגרסאות. אם אתה מרוצה, לחץ על הכפתור. עדכון חדש יציג זאת שוב.",
@@ -328,7 +314,6 @@
"obsidianLiveSyncSettingTab.msgObjectStorageWarning": "אזהרה: תכונה זו בשלב פיתוח, לכן שים לב לנקודות הבאות:\n- ארכיטקטורת הוספה בלבד. נדרשת בנייה מחדש לצמצום האחסון.\n- קצת רגיש.\n- בסנכרון הראשון, כל ההיסטוריה תועבר מהשרת המרוחד. שים לב למגבלות נתונים ומהירות.\n- רק הפרשים מסונכרנים בזמן אמת.\n\nאם נתקלת בבעיות, או שיש לך רעיונות לגבי תכונה זו, אנא פתח Issue ב-GitHub.\nאנחנו מעריכים את ההקדשה הגדולה שלך.",
"obsidianLiveSyncSettingTab.msgOriginCheck": "בדיקת מקור: ${org}",
"obsidianLiveSyncSettingTab.msgRebuildRequired": "נדרשת בנייה מחדש של מסדי הנתונים כדי להחיל את השינויים. אנא בחר את השיטה.\n\n<details>\n<summary>מקרא</summary>\n\n| סמל | משמעות |\n|: ------ :| ------- |\n| ⇔ | מעודכן |\n| ⇄ | סנכרן לאיזון |\n| ⇐,⇒ | העבר לדריסה |\n| ⇠,⇢ | העבר לדריסה מהצד השני |\n\n</details>\n\n## ${OPTION_REBUILD_BOTH}\nבמבט: 📄 ⇒¹ 💻 ⇒² 🛰️ ⇢ⁿ 💻 ⇄ⁿ⁺¹ 📄\nבנה מחדש גם את מסד הנתונים המקומי וגם המרוחד תוך שימוש בקבצים קיימים ממכשיר זה.\nפעולה זו תנעל מכשירים אחרים שיצטרכו לבצע משיכה.\n## ${OPTION_FETCH}\nבמבט: 📄 ⇄² 💻 ⇐¹ 🛰️ ⇔ 💻 ⇔ 📄\nאתחל את מסד הנתונים המקומי ובנה אותו מחדש תוך שימוש בנתונים שנמשכו ממסד הנתונים המרוחד.\nכולל את המקרה שבו בנית מחדש את מסד הנתונים המרוחד.\n## ${OPTION_ONLY_SETTING}\nשמור רק את ההגדרות. **זהירות: עלול לגרום לפגיעה בנתונים**; בנייה מחדש של מסד הנתונים נדרשת בדרך כלל.",
"obsidianLiveSyncSettingTab.msgSelectAndApplyPreset": "אנא בחר והחל פריט קבוע מראש כלשהו להשלמת האשף.",
"obsidianLiveSyncSettingTab.msgSetCorsCredentials": "הגדר cors.credentials",
"obsidianLiveSyncSettingTab.msgSetCorsOrigins": "הגדר cors.origins",
"obsidianLiveSyncSettingTab.msgSetMaxDocSize": "הגדר couchdb.max_document_size",
@@ -385,7 +370,6 @@
"obsidianLiveSyncSettingTab.titleActiveRemoteServer": "שרת מרוחד פעיל",
"obsidianLiveSyncSettingTab.titleAppearance": "מראה",
"obsidianLiveSyncSettingTab.titleConflictResolution": "פתרון קונפליקטים",
"obsidianLiveSyncSettingTab.titleCongratulations": "מזל טוב!",
"obsidianLiveSyncSettingTab.titleCouchDB": "CouchDB",
"obsidianLiveSyncSettingTab.titleDeletionPropagation": "הפצת מחיקות",
"obsidianLiveSyncSettingTab.titleEncryptionNotEnabled": "ההצפנה אינה מופעלת",
-16
View File
@@ -291,7 +291,6 @@
"moduleLiveSyncMain.titleScramEnabled": "緊急停止(Scram)が有効",
"moduleLocalDatabase.logWaitingForReady": "しばらくお待ちください...",
"moduleLog.showLog": "ログを表示",
"moduleMigration.docUri": "https://github.com/vrtmrz/obsidian-livesync/blob/main/README.md#how-to-use",
"moduleMigration.fix0256.buttons.checkItLater": "後で確認する",
"moduleMigration.fix0256.buttons.DismissForever": "修正済み、今後確認しない",
"moduleMigration.fix0256.buttons.fix": "修正",
@@ -313,26 +312,16 @@
"moduleMigration.logRemoteTweakUnavailable": "リモートの調整値を取得できませんでした",
"moduleMigration.logSetupCancelled": "セットアップがキャンセルされました。Self-hosted LiveSyncはセットアップを待っています!",
"moduleMigration.msgFetchRemoteAgain": "ご存知のとおり、self-hosted LiveSyncはデフォルトの動作とデータベース構造を変更しました。\n\nご協力のおかげで、リモートデータベースはすでに移行されているようです。おめでとうございます!\n\nしかし、もう少し必要です。このデバイスの設定はリモートデータベースと互換性がありません。リモートデータベースを再度フェッチする必要があります。今すぐリモートから再フェッチしますか?\n\n___注意: 設定が変更され、データベースが再フェッチされるまで同期できません。___\n___注意2: チャンクは完全に不変なので、メタデータと差分のみフェッチできます。___",
"moduleMigration.msgInitialSetup": "このデバイスは**まだセットアップされていません**。セットアッププロセスをご案内します。\n\nすべてのダイアログの内容はクリップボードにコピーできます。後で参照する必要があれば、Obsidianのノートに貼り付けてください。翻訳ツールを使ってお使いの言語に翻訳することもできます。\n\nまず、**セットアップURI**をお持ちですか?\n\n注意: それが何か分からない場合は、[documentation](${URI_DOC})を参照してください。",
"moduleMigration.msgRecommendSetupUri": "セットアップURIを生成して使用することを強くお勧めします。\nこれについて知識がない場合は、[documentation](${URI_DOC})を参照してください(重要です)。\n\n手動でセットアップしますか?",
"moduleMigration.msgSinceV02321": "v0.23.21以降、self-hosted LiveSyncはデフォルトの動作とデータベース構造を変更しました。以下の変更が行われました:\n\n1. **ファイル名の大文字小文字の区別**\n ファイル名の処理が大文字小文字を区別しなくなりました。これは、ファイル名の大文字小文字を効果的に管理しないLinuxとiOS以外のほとんどのプラットフォームにとって有益な変更です。\n (これらの環境では、同じ名前で大文字小文字が異なるファイルに対して警告が表示されます)。\n\n2. **チャンクのリビジョン処理**\n チャンクは不変であり、リビジョンを固定できます。この変更により、ファイル保存のパフォーマンスが向上します。\n\n___しかし、これらの変更を有効にするには、リモートとローカルの両方のデータベースを再構築する必要があります。このプロセスは数分かかります。時間に余裕があるときに行うことをお勧めします。___\n\n- 以前の動作を維持したい場合は、`${KEEP}`を使用してこのプロセスをスキップできます。\n- 時間がない場合は、`${DISMISS}`を選択してください。後で再度確認されます。\n- 別のデバイスでデータベースを再構築した場合は、`${DISMISS}`を選択して再度同期してみてください。差異が検出されたため、再度確認されます。",
"moduleMigration.optionAdjustRemote": "リモートに合わせる",
"moduleMigration.optionDecideLater": "後で決める",
"moduleMigration.optionEnableBoth": "両方を有効にする",
"moduleMigration.optionEnableFilenameCaseInsensitive": "#1のみ有効にする",
"moduleMigration.optionEnableFixedRevisionForChunks": "#2のみ有効にする",
"moduleMigration.optionHaveSetupUri": "はい、持っています",
"moduleMigration.optionKeepPreviousBehaviour": "以前の動作を維持",
"moduleMigration.optionManualSetup": "すべて手動でセットアップ",
"moduleMigration.optionNoAskAgain": "いいえ、後で確認する",
"moduleMigration.optionNoSetupUri": "いいえ、持っていません",
"moduleMigration.optionRemindNextLaunch": "次回起動時にリマインド",
"moduleMigration.optionSetupViaP2P": "%{short_p2p_sync}を使ってセットアップ",
"moduleMigration.optionSetupWizard": "セットアップウィザードへ",
"moduleMigration.optionYesFetchAgain": "はい、再フェッチする",
"moduleMigration.titleCaseSensitivity": "大文字小文字の区別",
"moduleMigration.titleRecommendSetupUri": "セットアップURIの使用を推奨",
"moduleMigration.titleWelcome": "Self-hosted LiveSyncへようこそ",
"moduleObsidianMenu.replicate": "レプリケート",
"More actions": "その他の操作",
"Move remotely deleted files to the trash, instead of deleting.": "リモートで削除されたファイルを削除せずにゴミ箱に移動する。",
@@ -361,12 +350,10 @@
"obsidianLiveSyncSettingTab.btnEnable": "有効化",
"obsidianLiveSyncSettingTab.btnFix": "修正",
"obsidianLiveSyncSettingTab.btnGotItAndUpdated": "理解しました、更新しました。",
"obsidianLiveSyncSettingTab.btnNext": "次へ",
"obsidianLiveSyncSettingTab.btnStart": "開始",
"obsidianLiveSyncSettingTab.btnTest": "テスト",
"obsidianLiveSyncSettingTab.btnUse": "使用",
"obsidianLiveSyncSettingTab.buttonFetch": "フェッチ",
"obsidianLiveSyncSettingTab.buttonNext": "次へ",
"obsidianLiveSyncSettingTab.defaultLanguage": "デフォルト",
"obsidianLiveSyncSettingTab.descConnectSetupURI": "セットアップURIを使用してSelf-hosted LiveSyncをセットアップする推奨方法です。",
"obsidianLiveSyncSettingTab.descCopySetupURI": "新しいデバイスのセットアップにおすすめ!",
@@ -429,7 +416,6 @@
"obsidianLiveSyncSettingTab.msgEnableCorsChttpd": "chttpd.enable_corsを設定",
"obsidianLiveSyncSettingTab.msgEnableEncryptionRecommendation": "エンドツーエンド暗号化とパス難読化を有効にすることをお勧めします。暗号化なしで続行してもよろしいですか?",
"obsidianLiveSyncSettingTab.msgFetchConfigFromRemote": "リモートサーバーから設定を取得しますか?",
"obsidianLiveSyncSettingTab.msgGenerateSetupURI": "完了!他のデバイスをセットアップするためのセットアップURIを生成しますか?",
"obsidianLiveSyncSettingTab.msgIfConfigNotPersistent": "サーバー設定が永続的でない場合(例: Dockerで実行中)、ここの値は変更される可能性があります。接続できるようになったら、サーバーのlocal.iniの設定を更新してください。",
"obsidianLiveSyncSettingTab.msgInvalidPassphrase": "暗号化パスフレーズが無効かもしれません。続行してもよろしいですか?",
"obsidianLiveSyncSettingTab.msgNewVersionNote": "アップグレード通知でここに来ましたか?バージョン履歴を確認してください。納得したらボタンをクリックしてください。新しい更新があると再度確認されます。",
@@ -439,7 +425,6 @@
"obsidianLiveSyncSettingTab.msgObjectStorageWarning": "警告: この機能は開発中です。以下の点にご注意ください:\n- 追記専用アーキテクチャ。ストレージを縮小するには再構築が必要です。\n- やや不安定です。\n- 初回同期時、すべての履歴がリモートから転送されます。データ制限と速度に注意してください。\n- ライブ同期は差分のみです。\n\n問題があれば、またはこの機能についてアイデアがあれば、GitHubにIssueを作成してください。\nご協力に感謝します。",
"obsidianLiveSyncSettingTab.msgOriginCheck": "オリジン確認: ${org}",
"obsidianLiveSyncSettingTab.msgRebuildRequired": "変更を適用するにはデータベースの再構築が必要です。変更を適用する方法を選択してください。\n\n<details>\n<summary>凡例</summary>\n\n| 記号 | 意味 |\n|: ------ :| ------- |\n| ⇔ | 最新 |\n| ⇄ | 同期してバランスを取る |\n| ⇐,⇒ | 上書きするため転送 |\n| ⇠,⇢ | 反対側から上書きするため転送 |\n\n</details>\n\n## ${OPTION_REBUILD_BOTH}\n概要: 📄 ⇒¹ 💻 ⇒² 🛰️ ⇢ⁿ 💻 ⇄ⁿ⁺¹ 📄\nこのデバイスの既存ファイルを使用してローカルとリモートの両方のデータベースを再構築します。\n他のデバイスはロックアウトされ、フェッチが必要です。\n## ${OPTION_FETCH}\n概要: 📄 ⇄² 💻 ⇐¹ 🛰️ ⇔ 💻 ⇔ 📄\nローカルデータベースを初期化し、リモートデータベースから取得したデータを使用して再構築します。\nリモートデータベースを再構築した場合も含まれます。\n## ${OPTION_ONLY_SETTING}\n設定のみを保存します。**注意: データ破損につながる可能性があります**。通常、データベースの再構築が必要です。",
"obsidianLiveSyncSettingTab.msgSelectAndApplyPreset": "ウィザードを完了するには、プリセット項目を選択して適用してください。",
"obsidianLiveSyncSettingTab.msgSetCorsCredentials": "cors.credentialsを設定",
"obsidianLiveSyncSettingTab.msgSetCorsOrigins": "cors.originsを設定",
"obsidianLiveSyncSettingTab.msgSetMaxDocSize": "couchdb.max_document_sizeを設定",
@@ -496,7 +481,6 @@
"obsidianLiveSyncSettingTab.titleActiveRemoteServer": "アクティブなリモートサーバー",
"obsidianLiveSyncSettingTab.titleAppearance": "外観",
"obsidianLiveSyncSettingTab.titleConflictResolution": "競合解決",
"obsidianLiveSyncSettingTab.titleCongratulations": "おめでとうございます!",
"obsidianLiveSyncSettingTab.titleCouchDB": "CouchDB サーバー",
"obsidianLiveSyncSettingTab.titleDeletionPropagation": "削除の伝播",
"obsidianLiveSyncSettingTab.titleEncryptionNotEnabled": "暗号化が有効になっていません",
+319 -17
View File
@@ -1,33 +1,78 @@
{
", please select the option that best describes the current state of your Vault. The application will then check your files in the most appropriate way based on your selection.": ", 이 보관함의 현재 상태를 가장 잘 설명하는 옵션을 선택해 주세요. 선택하신 내용에 따라 가장 적절한 방식으로 파일을 확인합니다.",
"(Active)": "(활성)",
"(BETA) Always overwrite with a newer file": "(베타) 항상 새로운 파일로 덮어쓰기",
"(Beta) Use ignore files": "(베타) 제외 규칙 파일 사용",
"(Days passed, 0 to disable automatic-deletion)": "(지난 일수, 0으로 설정하면 자동 삭제 비활성화)",
"(e.g., after editing many files whilst offline)": "(예: 오프라인 상태에서 많은 파일을 편집한 뒤)",
"(e.g., immediately after restoring on another computer, or having recovered from a backup)": "(예: 다른 컴퓨터에서 복원한 직후이거나 백업에서 복구한 경우)",
"(e.g., setting up for the first time on a new smartphone, starting from a clean slate)": "(예: 새 스마트폰에서 처음 설정하여 빈 상태에서 시작하는 경우)",
"(ex. Read chunks online) If this option is enabled, LiveSync reads chunks online directly instead of replicating them locally. Increasing Custom chunk size is recommended.": "(예: 청크를 원격에서 읽음) 이 옵션을 활성화하면, LiveSync는 청크를 로컬에 복제하지 않고 원격에서 직접 읽습니다. 커스텀 청크 크기를 키우는 것을 권장합니다.",
"(MB) If this is set, changes to local and remote files that are larger than this will be skipped. If the file becomes smaller again, a newer one will be used.": "(MB) 이 값이 설정되면, 이보다 큰 로컬 및 원격 파일의 변경 사항은 건너뜁니다. 파일이 다시 작아지면 더 새로운 파일이 사용됩니다.",
"(Mega chars)": "(백만 자 단위)",
"(Missing)": "(없음)",
"(Not recommended) If set, credentials will be stored in the file.": "(권장하지 않음) 설정한 경우 자격 증명이 파일에 저장됩니다.",
"(Obsolete) Use an old adapter for compatibility": "(사용 중단) 호환성을 위해 이전 어댑터 사용",
"(RegExp) Empty to sync all files. Set filter as a regular expression to limit synchronising files.": "(정규식) 비워 두면 모든 파일을 동기화합니다. 정규식을 지정하면 동기화할 파일을 제한할 수 있습니다.",
"(RegExp) If this is set, any changes to local and remote files that match this will be skipped.": "(정규식) 설정하면 이 패턴과 일치하는 로컬 및 원격 파일 변경은 모두 건너뜁니다.",
"(Select this if you are already using synchronisation on another computer or smartphone.) This option is suitable if you are new to LiveSync and want to set it up from scratch.": "(다른 컴퓨터나 스마트폰에서 이미 동기화를 사용 중이라면 선택하세요.) 이 기기를 기존 LiveSync 구성에 추가하려는 경우에 적합합니다.",
"(Select this if you are configuring this device as the first synchronisation device.) This option is suitable if you are new to LiveSync and want to set it up from scratch.": "(이 기기를 첫 번째 동기화 기기로 설정한다면 선택하세요.) LiveSync를 처음 사용하며 처음부터 설정하려는 경우에 적합합니다.",
"↑: Overwrite Remote": "↑: 원격 덮어쓰기",
"↓: Overwrite Local": "↓: 로컬 덮어쓰기",
"⇅: Use newer": "⇅: 더 새로운 쪽 사용",
"+1 week": "+1주",
"> [!INFO]- The connected devices have been detected as follows:\n${devices}": "> [!INFO]- 다음 연결된 기기가 감지되었습니다:\n${devices}",
"⚠️ Important Notice": "⚠️ 중요 안내",
"⚠️ Please Confirm the Following": "⚠️ 다음 내용을 확인해 주세요",
"✔ SELECT": "✔ 선택",
"✔ SYNC": "✔ 동기화",
"✔ WATCH": "✔ 감시",
"📡 Off": "📡 꺼짐",
"📡 On": "📡 켜짐",
"🔴 Disconnected": "🔴 연결 끊김",
"🕵️ Diag": "🕵️ 진단",
"🗑 Delete": "🗑 삭제",
"🟢 Connected": "🟢 연결됨",
"${count} issue(s) detected!": "${count}개의 문제가 감지되었습니다!",
"A Setup URI is a single string of text containing your server address and authentication details. Using a URI, if one was generated by your server installation script, provides a simple and secure configuration.": "Setup URI는 서버 주소와 인증 정보를 담은 하나의 문자열입니다. 서버 설치 스크립트가 URI를 생성했다면, 이를 사용해 간단하고 안전하게 구성할 수 있습니다.",
"Accept": "수락",
"Accept in session": "이 세션에서만 수락",
"ACCEPTED": "수락됨",
"ACCEPTED (in session)": "수락됨 (이 세션에서만)",
"Access Key": "액세스 키",
"Access Key ID": "액세스 키 ID",
"Action": "작업",
"Activate": "활성화",
"Active Remote Configuration": "활성 원격 구성",
"Add default patterns": "기본 패턴 추가",
"Add new connection": "연결 추가",
"AddOn Module (ConfigSync) has not been loaded. This is very unexpected situation. Please report this issue.": "애드온 모듈(ConfigSync)이 로드되지 않았습니다. 매우 예기치 못한 상황입니다. 이 문제를 신고해 주세요.",
"AddOn Module (HiddenFileSync) has not been loaded. This is very unexpected situation. Please report this issue.": "애드온 모듈(HiddenFileSync)이 로드되지 않았습니다. 매우 예기치 못한 상황입니다. 이 문제를 신고해 주세요.",
"Advanced": "고급",
"Advanced Settings": "고급 설정",
"After restarting, the data on this device will be uploaded to the server as the 'master copy'. Please be aware that any unintended data currently on the server will be completely overwritten.": "재시작하면 이 기기의 데이터가 '원본'으로서 서버에 업로드됩니다. 현재 서버에 있는 의도치 않은 데이터는 모두 완전히 덮어써진다는 점에 유의해 주세요.",
"After restarting, the database on this device will be rebuilt using data from the server. If there are any unsynchronised files in this vault, conflicts may occur with the server data.": "재시작하면 서버의 데이터를 사용해 이 기기의 데이터베이스가 재구축됩니다. 이 보관함에 동기화되지 않은 파일이 있다면 서버 데이터와 충돌이 발생할 수 있습니다.",
"After that, synchronise to a brand new vault on each other device with the new remote one by one.": "그런 다음 다른 기기에서도 하나씩 새 원격을 사용해 완전히 새로운 보관함에 동기화하세요.",
"All checks passed successfully!": "모든 검사를 통과했습니다!",
"All devices have the same progress value (${progress}). Your devices seem to be synchronised. And be able to proceed with Garbage Collection.": "모든 기기의 진행 값이 동일합니다(${progress}). 기기들이 동기화된 것으로 보이므로 가비지 컬렉션을 진행할 수 있습니다.",
"All the same or non-existent": "모두 동일하거나 존재하지 않음",
"Allow in session": "이 세션에서만 허용",
"Allow permanently": "항상 허용",
"Also, please note that if you are using Peer-to-Peer synchronization, this configuration will be used when you switch to other methods and connect to a remote server in the future.": "또한 Peer-to-Peer 동기화를 사용 중이라면, 이 구성은 나중에 다른 방식으로 전환하여 원격 서버에 연결할 때 사용된다는 점에 유의해 주세요.",
"Always prompt merge conflicts": "항상 병합 충돌 알림",
"Analyse": "분석",
"Analyse database usage": "데이터베이스 사용량 분석",
"Analyse database usage and generate a TSV report for diagnosis yourself. You can paste the generated report with any spreadsheet you like.": "데이터베이스 사용량을 분석하고 직접 진단할 수 있도록 TSV 보고서를 생성합니다. 생성된 보고서는 원하는 스프레드시트에 붙여 넣어 확인할 수 있습니다.",
"Apply All Selected": "선택 항목 모두 적용",
"Apply Latest Change if Conflicting": "충돌 시 최신 변경 사항 적용",
"Apply preset configuration": "프리셋 구성 적용",
"Apply the settings": "설정 적용",
"Ask a passphrase at every launch": "시작할 때마다 패스프레이즈 묻기",
"Auto Connect": "자동 연결",
"Auto Start P2P Connection": "P2P 연결 자동 시작",
"Automatic": "자동",
"Automatically Sync all files when opening Obsidian.": "Obsidian을 열 때 모든 파일을 자동으로 동기화합니다.",
"Available Peers": "사용 가능한 피어",
"Back": "뒤로",
"Back to non-configured": "미구성 상태로 되돌리기",
"Batch database update": "일괄 데이터베이스 업데이트",
@@ -35,19 +80,31 @@
"Batch size": "배치 크기",
"Batch size of on-demand fetching": "필요 시 가져오기의 배치 크기",
"Before v0.17.16, we used an old adapter for the local database. Now the new adapter is preferred. However, it needs local database rebuilding. Please disable this toggle when you have enough time. If leave it enabled, also while fetching from the remote database, you will be asked to disable this.": "v0.17.16 이전에는 로컬 데이터베이스에 이전 어댑터를 사용했습니다. 이제는 새로운 어댑터를 권장합니다. 하지만 로컬 데이터베이스 재구축이 필요합니다. 충분한 시간이 있을 때 이 토글을 비활성화해 주세요. 활성화된 상태로 두면 원격 데이터베이스에서 가져올 때도 이를 비활성화하라는 메시지가 나타납니다.",
"Broadcasting?": "브로드캐스트란?",
"Bucket Name": "버킷 이름",
"by resetting the remote, you will be informed on other devices.": "원격을 초기화하면 다른 기기에서 알림을 받게 됩니다.",
"Cancel": "취소",
"Cancel Garbage Collection": "가비지 컬렉션 취소",
"Changing the encryption algorithm will prevent access to any data previously encrypted with a different algorithm. Ensure that all your devices are configured to use the same algorithm to maintain access to your data.": "암호화 알고리즘을 변경하면 이전에 다른 알고리즘으로 암호화된 데이터에는 접근할 수 없게 됩니다. 데이터에 계속 접근할 수 있도록 모든 기기가 동일한 알고리즘을 사용하도록 설정해 주세요.",
"Changing this setting requires migrating existing data (a bit time may be taken) and restarting Obsidian. Please make sure to back up your data before proceeding.": "이 설정을 변경하려면 기존 데이터를 마이그레이션하고(시간이 다소 걸릴 수 있습니다) Obsidian을 재시작해야 합니다. 진행하기 전에 반드시 데이터를 백업해 주세요.",
"Check": "확인",
"Check and convert non-path-obfuscated files": "경로 난독화되지 않은 파일 검사 및 변환",
"Check for documents that have not been converted to path-obfuscated IDs and convert them if necessary.": "아직 경로 난독화 ID로 변환되지 않은 문서를 확인하고 필요하면 변환합니다.",
"Checking connection... Please wait.": "연결을 확인하는 중입니다... 잠시만 기다려 주세요.",
"Chunks": "청크",
"Close": "닫기",
"Close & Disconnect": "닫고 연결 끊기",
"Close this dialog": "이 대화 상자 닫기",
"Closed:": "종료됨:",
"cmdConfigSync.showCustomizationSync": "사용자 설정 동기화 표시",
"Comma separated `.gitignore, .dockerignore`": "쉼표로 구분된 `.gitignore, .dockerignore`",
"Command": "명령",
"Communicating": "통신 중",
"Compaction in progress on remote database...": "원격 데이터베이스에서 압축 정리를 진행 중입니다...",
"Compaction on remote database completed successfully.": "원격 데이터베이스 압축 정리가 성공적으로 완료되었습니다.",
"Compaction on remote database failed.": "원격 데이터베이스 압축 정리에 실패했습니다.",
"Compaction on remote database timed out.": "원격 데이터베이스 압축 정리 시간이 초과되었습니다.",
"Compare file": "파일 비교",
"Compare the content of files between on local database and storage. If not matched, you will be asked which one you want to keep.": "로컬 데이터베이스와 저장소 간의 파일 내용을 비교합니다. 일치하지 않으면 어떤 쪽을 유지할지 묻게 됩니다.",
"Compatibility (Conflict Behaviour)": "호환성 (충돌 동작)",
"Compatibility (Database structure)": "호환성 (데이터베이스 구조)",
@@ -56,48 +113,78 @@
"Compatibility (Remote Database)": "호환성 (원격 데이터베이스)",
"Compatibility (Trouble addressed)": "호환성 (문제 대응)",
"Compute revisions for chunks": "청크에 대한 리비전 계산",
"Configuration": "구성",
"Configuration Encryption": "구성 암호화",
"Configure": "설정",
"Configure And Change Remote": "원격 구성 및 변경",
"Configure E2EE": "E2EE 구성",
"Configure Remote": "원격 구성",
"Configure the same server information as your other devices again, manually, very advanced users only.": "다른 기기와 동일한 서버 정보를 다시 직접 입력합니다. 숙련된 사용자 전용입니다.",
"Connect": "연결",
"Connected to Signaling Server (as Peer ID: ${peerId})": "시그널링 서버에 연결되었습니다 (피어 ID: ${peerId})",
"Connected:": "연결됨:",
"Connection Method": "연결 방법",
"Connection Settings": "연결 설정",
"Connection:": "연결:",
"Continue anyway": "무시하고 계속",
"Continue to CouchDB setup": "CouchDB 설정으로 계속",
"Continue to Peer-to-Peer only setup": "Peer-to-Peer 전용 설정으로 계속",
"Continue to S3/MinIO/R2 setup": "S3/MinIO/R2 설정으로 계속",
"Copy": "복사",
"Copy Report to clipboard": "보고서를 클립보드에 복사",
"CouchDB Configuration": "CouchDB 구성",
"CouchDB Connection Tweak": "CouchDB 연결 조정",
"Create P2P remote": "P2P 원격 만들기",
"Cross-platform": "크로스 플랫폼",
"Current adapter: {adapter}": "현재 어댑터: {adapter}",
"Custom Headers": "사용자 지정 헤더",
"Customization Sync": "사용자 설정 동기화",
"Customization Sync (Beta3)": "사용자 설정 동기화 (Beta3)",
"Data Compression": "데이터 압축",
"Data to Copy": "복사할 데이터",
"Database -> Storage": "데이터베이스 -> 스토리지",
"Database Adapter": "데이터베이스 어댑터",
"Database Name": "데이터베이스 이름",
"Database suffix": "데이터베이스 접미사",
"Date": "날짜",
"Default": "기본값",
"Delay conflict resolution of inactive files": "비활성 파일의 충돌 해결 지연",
"Delay merge conflict prompt for inactive files.": "비활성 파일의 병합 충돌 프롬프트 지연.",
"Delete": "삭제",
"Delete all customization sync data": "모든 사용자 설정 동기화 데이터 삭제",
"Delete all data on the remote server.": "원격 서버의 모든 데이터를 삭제합니다.",
"Delete All of": "모두 삭제할 대상",
"Delete local database to reset or uninstall Self-hosted LiveSync": "Self-hosted LiveSync를 초기화하거나 제거하기 위해 로컬 데이터베이스를 삭제",
"Delete old metadata of deleted files on start-up": "시작 시 삭제된 파일의 오래된 메타데이터 삭제",
"Delete Remote Configuration": "원격 구성 삭제",
"Delete remote configuration '{name}'?": "'{name}' 원격 구성을 삭제할까요?",
"Delete remote configuration '${name}'?": "'${name}' 원격 구성을 삭제할까요?",
"DENIED": "거부됨",
"DENIED (in session)": "거부됨 (이 세션에서만)",
"Deny": "거부",
"Deny in session": "이 세션에서만 거부",
"Deny permanently": "항상 거부",
"Deselect all": "선택 모두 해제",
"desktop": "데스크톱",
"Detected Peers": "감지된 피어",
"Developer": "개발자",
"Device": "기기",
"device name": "기기 이름",
"Device name": "기기 이름",
"Device name to identify the device. Please use shorter one for the stable peer detection, i.e., \"iphone-16\" or \"macbook-2021\".": "기기를 식별하기 위한 기기 이름입니다. 피어를 안정적으로 감지할 수 있도록 \"iphone-16\"이나 \"macbook-2021\"처럼 짧은 이름을 사용해 주세요.",
"Device Peer ID": "기기 피어 ID",
"Device Setup Method": "기기 설정 방법",
"Devices:": "기기:",
"Diagnostic RTCPeerConnection is enabled": "진단용 RTCPeerConnection이 활성화되어 있습니다",
"dialog.yourLanguageAvailable": "Self-hosted LiveSync가 사용 중인 언어의 번역을 제공하므로 %{Display Language} 설정이 활성화되었습니다.\n\n참고: 모든 메시지가 번역되어 있지는 않습니다. 여러분의 기여를 기다리고 있습니다!\n참고 2: 이슈를 등록할 때는 **%{lang-def} 로 되돌린 뒤** 스크린샷과 메시지, 로그를 첨부해 주세요. 설정 대화 상자에서 되돌릴 수 있습니다.\n편하게 사용하실 수 있기를 바랍니다!",
"dialog.yourLanguageAvailable.btnRevertToDefault": "%{lang-def} 유지",
"dialog.yourLanguageAvailable.Title": " 번역을 사용할 수 있습니다!",
"Diff": "차이",
"Different": "다름",
"Disables all synchronization and restart.": "모든 동기화를 비활성화하고 재시작합니다.",
"Disables logging, only shows notifications. Please disable if you report an issue.": "로깅을 비활성화하고 알림만 표시합니다. 문제를 신고하는 경우 비활성화해 주세요.",
"Disconnect": "연결 끊기",
"Dismiss": "닫기",
"Display Language": "표시 언어",
"Display name": "표시 이름",
"Do not check configuration mismatch before replication": "복제 전 구성 불일치 확인 안 함",
@@ -136,33 +223,70 @@
"Enable customization sync": "사용자 설정 동기화 활성화",
"Enable Developers' Debug Tools.": "개발자 디버그 도구 활성화",
"Enable edge case treatment features": "특수 사례 처리 기능 활성화",
"Enable P2P Replicator": "P2P 복제기 활성화",
"Enable poweruser features": "파워 유저 기능 활성화",
"Enable this if your Object Storage doesn't support CORS": "객체 스토리지가 CORS를 지원하지 않는 경우 활성화하세요",
"Enable this option to automatically apply the most recent change to documents even when it conflicts": "이 옵션을 활성화하면 충돌이 있어도 문서에 가장 최근 변경 사항을 자동으로 적용합니다",
"Enabled": "활성화됨",
"Enabling end-to-end encryption ensures that your data is encrypted on your device before being sent to the remote server. This means that even if someone gains access to the server, they won't be able to read your data without the passphrase. Make sure to remember your passphrase, as it will be required to decrypt your data on other devices.": "종단 간 암호화를 활성화하면 데이터가 원격 서버로 전송되기 전에 기기에서 암호화됩니다. 따라서 누군가 서버에 접근하더라도 패스프레이즈 없이는 데이터를 읽을 수 없습니다. 다른 기기에서 데이터를 복호화할 때 필요하므로 패스프레이즈를 반드시 기억해 두세요.",
"Encrypt contents on the remote database. If you use the plugin's synchronization feature, enabling this is recommended.": "원격 데이터베이스의 내용을 암호화합니다. 플러그인의 동기화 기능을 사용하는 경우 활성화를 권장합니다.",
"Encrypting sensitive configuration items": "민감한 구성 항목 암호화",
"Encryption Algorithm": "암호화 알고리즘",
"Encryption phassphrase. If changed, you should overwrite the server's database with the new (encrypted) files.": "암호화에 사용할 패스프레이즈입니다. 변경한 경우, 새로 암호화된 파일로 서버의 데이터베이스를 덮어써야 합니다.",
"End-to-End Encryption": "종단 간 암호화",
"Endpoint URL": "엔드포인트 URL",
"Enhance chunk size": "청크 크기 확대",
"Enter a folder prefix (optional)": "폴더 접두사를 입력하세요 (선택 사항)",
"Enter Server Information": "서버 정보 입력",
"Enter Setup URI": "Setup URI 입력",
"Enter the server information manually": "서버 정보를 수동으로 입력",
"Enter TURN credential": "TURN 자격 증명을 입력하세요",
"Enter TURN username": "TURN 사용자 이름을 입력하세요",
"Enter your Access Key ID": "액세스 키 ID를 입력하세요",
"Enter your Bucket Name": "버킷 이름을 입력하세요",
"Enter your database name": "데이터베이스 이름을 입력하세요",
"Enter your JWT Key ID": "JWT 키 ID를 입력하세요",
"Enter your JWT secret or private key": "JWT 시크릿 또는 개인 키를 입력하세요",
"Enter your JWT Subject (CouchDB Username)": "JWT 주체(CouchDB 사용자 이름)를 입력하세요",
"Enter your passphrase": "패스프레이즈를 입력하세요",
"Enter your password": "비밀번호를 입력하세요",
"Enter your Region (e.g., us-east-1, auto for R2)": "리전을 입력하세요 (예: us-east-1, R2는 auto)",
"Enter your Secret Access Key": "시크릿 액세스 키를 입력하세요",
"Enter your username": "사용자 이름을 입력하세요",
"Error during connection test: ${reason}": "연결 테스트 중 오류가 발생했습니다: ${reason}",
"Error during testAndFixSettings: ${reason}": "testAndFixSettings 실행 중 오류가 발생했습니다: ${reason}",
"Experimental Settings": "실험적 설정",
"Export": "내보내기",
"Failed to connect to remote for compaction.": "압축 정리를 위해 원격 데이터베이스에 연결하지 못했습니다.",
"Failed to connect to remote for compaction. ${reason}": "압축 정리를 위해 원격 데이터베이스에 연결하지 못했습니다. ${reason}",
"Failed to connect to the server: ${reason}": "서버에 연결하지 못했습니다: ${reason}",
"Failed to connect to the server. Please check your settings.": "서버에 연결하지 못했습니다. 설정을 확인해 주세요.",
"Failed to connect to the signalling relay: ${reason}": "시그널링 중계 서버에 연결하지 못했습니다: ${reason}",
"Failed to create replicator instance.": "복제기 인스턴스를 생성하지 못했습니다.",
"Failed to parse Setup-URI.": "Setup-URI를 해석하지 못했습니다.",
"Failed to start one-shot replication before Garbage Collection. Garbage Collection Cancelled.": "가비지 컬렉션 전에 일회성 복제를 시작하지 못했습니다. 가비지 컬렉션을 취소합니다.",
"Failed to start replication after Garbage Collection.": "가비지 컬렉션 후 복제를 시작하지 못했습니다.",
"Failed:": "실패:",
"Fetch": "가져오기",
"Fetch chunks on demand": "필요 시 청크 원격 가져오기",
"Fetch database with previous behaviour": "이전 동작으로 데이터베이스 가져오기",
"Fetch remote settings": "원격 설정 가져오기",
"FETCHING": "가져오는 중",
"Fetching status...": "상태를 가져오는 중입니다...",
"File integrity": "파일 무결성",
"File to resolve conflict": "충돌을 해결할 파일",
"File to view History": "기록을 볼 파일",
"Filename": "파일명",
"Final Confirmation: Overwrite Server Data with This Device's Files": "최종 확인: 이 기기의 파일로 서버 데이터 덮어쓰기",
"First, please select the option that best describes your current situation.": "먼저 현재 상황에 가장 잘 맞는 항목을 선택해 주세요.",
"Fix": "수정",
"Flag and restart": "표시 후 재시작",
"Flagged Selective": "플래그 지정 선택적",
"Folder Prefix": "폴더 접두사",
"For HS256/HS512 algorithms, provide the shared secret key. For ES256/ES512 algorithms, provide the pkcs8 PEM-formatted private key.": "HS256/HS512 알고리즘에는 공유 시크릿 키를, ES256/ES512 알고리즘에는 pkcs8 PEM 형식의 개인 키를 입력하세요.",
"Forces the file to be synced when opened.": "파일을 열 때 강제로 동기화합니다.",
"Fresh Start Wipe": "초기화 후 새로 시작",
"Furthermore, if conflicts are already present in the server data, they will be synchronised to this device as they are, and you will need to resolve them locally.": "또한 서버 데이터에 이미 충돌이 존재한다면 그 상태 그대로 이 기기에 동기화되므로, 로컬에서 직접 해결해야 합니다.",
"Garbage Collection cancelled by user.": "사용자가 가비지 컬렉션을 취소했습니다.",
"Garbage Collection completed. Deleted chunks: ${deletedChunks} / ${totalChunks}. Time taken: ${seconds} seconds.": "가비지 컬렉션이 완료되었습니다. 삭제된 청크: ${deletedChunks} / ${totalChunks}. 소요 시간: ${seconds}초.",
"Garbage Collection Confirmation": "가비지 컬렉션 확인",
@@ -170,15 +294,33 @@
"Garbage Collection: Found ${unusedChunks} unused chunks to delete.": "가비지 컬렉션: 삭제할 미사용 청크 ${unusedChunks}개를 찾았습니다.",
"Garbage Collection: Scanned ${scanned} / ~${docCount}": "가비지 컬렉션: ${scanned} / ~${docCount} 검사함",
"Garbage Collection: Scanning completed. Total chunks: ${totalChunks}, Used chunks: ${usedChunks}": "가비지 컬렉션: 검사 완료. 전체 청크 수: ${totalChunks}, 사용 중인 청크 수: ${usedChunks}",
"Gathering information...": "정보를 수집하는 중입니다...",
"Generate Random ID": "임의 ID 생성",
"Group ID": "그룹 ID",
"Handle files as Case-Sensitive": "파일을 대소문자 구분으로 처리",
"Have you created a backup before proceeding?": "진행하기 전에 백업을 만드셨나요?",
"Hidden file synchronization have been temporarily disabled. Please enable them after the fetching, if you need them.": "숨김 파일 동기화가 일시적으로 비활성화되었습니다. 필요하다면 가져오기가 끝난 뒤에 다시 활성화해 주세요.",
"Hidden Files": "숨김 파일",
"Hide completely": "완전히 숨기기",
"Hide not applicable items": "해당 없는 항목 숨기기",
"Higher (${local} > ${remote})": "더 높음 (${local} > ${remote})",
"Highlight diff": "차이 강조",
"How to display network errors when the sync server is unreachable.": "동기화 서버에 연결할 수 없을 때 네트워크 오류를 어떻게 표시할지 설정합니다.",
"How would you like to configure the connection to your server?": "서버 연결을 어떻게 구성하시겠습니까?",
"However, This should not be enabled if you want to increase your secrecy more.": "다만 기밀성을 더 높이고 싶다면 이 옵션은 활성화하지 않는 것이 좋습니다.",
"I am adding a device to an existing synchronisation setup": "기존 동기화 구성에 기기를 추가합니다",
"I am setting this up for the first time": "처음으로 설정합니다",
"I am setting up a new server for the first time / I want to reset my existing server.": "새 서버를 처음 설정하거나, 기존 서버를 초기화하려고 합니다.",
"I am unable to create a backup of my Vault.": "보관함을 백업할 수 없습니다.",
"I am unable to create a backup of my Vaults.": "보관함들을 백업할 수 없습니다.",
"I have created a backup of my Vault.": "보관함을 백업했습니다.",
"I know my server details, let me enter them": "서버 정보를 알고 있으니 직접 입력하겠습니다",
"I understand that all changes made on other smartphones or computers possibly could be lost.": "다른 스마트폰이나 컴퓨터에서 변경한 내용이 모두 사라질 수 있다는 점을 이해합니다.",
"I understand that other devices will no longer be able to synchronise, and will need to be reset the synchronisation information.": "다른 기기는 더 이상 동기화할 수 없게 되며, 동기화 정보를 초기화해야 한다는 점을 이해합니다.",
"I understand that this action is irreversible once performed.": "이 작업은 한 번 수행하면 되돌릴 수 없다는 점을 이해합니다.",
"I understand the risks and will proceed without a backup.": "위험을 이해하며 백업 없이 진행하겠습니다.",
"I Understand, Overwrite Server": "이해했습니다, 서버 덮어쓰기",
"If \"Auto Start P2P Connection\" is enabled, the P2P connection will be started automatically when the plug-in launches.": "\"P2P 연결 자동 시작\"이 활성화되어 있으면 플러그인이 시작될 때 P2P 연결이 자동으로 시작됩니다.",
"If disabled(toggled), chunks will be split on the UI thread (Previous behaviour).": "비활성화(토글)되면 청크는 UI 스레드에서 분할됩니다 (이전 동작).",
"If enabled per-filed efficient customization sync will be used. We need a small migration when enabling this. And all devices should be updated to v0.23.18. Once we enabled this, we lost a compatibility with old versions.": "활성화하면 파일별 효율적인 사용자 설정 동기화가 사용됩니다. 이를 활성화할 때 소규모 데이터 구조 전환이 필요합니다. 모든 기기를 v0.23.18로 업데이트해야 합니다. 이를 활성화하면 이전 버전과의 호환성이 사라집니다.",
"If enabled, chunks will be split into no more than 100 items. However, dedupe is slightly weaker.": "활성화하면 청크는 최대 100개 항목으로 분할됩니다. 하지만 중복 제거 기능이 약간 약해집니다.",
@@ -191,16 +333,38 @@
"If this enabled, chunks will be split into semantically meaningful segments. Not all platforms support this feature.": "이 옵션을 활성화하면 청크가 문단이나 의미 단위로 나뉘어 저장됩니다. 단, 이 기능은 일부 플랫폼에서는 지원되지 않을 수 있습니다.",
"If this is set, changes to local files which are matched by the ignore files will be skipped. Remote changes are determined using local ignore files.": "이 옵션을 활성화하면, 제외 규칙 파일에 일치하는 로컬 파일의 변경 사항은 건너뜁니다. 원격 변경 여부 또한 로컬의 제외 규칙 파일에 따라 판단됩니다.",
"If this option is enabled, PouchDB will hold the connection open for 60 seconds, and if no change arrives in that time, close and reopen the socket, instead of holding it open indefinitely. Useful when a proxy limits request duration but can increase resource usage.": "이 옵션이 활성화되면 PouchDB는 연결을 더이상 무한히 열어두지 않고 60초 동안 유지합니다. 그 시간 내에 변경 사항이 없으면 소켓을 닫고 다시 엽니다. 프록시가 요청 지속 시간을 제한할 때 유용하지만 리소스 사용량이 증가할 수 있습니다.",
"If you cannot avoid CORS issues, you might want to try this option. It uses Obsidian's internal API to communicate with the CouchDB server. Not compliant with web standards, but works. Note that this might break in future Obsidian versions.": "CORS 문제를 피할 수 없다면 이 옵션을 시도해 볼 수 있습니다. Obsidian의 내부 API를 사용해 CouchDB 서버와 통신합니다. 웹 표준을 따르지는 않지만 동작합니다. 향후 Obsidian 버전에서 동작하지 않을 수 있다는 점에 유의하세요.",
"If you cannot avoid CORS issues, you might want to try this option. It uses Obsidian's internal API to communicate with the S3 server. Not compliant with web standards, but works. Note that this might break in future Obsidian versions.": "CORS 문제를 피할 수 없다면 이 옵션을 시도해 볼 수 있습니다. Obsidian의 내부 API를 사용해 S3 서버와 통신합니다. 웹 표준을 따르지는 않지만 동작합니다. 향후 Obsidian 버전에서 동작하지 않을 수 있다는 점에 유의하세요.",
"If you have unsynchronised changes in your Vault on this device, they will likely diverge from the server's versions after the reset. This may result in a large number of file conflicts.": "이 기기의 보관함에 동기화되지 않은 변경 사항이 있다면, 초기화 후 서버의 버전과 어긋나기 쉽습니다. 그 결과 많은 파일에서 충돌이 발생할 수 있습니다.",
"If you reached the payload size limit when using IBM Cloudant, please decrease batch size and batch limit to a lower value.": "IBM Cloudant를 사용하다가 페이로드 크기 제한에 도달했다면, 배치 크기와 배치 개수 제한을 더 낮은 값으로 줄여 주세요.",
"If you understand the risks and still wish to proceed, select so.": "위험을 이해하고도 진행하려면 그렇게 선택해 주세요.",
"If you want to store the data in a specific folder within the bucket, you can specify a folder prefix here. Otherwise, leave it blank to store data at the root of the bucket.": "버킷 안의 특정 폴더에 데이터를 저장하려면 여기에 폴더 접두사를 지정할 수 있습니다. 그렇지 않다면 비워 두어 버킷 최상위에 데이터를 저장하세요.",
"If you want to use `LiveSync`, you should broadcast changes. All `watching` peers which detects this will start the replication for fetching.": "`LiveSync`를 사용하려면 변경 사항을 브로드캐스트해야 합니다. 이를 감지한 모든 `watching` 상태의 피어가 가져오기를 위한 복제를 시작합니다.",
"Ignore": "무시",
"Ignore and Proceed": "무시하고 계속",
"Ignore files": "제외 규칙 파일",
"Ignore patterns": "무시 패턴",
"Import connection": "연결 가져오기",
"IMPORTANT": "중요",
"In most cases, you should stick with the default algorithm (${algorithm}), This setting is only required if you have an existing Vault encrypted in a different format.": "대부분의 경우 기본 알고리즘(${algorithm})을 그대로 사용하는 것이 좋습니다. 이 설정은 기존 보관함이 다른 형식으로 암호화되어 있는 경우에만 필요합니다.",
"In this scenario, Self-hosted LiveSync will recreate metadata for every file and deliberately generate conflicts. Where the file content is identical, these conflicts will be resolved automatically.": "이 경우 Self-hosted LiveSync는 모든 파일의 메타데이터를 다시 만들면서 의도적으로 충돌을 발생시킵니다. 파일 내용이 동일한 경우 이러한 충돌은 자동으로 해결됩니다.",
"Incoming:": "수신:",
"Incubate Chunks in Document": "문서 내 청크 임시 보관",
"Initial Action": "초기 작업",
"Initialise all journal history, On the next sync, every item will be received and sent.": "모든 저널 기록을 초기화합니다. 다음 동기화 때 모든 항목을 다시 받고 다시 보냅니다.",
"Initialise journal received history. On the next sync, every item except this device sent will be downloaded again.": "저널 수신 기록을 초기화합니다. 다음 동기화 때 이 기기가 보낸 항목을 제외한 모든 항목을 다시 내려받습니다.",
"Initialise journal sent history. On the next sync, every item except this device received will be sent again.": "저널 송신 기록을 초기화합니다. 다음 동기화 때 이 기기가 받은 항목을 제외한 모든 항목을 다시 보냅니다.",
"Interval (sec)": "간격 (초)",
"INVERTED": "반전됨",
"Issue detection log:": "문제 감지 로그:",
"It is strongly advised to create a backup before proceeding. Continuing without a backup may lead to data loss.": "진행하기 전에 백업을 만드시기를 강력히 권장합니다. 백업 없이 진행하면 데이터가 손실될 수 있습니다.",
"Just for a minute, please!": "잠시만 기다려 주세요!",
"JWT (JSON Web Token) authentication allows you to securely authenticate with the CouchDB server using tokens. Ensure that your CouchDB server is configured to accept JWTs and that the provided key and settings match the server's configuration. Incidentally, I have not verified it very thoroughly.": "JWT(JSON Web Token) 인증을 사용하면 토큰으로 CouchDB 서버에 안전하게 인증할 수 있습니다. CouchDB 서버가 JWT를 받아들이도록 설정되어 있는지, 그리고 입력한 키와 설정이 서버 구성과 일치하는지 확인해 주세요. 참고로 이 기능은 그리 철저하게 검증되지는 않았습니다.",
"JWT Algorithm": "JWT 알고리즘",
"JWT Expiration Duration (minutes)": "JWT 만료 시간(분)",
"JWT Key": "JWT 키",
"JWT Key ID (kid)": "JWT 키 ID (kid)",
"JWT Subject (sub)": "JWT 주체 (sub)",
"K.exp": "실험 기능",
"K.long_p2p_sync": "%{title_p2p_sync} (%{exp})",
"K.P2P": "%{Peer}-to-%{Peer}",
@@ -222,6 +386,7 @@
"lang-zh-tw": "繁體中文",
"Later": "나중에",
"Limit: {datetime} ({timestamp})": "제한: {datetime} ({timestamp})",
"live": "실시간",
"LiveSync could not handle multiple vaults which have same name without different prefix, This should be automatically configured.": "LiveSync는 접두사로 구분되지 않은 동일한 이름의 보관함을 여러 개 처리할 수 없습니다. 이 값은 자동으로 구성되어야 합니다.",
"liveSyncReplicator.beforeLiveSync": "LiveSync 전에 OneShot을 먼저 시작합니다...",
"liveSyncReplicator.cantReplicateLowerValue": "더 낮은 값으로 복제할 수 없습니다.",
@@ -250,6 +415,7 @@
"liveSyncSetting.valueShouldBeInRange": "값은 ${min} < 값 < ${max} 범위에 있어야 합니다",
"liveSyncSettings.btnApply": "적용",
"Local Database Tweak": "로컬 데이터베이스 조정",
"Local only": "로컬에만 있음",
"Lock": "잠금",
"Lock Server": "서버 잠금",
"Lock the remote server to prevent synchronization with other devices.": "다른 기기와의 동기화를 방지하기 위해 원격 서버를 잠급니다.",
@@ -258,6 +424,9 @@
"logPane.pause": "일시 정지",
"logPane.title": "Self-hosted LiveSync 로그",
"logPane.wrap": "줄 바꿈",
"Lower (${local} < ${remote})": "더 낮음 (${local} < ${remote})",
"Maintenance Commands": "유지보수 명령",
"Maintenance mode": "유지보수 모드",
"Maximum delay for batch database updating": "일괄 데이터베이스 업데이트 최대 지연",
"Maximum file size": "최대 파일 크기",
"Maximum Incubating Chunk Size": "임시 보관 청크의 최대 크기",
@@ -270,6 +439,7 @@
"Merge": "병합",
"Minimum delay for batch database updating": "일괄 데이터베이스 업데이트 최소 지연",
"Minimum interval for syncing": "동기화 최소 간격",
"Mixed": "혼합",
"moduleCheckRemoteSize.logCheckingStorageSizes": "스토리지 크기 확인 중",
"moduleCheckRemoteSize.logCurrentStorageSize": "원격 스토리지 크기: ${measuredSize}",
"moduleCheckRemoteSize.logExceededWarning": "원격 스토리지 크기: ${measuredSize}이(가) ${notifySize}을(를) 초과했습니다",
@@ -308,7 +478,6 @@
"moduleLiveSyncMain.titleScramEnabled": "긴급 정지 활성화됨",
"moduleLocalDatabase.logWaitingForReady": "준비 대기 중...",
"moduleLog.showLog": "로그 표시",
"moduleMigration.docUri": "https://github.com/vrtmrz/obsidian-livesync/blob/main/README.md#how-to-use",
"moduleMigration.fix0256.buttons.checkItLater": "나중에 확인",
"moduleMigration.fix0256.buttons.DismissForever": "이미 해결했으니 다시 묻지 않기",
"moduleMigration.fix0256.buttons.fix": "수정",
@@ -330,45 +499,51 @@
"moduleMigration.logRemoteTweakUnavailable": "원격 조정 값을 가져올 수 없습니다",
"moduleMigration.logSetupCancelled": "설정이 취소되었습니다. Self-hosted LiveSync가 설정을 기다리고 있습니다!",
"moduleMigration.msgFetchRemoteAgain": "이미 알고 계시겠지만, Self-hosted LiveSync의 기본 동작 방식과 데이터베이스 구조가 변경되었습니다.\n\n다행히도 여러분의 노력 덕분에 원격 데이터베이스는 이미 성공적으로 데이터 구조 전환이 완료된 것으로 보입니다. 축하드립니다!\n\n하지만 아직 일부 추가 작업이 필요합니다. 이 기기의 설정이 원격 데이터베이스와 호환되지 않으므로, 원격 데이터를 다시 가져와야 합니다. 지금 원격 데이터베이스를 다시 가져오시겠습니까?\n\n___참고: 설정이 변경되고 데이터베이스를 다시 불러오기 전까지는 동기화가 불가능합니다.___\n___참고2: 청크는 변경이 불가능한 구조이므로, 메타데이터와 차이점만 가져올 수 있습니다.___",
"moduleMigration.msgInitialSetup": "이 기기는 **아직 초기 설정이 완료되지 않았습니다**. 지금부터 설정 과정을 안내해 드리겠습니다.\n\n모든 대화 내용은 클립보드에 복사할 수 있습니다. 나중에 참고하려면 Obsidian 노트에 붙여넣거나 번역 도구를 활용해 번역하셔도 됩니다.\n\n먼저, **Setup URI**를 가지고 계신가요?\n\n참고: Setup URI가 무엇인지 잘 모르시겠다면 [문서](${URI_DOC})를 참고해 주세요.",
"moduleMigration.msgRecommendSetupUri": "Setup URI를 생성해 사용하는 것을 강력히 권장합니다.\nSetup URI가 무엇인지 잘 모르시겠다면 [문서](${URI_DOC})를 참고해 주세요. 중요한 내용이니 꼭 확인하시기 바랍니다.\n\n직접 수동 설정을 진행하시겠습니까?",
"moduleMigration.msgSinceV02321": "v0.23.21부터 Self-hosted LiveSync의 기본 동작 방식과 데이터베이스 구조가 변경되었습니다. 변경 내용은 다음과 같습니다:\n\n1. **파일명의 대소문자 구분**\n 이제 파일명을 대소문자 구분 없이 처리합니다. 파일명의 대소문자를 제대로 관리하지 못하는 Linux와 iOS를 제외한 대부분의 플랫폼에서 유리한 변경입니다.\n (해당 플랫폼에서는 이름이 같고 대소문자만 다른 파일에 대해 경고가 표시됩니다)\n\n2. **청크의 리비전 처리**\n 청크는 변경 불가능하므로 리비전을 고정할 수 있습니다. 이 변경으로 파일 저장 성능이 향상됩니다.\n\n___다만 이 변경 중 어느 하나라도 적용하려면 원격과 로컬 데이터베이스를 모두 재구축해야 합니다. 이 과정은 몇 분이 걸리므로 시간이 충분할 때 진행하시기를 권장합니다.___\n\n- 기존 동작을 유지하려면 `${KEEP}`을 선택해 이 과정을 건너뛸 수 있습니다.\n- 시간이 충분하지 않다면 `${DISMISS}`를 선택해 주세요. 나중에 다시 여쭤보겠습니다.\n- 다른 기기에서 이미 데이터베이스를 재구축했다면 `${DISMISS}`를 선택한 뒤 다시 동기화해 보세요. 차이가 감지되면 다시 안내해 드립니다.",
"moduleMigration.optionAdjustRemote": "원격에 맞추기",
"moduleMigration.optionDecideLater": "나중에 결정하기",
"moduleMigration.optionEnableBoth": "둘 다 활성화",
"moduleMigration.optionEnableFilenameCaseInsensitive": "#1만 활성화",
"moduleMigration.optionEnableFixedRevisionForChunks": "#2만 활성화",
"moduleMigration.optionHaveSetupUri": "예, 있습니다",
"moduleMigration.optionKeepPreviousBehaviour": "이전 동작 유지",
"moduleMigration.optionManualSetup": "모든 것을 수동으로 설정",
"moduleMigration.optionNoAskAgain": "아니요 (나중에 다시 물어보기)",
"moduleMigration.optionNoSetupUri": "아니요, 없습니다",
"moduleMigration.optionRemindNextLaunch": "다음 시작 시 알림",
"moduleMigration.optionSetupViaP2P": "%{short_p2p_sync}를 사용하여 설정",
"moduleMigration.optionSetupWizard": "설정 마법사로 안내",
"moduleMigration.optionYesFetchAgain": "예 (다시 가져오기)",
"moduleMigration.titleCaseSensitivity": "대소문자 구분",
"moduleMigration.titleRecommendSetupUri": "Setup URI 사용 권장",
"moduleMigration.titleWelcome": "Self-hosted LiveSync에 오신 것을 환영합니다",
"moduleObsidianMenu.replicate": "복제",
"More actions": "추가 작업",
"Mostly Complete: Decision Required": "거의 완료: 결정이 필요합니다",
"Move remotely deleted files to the trash, instead of deleting.": "원격에서 삭제된 파일을 삭제하는 대신 휴지통으로 이동합니다.",
"My remote server is already set up. I want to join this device.": "원격 서버가 이미 설정되어 있습니다. 이 기기를 참여시키고 싶습니다.",
"Name": "이름",
"Network warning style": "네트워크 경고 표시 방식",
"NEW": "신규",
"New Remote": "새 원격",
"Newer (${diff})": "더 새로움 (${diff})",
"No checks have been performed yet.": "아직 검사를 수행하지 않았습니다.",
"No connected device information found. Cancelling Garbage Collection.": "연결된 기기 정보를 찾을 수 없습니다. 가비지 컬렉션을 취소합니다.",
"No Connection": "연결 없음",
"No devices available. Waiting for other devices to connect...": "사용 가능한 기기가 없습니다. 다른 기기가 연결되기를 기다리는 중입니다...",
"No Items.": "항목이 없습니다.",
"No limit configured": "제한이 설정되지 않음",
"NO PREVIEW": "미리 보기 없음",
"No, please take me back": "아니요, 이전으로 돌아가겠습니다",
"Node ID": "노드 ID",
"Node Information Missing": "노드 정보 누락",
"Non-Synchronising files": "동기화하지 않는 파일",
"Normal Files": "일반 파일",
"Not all messages have been translated. And, please revert to \"Default\" when reporting errors.": "모든 메시지가 번역되지 않았습니다. 오류 신고 시 \"기본값\"으로 되돌려 주세요.",
"Not configured": "구성되지 않음",
"Not now": "나중에",
"Note that the Group ID is not limited to the generated format; you can use any string as the Group ID.": "그룹 ID는 생성된 형식으로 제한되지 않으며, 어떤 문자열이든 그룹 ID로 사용할 수 있습니다.",
"Note that you can generate a new Setup URI by running the \"Copy settings as a new Setup URI\" command in the command palette.": "명령 팔레트에서 \"Copy settings as a new Setup URI\" 명령을 실행하면 새 Setup URI를 생성할 수 있습니다.",
"Notify all setting files": "모든 설정 파일 알림",
"Notify customized": "사용자 설정 알림",
"Notify when other device has newly customized.": "다른 기기에서 새로운 사용자 설정이 있을 때 알림을 받습니다.",
"Notify when the estimated remote storage size exceeds on start up": "시작 시 예상 원격 스토리지 크기가 초과되면 알림",
"Number of batches to process at a time. Defaults to 40. Minimum is 2. This along with batch size controls how many docs are kept in memory at a time.": "한 번에 처리할 배치 개수입니다. 기본값은 40이고 최소값은 2입니다. 배치 크기와 함께 한 번에 메모리에 보관되는 문서 수를 결정합니다.",
"Number of changes to sync at a time. Defaults to 50. Minimum is 2.": "한 번에 동기화할 변경 사항의 수입니다. 기본값은 50입니다. 최소값은 2입니다.",
"Obfuscate Properties": "속성 난독화",
"Obfuscating properties (e.g., path of file, size, creation and modification dates) adds an additional layer of security by making it harder to identify the structure and names of your files and folders on the remote server. This helps protect your privacy and makes it more difficult for unauthorized users to infer information about your data.": "속성(예: 파일 경로, 크기, 생성 및 수정 날짜)을 난독화하면 원격 서버에서 파일과 폴더의 구조 및 이름을 파악하기 어려워져 보안이 한층 강화됩니다. 이는 개인 정보를 보호하는 데 도움이 되며, 권한 없는 사용자가 데이터에 대한 정보를 추측하기 어렵게 만듭니다.",
"Obsidian version": "Obsidian 버전",
"obsidianLiveSyncSettingTab.btnApply": "적용",
"obsidianLiveSyncSettingTab.btnCheck": "확인",
@@ -378,12 +553,10 @@
"obsidianLiveSyncSettingTab.btnEnable": "활성화",
"obsidianLiveSyncSettingTab.btnFix": "수정",
"obsidianLiveSyncSettingTab.btnGotItAndUpdated": "알겠습니다. 업데이트했습니다.",
"obsidianLiveSyncSettingTab.btnNext": "다음",
"obsidianLiveSyncSettingTab.btnStart": "시작",
"obsidianLiveSyncSettingTab.btnTest": "테스트",
"obsidianLiveSyncSettingTab.btnUse": "사용",
"obsidianLiveSyncSettingTab.buttonFetch": "가져오기",
"obsidianLiveSyncSettingTab.buttonNext": "다음",
"obsidianLiveSyncSettingTab.defaultLanguage": "기본값",
"obsidianLiveSyncSettingTab.descConnectSetupURI": "이것은 Setup URI로 Self-hosted LiveSync를 설정하는 권장 방법입니다.",
"obsidianLiveSyncSettingTab.descCopySetupURI": "새 기기 설정에 완벽합니다!",
@@ -447,7 +620,6 @@
"obsidianLiveSyncSettingTab.msgEnableCorsChttpd": "chttpd.enable_cors 설정",
"obsidianLiveSyncSettingTab.msgEnableEncryptionRecommendation": "종단 간 암호화와 경로 난독화를 활성화하는 것을 권장합니다. 정말로 암호화 없이 계속하시겠습니까?",
"obsidianLiveSyncSettingTab.msgFetchConfigFromRemote": "원격 서버에서 구성을 가져오시겠습니까?",
"obsidianLiveSyncSettingTab.msgGenerateSetupURI": "모든 작업이 완료되었습니다! 다른 기기를 설정하기 위해 Setup URI를 생성하시겠습니까?",
"obsidianLiveSyncSettingTab.msgIfConfigNotPersistent": "서버 설정이 영구적으로 저장되지 않는 환경(예: Docker에서 실행 중)에서는 이곳의 값들이 변경될 수 있습니다. 연결이 가능해지면 서버의 local.ini 파일에서 설정을 수동으로 업데이트해 주세요.",
"obsidianLiveSyncSettingTab.msgInvalidPassphrase": "암호화 패스프레이즈가 유효하지 않을 수 있습니다. 정말로 계속하시겠습니까?",
"obsidianLiveSyncSettingTab.msgNewVersionNote": "업그레이드 알림으로 여기에 오셨나요? 버전 기록을 검토해 주세요. 만족하신다면 버튼을 클릭하세요. 새로운 업데이트 시 다시 안내됩니다.",
@@ -457,7 +629,6 @@
"obsidianLiveSyncSettingTab.msgObjectStorageWarning": "경고: 이 기능은 아직 개발 중이므로 다음 사항을 유의해 주세요:\n- 추가 전용 구조로 동작합니다. 저장 용량을 줄이려면 재구축이 필요합니다.\n- 다소 불안정합니다.\n- 최초 동기화 시 모든 기록이 원격에서 전송됩니다. 데이터 사용량 제한과 느린 속도에 유의해 주세요.\n- 실시간 동기화는 변경분만 처리합니다.\n\n문제가 발생했거나 이 기능에 대한 아이디어가 있다면 GitHub에 이슈를 등록해 주세요.\n큰 관심에 깊이 감사드립니다.",
"obsidianLiveSyncSettingTab.msgOriginCheck": "출처 확인: ${org}",
"obsidianLiveSyncSettingTab.msgRebuildRequired": "변경 사항을 적용하려면 데이터베이스를 재구축해야 합니다. 변경 사항을 적용할 방법을 선택해 주세요.\n\n<details>\n<summary>범례</summary>\n\n| 기호 | 의미 |\n|: ------ :| ------- |\n| ⇔ | 최신 상태 |\n| ⇄ | 양쪽을 맞추는 동기화 |\n| ⇐,⇒ | 덮어쓰기 전송 |\n| ⇠,⇢ | 반대편에서 덮어쓰기 전송 |\n\n</details>\n\n## ${OPTION_REBUILD_BOTH}\n한눈에 보기: 📄 ⇒¹ 💻 ⇒² 🛰️ ⇢ⁿ 💻 ⇄ⁿ⁺¹ 📄\n이 기기의 기존 파일을 사용해 로컬과 원격 데이터베이스를 모두 재구축합니다.\n이 경우 다른 기기는 잠기며, 가져오기를 수행해야 합니다.\n## ${OPTION_FETCH}\n한눈에 보기: 📄 ⇄² 💻 ⇐¹ 🛰️ ⇔ 💻 ⇔ 📄\n로컬 데이터베이스를 초기화한 뒤, 원격 데이터베이스에서 가져온 데이터로 재구축합니다.\n원격 데이터베이스를 이미 재구축한 경우도 여기에 해당합니다.\n## ${OPTION_ONLY_SETTING}\n설정만 저장합니다. **주의: 데이터가 손상될 수 있습니다.** 일반적으로는 데이터베이스 재구축이 필요합니다.",
"obsidianLiveSyncSettingTab.msgSelectAndApplyPreset": "마법사를 완료하려면 프리셋 항목을 선택하고 적용해 주세요.",
"obsidianLiveSyncSettingTab.msgSetCorsCredentials": "cors.credentials 설정",
"obsidianLiveSyncSettingTab.msgSetCorsOrigins": "cors.origins 설정",
"obsidianLiveSyncSettingTab.msgSetMaxDocSize": "couchdb.max_document_size 설정",
@@ -514,7 +685,6 @@
"obsidianLiveSyncSettingTab.titleActiveRemoteServer": "활성 원격 서버",
"obsidianLiveSyncSettingTab.titleAppearance": "모양",
"obsidianLiveSyncSettingTab.titleConflictResolution": "충돌 해결",
"obsidianLiveSyncSettingTab.titleCongratulations": "축하합니다!",
"obsidianLiveSyncSettingTab.titleCouchDB": "CouchDB",
"obsidianLiveSyncSettingTab.titleDeletionPropagation": "삭제 전파",
"obsidianLiveSyncSettingTab.titleEncryptionNotEnabled": "암호화가 활성화되지 않음",
@@ -541,16 +711,29 @@
"obsidianLiveSyncSettingTab.titleUpdateThinning": "업데이트 솎아내기",
"obsidianLiveSyncSettingTab.warnCorsOriginUnmatched": "⚠ CORS 출처가 일치하지 않습니다 ${from}->${to}",
"obsidianLiveSyncSettingTab.warnNoAdmin": "⚠ 관리자 권한이 없습니다.",
"Of course, we can back up the data before proceeding.": "물론 진행하기 전에 데이터를 백업할 수 있습니다.",
"Off": "꺼짐",
"Ok": "확인",
"Old Algorithm": "이전 알고리즘",
"Older (${diff})": "더 오래됨 (${diff})",
"Older fallback (Slow, W/O WebAssembly)": "이전 대체 방식 (느림, WebAssembly 없음)",
"On": "켜짐",
"On the source device, from the command palette, run the 'Show settings as a QR code' command.": "원본 기기에서 명령 팔레트를 열고 'Show settings as a QR code' 명령을 실행합니다.",
"On the source device, open Obsidian.": "원본 기기에서 Obsidian을 엽니다.",
"On this device, please keep this Vault open.": "이 기기에서는 이 보관함을 계속 열어 두세요.",
"On this device, switch to the camera app or use a QR code scanner to scan the displayed QR code.": "이 기기에서 카메라 앱으로 전환하거나 QR 코드 스캐너를 사용해 화면에 표시된 QR 코드를 스캔합니다.",
"Open": "열기",
"Open connection": "연결 열기",
"Open P2P Setup...": "P2P 설정 열기...",
"Open the dialog": "대화상자 열기",
"Other files": "기타 파일",
"Overwrite": "덮어쓰기",
"Overwrite patterns": "덮어쓰기 패턴",
"Overwrite remote": "원격 덮어쓰기",
"Overwrite remote with local DB and passphrase.": "로컬 DB와 패스프레이즈로 원격을 덮어씁니다.",
"Overwrite Server Data with This Device's Files": "이 기기의 파일로 서버 데이터를 덮어쓰기",
"P2P Configuration": "P2P 구성",
"P2P Status": "P2P 상태",
"P2P.AskPassphraseForDecrypt": "원격 피어가 구성을 공유했습니다. 구성을 복호화하려면 패스프레이즈를 입력해 주세요.",
"P2P.AskPassphraseForShare": "원격 피어가 이 기기의 구성을 요청했습니다. 구성을 공유하려면 패스프레이즈를 입력해 주세요. 이 대화상자를 취소하여 요청을 무시할 수 있습니다.",
"P2P.DisabledButNeed": "%{title_p2p_sync}가 비활성화되어 있습니다. 정말로 활성화하시겠습니까?",
@@ -574,36 +757,59 @@
"paneMaintenance.remoteLockedResolvedDevice": "원격 데이터베이스가 잠겨 있지만 이 기기는 이미 승인되었습니다.",
"paneMaintenance.unlockDatabaseReady": "데이터베이스 잠금 해제",
"Passphrase": "패스프레이즈",
"Passphrase is required.": "패스프레이즈가 필요합니다.",
"Passphrase of sensitive configuration items": "민감한 구성 항목의 패스프레이즈",
"password": "비밀번호",
"Password": "비밀번호",
"Paste a connection string": "연결 문자열 붙여넣기",
"Paste the Setup URI generated from one of your active devices.": "사용 중인 기기 중 하나에서 생성한 Setup URI를 붙여 넣으세요.",
"Path": "경로",
"Path Obfuscation": "경로 난독화",
"Patterns to match files for overwriting instead of merging": "병합 대신 덮어쓸 파일을 판별하는 패턴",
"Patterns to match files for syncing": "동기화할 파일을 판별하는 패턴",
"Peer ID:": "피어 ID:",
"Peer to Peer Replicator": "Peer to Peer 복제기",
"Peer-to-Peer only": "Peer-to-Peer 전용",
"Peer-to-Peer Synchronisation": "피어 투 피어(P2P) 동기화",
"Peers": "피어",
"Per-file-saved customization sync": "파일별 저장 사용자 설정 동기화",
"Perform": "실행",
"Perform cleanup": "정리 실행",
"Perform Garbage Collection": "가비지 컬렉션 실행",
"Perform Garbage Collection to remove unused chunks and reduce database size.": "사용하지 않는 청크를 제거하고 데이터베이스 크기를 줄이기 위해 가비지 컬렉션을 실행합니다.",
"Periodic Sync interval": "주기적 동기화 간격",
"PERMANENT": "영구",
"Pick a file to resolve conflict": "충돌을 해결할 파일 선택",
"Pick a file to show history": "기록을 표시할 파일 선택",
"Please be aware that the End-to-End Encryption passphrase is not validated until the synchronisation process actually commences. This is a security measure designed to protect your data.": "종단 간 암호화 패스프레이즈는 동기화가 실제로 시작되기 전까지 검증되지 않는다는 점에 유의해 주세요. 이는 데이터를 보호하기 위한 보안 조치입니다.",
"Please configure your end-to-end encryption settings.": "종단 간 암호화 설정을 구성해 주세요.",
"Please disable 'Read chunks online' in settings to use Garbage Collection.": "가비지 컬렉션을 사용하려면 설정에서 'Read chunks online'을 비활성화해 주세요.",
"Please enable 'Compute revisions for chunks' in settings to use Garbage Collection.": "가비지 컬렉션을 사용하려면 설정에서 'Compute revisions for chunks'를 활성화해 주세요.",
"Please enter the CouchDB server information below.": "아래에 CouchDB 서버 정보를 입력해 주세요.",
"Please enter the details required to connect to your S3/MinIO/R2 compatible object storage service.": "S3/MinIO/R2 호환 오브젝트 스토리지 서비스에 연결하는 데 필요한 정보를 입력해 주세요.",
"Please enter the Peer-to-Peer Synchronisation information below.": "아래에 Peer-to-Peer 동기화 정보를 입력해 주세요.",
"Please enter the Setup URI that was generated during server installation or on another device, along with the vault passphrase.": "서버 설치 과정이나 다른 기기에서 생성된 Setup URI를 보관함 패스프레이즈와 함께 입력해 주세요.",
"Please follow the steps below to import settings from your existing device.": "기존 기기에서 설정을 가져오려면 아래 단계를 따라 주세요.",
"PLEASE NOTE": "유의 사항",
"Please select 'Cancel' explicitly to cancel this operation.": "이 작업을 취소하려면 반드시 \"취소\"를 명시적으로 선택해 주세요.",
"Please select a method to import the settings from another device.": "다른 기기에서 설정을 가져올 방법을 선택해 주세요.",
"Please select an active P2P remote configuration to change P2P sync targets.": "P2P 동기화 대상을 변경하려면 활성 P2P 원격 구성을 선택해 주세요.",
"Please select an option to proceed": "계속하려면 항목을 선택해 주세요",
"Please select the button below to restart and proceed to the data fetching confirmation.": "아래 버튼을 선택하면 재시작 후 데이터 가져오기 확인 단계로 진행합니다.",
"Please select the button below to restart and proceed to the final confirmation.": "아래 버튼을 선택하면 재시작 후 최종 확인 단계로 진행합니다.",
"Please select the type of server to which you are connecting.": "연결할 서버 유형을 선택해 주세요.",
"Please select your situation.": "현재 상황을 선택해 주세요.",
"Please set device name to identify this device. This name should be unique among your devices. While not configured, we cannot enable this feature.": "이 기기를 식별할 기기 이름을 설정해 주세요. 이 이름은 기기 간에 고유해야 합니다. 설정하기 전까지는 이 기능을 활성화할 수 없습니다.",
"Please set this device name": "이 기기의 이름을 설정해 주세요",
"Please understand that this is intended behaviour.": "이는 의도된 동작이라는 점을 이해해 주세요.",
"Plug-in version": "플러그인 버전",
"Plugins": "플러그인",
"Prepare the 'report' to create an issue": "이슈 생성을 위한 '보고서' 준비",
"Presets": "프리셋",
"Prevent fetching configuration from server": "서버에서 구성 가져오기 방지",
"Proceed": "진행",
"Proceed Garbage Collection": "가비지 컬렉션 계속",
"Proceed to the next step.": "다음 단계로 진행합니다.",
"Proceed with Setup URI": "Setup URI로 계속",
"Proceeding with Garbage Collection, ignoring missing nodes.": "누락된 노드를 무시하고 가비지 컬렉션을 계속 진행합니다.",
"Proceeding with Garbage Collection.": "가비지 컬렉션을 진행합니다.",
@@ -629,15 +835,22 @@
"RedFlag.FetchRemoteConfig.Title": "원격 구성 가져오기",
"Reduces storage space by discarding all non-latest revisions. This requires the same amount of free space on the remote server and the local client.": "최신 버전이 아닌 모든 리비전을 제거하여 저장 공간을 줄입니다. 이 작업을 수행하려면 원격 서버와 로컬 클라이언트에 동일한 양의 여유 공간이 필요합니다.",
"Reducing the frequency with which on-disk changes are reflected into the DB": "디스크 변경 사항이 데이터베이스에 반영되는 빈도를 줄입니다",
"Refresh": "새로 고침",
"Region": "지역",
"Relay settings": "중계 서버 설정",
"Reload": "다시 불러오기",
"Remediation": "복구 조치",
"Remediation Setting Changed": "복구 설정이 변경됨",
"Remote Database Tweak (In sunset)": "원격 데이터베이스 조정 (폐기 예정)",
"Remote Databases": "원격 데이터베이스",
"Remote name": "원격 이름",
"Remote only": "원격에만 있음",
"Remote server type": "원격 서버 유형",
"Remote Type": "원격 유형",
"Rename": "이름 바꾸기",
"Replicate now": "지금 복제",
"Replicating": "복제 중",
"Replicating...": "복제 중입니다...",
"Replicator.Dialogue.Locked.Action.Dismiss": "재확인을 위해 취소",
"Replicator.Dialogue.Locked.Action.Fetch": "이 기기의 동기화 재설정",
"Replicator.Dialogue.Locked.Action.Unlock": "원격 데이터베이스 잠금 해제",
@@ -660,6 +873,7 @@
"Reset": "재설정",
"Reset all": "모두 재설정",
"Reset all journal counter": "모든 저널 카운터 재설정",
"Reset and Resume Synchronisation": "동기화 초기화 후 재개",
"Reset journal received history": "저널 수신 기록 재설정",
"Reset journal sent history": "저널 송신 기록 재설정",
"Reset notification threshold and check the remote database usage": "알림 임계값을 초기화하고 원격 데이터베이스 사용량 확인",
@@ -672,14 +886,26 @@
"Resolve all conflicted files": "충돌한 모든 파일 해결",
"Resolve All conflicted files by the newer one": "충돌한 모든 파일을 최신 버전으로 해결",
"Resolve all conflicted files by the newer one. Caution: This will overwrite the older one, and cannot resurrect the overwritten one.": "충돌한 모든 파일을 더 최신 버전으로 해결합니다. 주의: 이전 버전은 덮어써지며 복원할 수 없습니다.",
"Restart and Fetch Data": "재시작 후 데이터 가져오기",
"Restart and Initialise Server": "재시작 후 서버 초기화",
"Restart Now": "지금 재시작",
"Restarting Obsidian is strongly recommended. Until restart, some changes may not take effect, and display may be inconsistent. Are you sure to restart now?": "Obsidian을 재시작하는 것을 강력히 권장합니다. 재시작하기 전까지는 일부 변경 사항이 적용되지 않거나 화면이 일관되지 않게 표시될 수 있습니다. 지금 재시작하시겠습니까?",
"Restore or reconstruct local database from remote.": "원격에서 로컬 데이터베이스를 복원하거나 재구축합니다.",
"Rev": "리비전",
"Revert changes": "변경 사항 되돌리기",
"Revoke": "철회",
"Room ID": "룸 ID",
"Room ID suffix:": "룸 ID 접미사:",
"Run Doctor": "진단 실행",
"S3/MinIO/R2 Configuration": "S3/MinIO/R2 구성",
"S3/MinIO/R2 Object Storage": "S3/MinIO/R2 객체 스토리지",
"Same": "동일",
"Same or local only": "동일하거나 로컬에만 있음",
"Save and Apply": "저장 후 적용",
"Save settings to a markdown file. You will be notified when new settings arrive. You can set different files by the platform.": "설정을 마크다운 파일에 저장합니다. 새로운 설정이 도착하면 알림을 받게 됩니다. 플랫폼별로 다른 파일을 설정할 수 있습니다.",
"Saving will be performed forcefully after this number of seconds.": "이 시간(초) 후에 강제로 저장이 수행됩니다.",
"Scan a QR Code (Recommended for mobile)": "QR 코드 스캔(모바일 권장)",
"Scan changes": "변경 사항 검사",
"Scan changes on customization sync": "사용자 설정 동기화 시 변경 사항 검색",
"Scan customization automatically": "사용자 설정 자동 검색",
"Scan customization before replicating.": "복제하기 전에 사용자 설정을 검색합니다.",
@@ -688,17 +914,28 @@
"Scan for Broken files": "손상된 파일 검사",
"Scan for hidden files before replication": "복제 전 숨김 파일 검사",
"Scan hidden files periodically": "주기적으로 숨김 파일 검사",
"Scan QR Code": "QR 코드 스캔",
"Scan the QR code displayed on an active device using this device's camera.": "이 기기의 카메라로 사용 중인 기기에 표시된 QR 코드를 스캔하세요.",
"Schedule and Restart": "예약 후 재시작",
"Scram Switches": "긴급 정지 스위치",
"Scram!": "긴급 정지",
"Seconds, 0 to disable": "초 단위, 0으로 설정하면 비활성화",
"Seconds. Saving to the local database will be delayed until this value after we stop typing or saving.": "초 단위입니다. 타이핑이나 저장을 중단한 후 이 시간동안 로컬 데이터베이스 저장이 지연됩니다.",
"Secret Access Key": "시크릿 액세스 키",
"Secret Key": "시크릿 키",
"Select active P2P remote": "활성 P2P 원격 선택",
"Select All Shiny": "새 항목 모두 선택",
"Select Flagged Shiny": "플래그된 새 항목 선택",
"Select P2P remote...": "P2P 원격 선택...",
"Select the database adapter to use.": "사용할 데이터베이스 어댑터를 선택합니다.",
"Selecting this option will result in the current data on this device being used to initialise the server. Any existing data on the server will be completely overwritten.": "이 옵션을 선택하면 이 기기의 현재 데이터로 서버를 초기화합니다. 서버에 있는 기존 데이터는 모두 완전히 덮어써집니다.",
"Selecting this option will result in this device joining the existing server. You need to fetching the existing synchronisation data from the server to this device.": "이 옵션을 선택하면 이 기기가 기존 서버에 참여합니다. 서버에 있는 기존 동기화 데이터를 이 기기로 가져와야 합니다.",
"Selective": "선택적",
"Send": "보내기",
"Send chunks": "청크 보내기",
"SENDING": "전송 중",
"Server URI": "서버 URI",
"SESSION": "세션",
"Setting.GenerateKeyPair.Desc": "키 페어를 생성했습니다!\n\n참고: 이 키 페어는 다시 표시되지 않습니다. 안전한 곳에 저장해 주세요. 분실하면 새 키 페어를 생성해야 합니다.\n참고 2: 공개 키는 spki 형식이고, 개인 키는 pkcs8 형식입니다. 편의를 위해 공개 키의 줄 바꿈은 `\\n`으로 변환됩니다.\n참고 3: 공개 키는 원격 데이터베이스에, 개인 키는 로컬 기기에 설정해야 합니다.\n\n>[!FOR YOUR EYES ONLY]-\n> <div class=\"sls-keypair\">\n>\n> ### 공개 키\n> ```\n${public_key}\n> ```\n>\n> ### 개인 키\n> ```\n${private_key}\n> ```\n>\n> </div>\n\n>[!Both for copying]-\n>\n> <div class=\"sls-keypair\">\n>\n> ```\n${public_key}\n${private_key}\n> ```\n>\n> </div>\n\n",
"Setting.GenerateKeyPair.Title": "새 키 페어가 생성되었습니다!",
"Setting.TroubleShooting": "문제 해결",
@@ -707,7 +944,10 @@
"Setting.TroubleShooting.ScanBrokenFiles": "손상된 파일 검사",
"Setting.TroubleShooting.ScanBrokenFiles.Desc": "데이터베이스에 올바르게 저장되지 않은 파일을 검사합니다.",
"SettingTab.Message.AskRebuild": "변경 사항을 적용하려면 원격 데이터베이스에서 가져와야 합니다. 계속 진행하시겠습니까?",
"Setup Complete: Preparing to Fetch Synchronisation Data": "설정 완료: 동기화 데이터를 가져올 준비 중",
"Setup Complete: Preparing to Initialise Server": "설정 완료: 서버를 초기화할 준비 중",
"Setup URI dialog cancelled.": "Setup URI 대화 상자가 취소되었습니다.",
"Setup-URI": "Setup-URI",
"Setup.Apply.Buttons.ApplyAndFetch": "적용 후 가져오기",
"Setup.Apply.Buttons.ApplyAndMerge": "적용 후 병합",
"Setup.Apply.Buttons.ApplyAndRebuild": "적용 후 재구축",
@@ -777,16 +1017,29 @@
"Show status inside the editor": "편집기 내부에 상태 표시",
"Show status on the status bar": "상태 표시줄에 상태 표시",
"Show verbose log. Please enable if you report an issue.": "자세한 로그를 표시합니다. 문제를 신고하는 경우 활성화해 주세요.",
"Signaling Server Connection": "시그널링 서버 연결",
"Signalling Status": "시그널링 상태",
"Skip and close": "건너뛰고 닫기",
"Snippets": "스니펫",
"Some devices have differing progress values (max: ${maxProgress}, min: ${minProgress}).\nThis may indicate that some devices have not completed synchronisation, which could lead to conflicts. Strongly recommend confirming that all devices are synchronised before proceeding.": "일부 기기의 진행 값이 다릅니다(최대: ${maxProgress}, 최소: ${minProgress}).\n이는 일부 기기가 동기화를 완료하지 않았음을 의미할 수 있으며, 충돌로 이어질 수 있습니다. 계속 진행하기 전에 모든 기기가 동기화되었는지 반드시 확인하는 것을 강력히 권장합니다.",
"Start Broadcasting": "브로드캐스트 시작",
"Start change-broadcasting on Connect": "연결 시 변경 사항 브로드캐스트 시작",
"Start Sync & Close": "동기화 시작 후 닫기",
"Starts synchronisation when a file is saved.": "파일이 저장될 때 동기화를 시작합니다.",
"Stat": "상태",
"Stats": "통계",
"Stop ⚡": "중지 ⚡",
"Stop Broadcasting": "브로드캐스트 중지",
"Stop reflecting database changes to storage files.": "데이터베이스 변경 사항을 스토리지 파일에 반영하는 것을 중단합니다.",
"Stop watching for file changes.": "파일 변경 사항 감시를 중단합니다.",
"Storage -> Database": "스토리지 -> 데이터베이스",
"Strongly Recommended": "적극 권장",
"Suppress notification of hidden files change": "숨김 파일 변경 알림 억제",
"Suspend database reflecting": "데이터베이스 반영 일시 중단",
"Suspend file watching": "파일 감시 일시 중단",
"Switch to IDB": "IDB로 전환",
"Switch to IndexedDB": "IndexedDB로 전환",
"Sync": "동기화",
"Sync after merging file": "파일 병합 후 동기화",
"Sync automatically after merging files": "파일 병합 후 자동으로 동기화",
"Sync Mode": "동기화 모드",
@@ -794,25 +1047,56 @@
"Sync on File Open": "파일 열기 시 동기화",
"Sync on Save": "저장 시 동기화",
"Sync on Startup": "시작 시 동기화",
"Sync once": "한 번 동기화",
"Synchronisation utilising journal files. You must have set up an S3/MinIO/R2 compatible object storage.": "저널 파일을 활용하는 동기화 방식입니다. S3/MinIO/R2 호환 객체 스토리지를 미리 구성해 두어야 합니다.",
"Synchronising files": "동기화할 파일",
"Syncing": "동기화",
"Syncing...": "동기화 중입니다...",
"Target patterns": "대상 패턴",
"Test Settings and Continue": "설정 테스트 후 계속",
"Testing only - Resolve file conflicts by syncing newer copies of the file, this can overwrite modified files. Be Warned.": "테스트 전용 - 파일의 새로운 사본을 동기화하여 파일 충돌을 해결하며, 수정된 파일을 덮어쓸 수 있습니다. 주의하세요.",
"The connection to the server has been configured successfully. As the next step,": "서버 연결이 성공적으로 구성되었습니다. 다음 단계로,",
"The delay for consecutive on-demand fetches": "연속 청크 요청 간 대기 시간",
"The files in this Vault are almost identical to the server's.": "이 보관함의 파일은 서버의 파일과 거의 동일합니다.",
"The following accepted nodes are missing its node information:\n- ${missingNodes}\n\nThis indicates that they have not been connected for some time or have been left on an older version.\nIt is preferable to update all devices if possible. If you have any devices that are no longer in use, you can clear all accepted nodes by locking the remote once.": "다음 승인된 노드에는 노드 정보가 없습니다:\n- ${missingNodes}\n\n이는 해당 노드가 한동안 연결되지 않았거나 이전 버전에 머물러 있음을 의미합니다.\n가능하다면 먼저 모든 기기를 업데이트하는 것이 좋습니다. 더 이상 사용하지 않는 기기가 있다면 원격을 한 번 잠가 승인된 노드를 모두 정리할 수 있습니다.",
"The Group ID and passphrase are used to identify your group of devices. Make sure to use the same Group ID and passphrase on all devices you want to synchronise.": "그룹 ID와 패스프레이즈는 기기 그룹을 식별하는 데 사용됩니다. 동기화하려는 모든 기기에서 동일한 그룹 ID와 패스프레이즈를 사용해야 합니다.",
"The Hash algorithm for chunk IDs": "청크 ID용 해시 알고리즘",
"The IndexedDB adapter often offers superior performance in certain scenarios, but it has been found to cause memory leaks when used with LiveSync mode. When using LiveSync mode, please use IDB adapter instead.": "IndexedDB 어댑터는 특정 상황에서 더 나은 성능을 보이는 경우가 많지만, LiveSync 모드에서 사용하면 메모리 누수를 일으키는 것으로 확인되었습니다. LiveSync 모드를 사용할 때는 IDB 어댑터를 사용해 주세요.",
"the latest synchronisation data will be downloaded from the server to this device.": "서버의 최신 동기화 데이터를 이 기기로 내려받습니다.",
"the local database, that is to say the synchronisation information, must be reconstituted.": "로컬 데이터베이스, 즉 동기화 정보를 다시 구성해야 합니다.",
"The maximum duration for which chunks can be incubated within the document. Chunks exceeding this period will graduate to independent chunks.": "청크를 문서 안에 임시 보관할 수 있는 최대 기간입니다. 이 기간을 넘긴 청크는 독립된 청크로 분리됩니다.",
"The maximum number of chunks that can be incubated within the document. Chunks exceeding this number will immediately graduate to independent chunks.": "문서 안에 임시 보관할 수 있는 청크의 최대 개수입니다. 이 개수를 넘긴 청크는 즉시 독립된 청크로 분리됩니다.",
"The maximum total size of chunks that can be incubated within the document. Chunks exceeding this size will immediately graduate to independent chunks.": "문서 안에 임시 보관할 수 있는 청크의 최대 총 크기입니다. 이 크기를 넘긴 청크는 즉시 독립된 청크로 분리됩니다.",
"The minimum interval for automatic synchronisation on event.": "이벤트 발생 시 자동 동기화의 최소 간격입니다.",
"The remote is already set up, and the configuration is compatible (or got compatible by this operation).": "원격은 이미 설정되어 있으며, 구성도 호환됩니다(또는 이번 작업으로 호환되었습니다).",
"The Setup-URI does not appear to be valid. Please check that you have copied it correctly.": "Setup-URI가 유효하지 않은 것으로 보입니다. 올바르게 복사했는지 확인해 주세요.",
"The Setup-URI is valid and ready to use.": "Setup-URI가 유효하며 사용할 준비가 되었습니다.",
"the single, authoritative master copy": "유일하고 확실한 원본이 됩니다",
"the synchronisation data on the server will be built based on the current data on this device.": "이 기기의 현재 데이터를 바탕으로 서버의 동기화 데이터를 구축합니다.",
"Themes": "테마",
"There is a way to resolve this on other devices.": "다른 기기에서 이를 해결할 방법이 있습니다.",
"There may be differences between the files in this Vault and the server.": "이 보관함의 파일과 서버의 파일 사이에 차이가 있을 수 있습니다.",
"Therefore, we ask that you exercise extreme caution when configuring server information manually. If an incorrect passphrase is entered, the data on the server will become corrupted.": "따라서 서버 정보를 수동으로 구성할 때에는 각별히 주의해 주시기 바랍니다. 잘못된 패스프레이즈를 입력하면 서버의 데이터가 손상됩니다.",
"This can isolate your connections between devices. Use the same Room ID for the same devices.": "이를 통해 기기 간 연결을 서로 격리할 수 있습니다. 같은 기기들끼리는 동일한 룸 ID를 사용하세요.",
"This device": "이 기기",
"This device name": "이 기기의 이름",
"This feature enables direct synchronisation between devices. No server is required, but both devices must be online at the same time for synchronisation to occur, and some features may be limited. Internet connection is only required to signalling (detecting peers) and not for data transfer.": "기기 간에 직접 동기화하는 기능입니다. 서버는 필요 없지만 동기화가 이루어지려면 두 기기가 동시에 온라인 상태여야 하며, 일부 기능은 제한될 수 있습니다. 인터넷 연결은 시그널링(피어 탐지)에만 필요하고 데이터 전송에는 필요하지 않습니다.",
"This is an advanced option for users who do not have a URI or who wish to configure detailed settings.": "URI가 없거나 세부 설정을 직접 구성하려는 사용자를 위한 고급 옵션입니다.",
"This is an extremely powerful operation. We strongly recommend that you copy your Vault folder to a safe location.": "이는 매우 강력한 작업입니다. 보관함 폴더를 안전한 위치에 복사해 두시기를 강력히 권장합니다.",
"This is the most suitable synchronisation method for the design. All functions are available. You must have set up a CouchDB instance.": "이 설계에 가장 적합한 동기화 방식입니다. 모든 기능을 사용할 수 있습니다. CouchDB 인스턴스를 미리 구성해 두어야 합니다.",
"This passphrase will not be copied to another device. It will be set to `Default` until you configure it again.": "이 패스프레이즈는 다른 기기로 복사되지 않습니다. 다시 구성할 때까지 `기본값`으로 설정됩니다.",
"This password is used to encrypt the connection. Use something long enough.": "이 비밀번호는 연결을 암호화하는 데 사용됩니다. 충분히 긴 값을 사용하세요.",
"This procedure will first delete all existing synchronisation data from the server. Following this, the server data will be completely rebuilt, using the current state of your Vault on this device (including its local database) as": "이 절차는 먼저 서버에서 기존 동기화 데이터를 모두 삭제합니다. 그다음 이 기기에 있는 보관함의 현재 상태(로컬 데이터베이스 포함)를 바탕으로 서버 데이터를 완전히 재구축합니다. 즉, 이 기기의 데이터가",
"This setting must be the same even when connecting to multiple synchronisation destinations.": "이 설정은 여러 동기화 대상에 연결하는 경우에도 동일해야 합니다.",
"This Vault is empty, or contains only new files that are not on the server.": "이 보관함은 비어 있거나, 서버에 없는 새 파일만 포함하고 있습니다.",
"This will rebuild the local database on this device using the most recent data from the server. This action is designed to resolve synchronisation inconsistencies and restore correct functionality.": "서버의 최신 데이터를 사용해 이 기기의 로컬 데이터베이스를 재구축합니다. 이 작업은 동기화 불일치를 해결하고 정상적인 동작을 복구하기 위한 것입니다.",
"This will recreate chunks for all files. If there were missing chunks, this may fix the errors.": "모든 파일의 청크를 다시 생성합니다. 누락된 청크가 있었다면 이 작업으로 오류가 해결될 수 있습니다.",
"To minimise the creation of new conflicts": "새로운 충돌 발생을 최소화하기 위해",
"Transfer Tweak": "전송 조정",
"TURN Credential": "TURN 자격 증명",
"TURN server settings are only necessary if you are behind a strict NAT or firewall that prevents direct P2P connections. In most cases, you can leave these fields blank.": "TURN 서버 설정은 직접적인 P2P 연결을 막는 엄격한 NAT나 방화벽 뒤에 있는 경우에만 필요합니다. 대부분의 경우 이 항목들은 비워 두어도 됩니다.",
"TURN Server URLs (comma-separated)": "TURN 서버 URL (쉼표로 구분)",
"TURN Username": "TURN 사용자 이름",
"TweakMismatchResolve.Action.DisableAutoAcceptCompatible": "자동 수용 비활성화",
"TweakMismatchResolve.Action.Dismiss": "무시",
"TweakMismatchResolve.Action.EnableAutoAcceptCompatible": "자동 수용 활성화",
@@ -1105,21 +1389,36 @@
"Ui.SetupWizard.SetupRemote.ProceedP2P": "Peer-to-Peer 전용 설정으로 계속",
"Ui.SetupWizard.SetupRemote.Title": "서버 정보 입력",
"Unique name between all synchronized devices. To edit this setting, please disable customization sync once.": "모든 동기화된 기기 간 고유 이름입니다. 이 설정을 편집하려면 사용자 설정 동기화를 한 번 비활성화해 주세요.",
"Unless you are certain, selecting this options is bit dangerous. It assumes that the server configuration is compatible with this device. If this is not the case, data loss may occur. Please ensure you know what you are doing.": "확신이 없다면 이 옵션을 선택하는 것은 다소 위험합니다. 이 옵션은 서버 구성이 이 기기와 호환된다고 가정합니다. 그렇지 않은 경우 데이터가 손실될 수 있습니다. 무엇을 하려는지 충분히 이해한 상태에서 진행해 주세요.",
"Updating list...": "목록을 갱신하는 중입니다...",
"URL": "URL",
"Use a custom passphrase": "사용자 지정 패스프레이즈 사용",
"Use a Setup URI (Recommended)": "Setup URI 사용(권장)",
"Use Custom HTTP Handler": "커스텀 HTTP 핸들러 사용",
"Use Diagnostic RTCPeerConnection for statistics": "통계에 진단용 RTCPeerConnection 사용",
"Use dynamic iteration count": "동적 반복 횟수 사용",
"Use internal API": "내부 API 사용",
"Use Internal API": "내부 API 사용",
"Use JWT Authentication": "JWT 인증 사용",
"Use Path-Style Access": "경로 방식(Path-Style) 접근 사용",
"Use Random Number": "임의의 숫자 사용",
"Use Segmented-splitter": "의미 기반 분할 사용",
"Use splitting-limit-capped chunk splitter": "분할 제한 상한 청크 분할기 사용",
"Use the trash bin": "휴지통 사용",
"Use timeouts instead of heartbeats": "하트비트 대신 타임아웃 사용",
"Use vrtmrz's relay": "vrtmrz의 중계 서버 사용",
"username": "사용자명",
"Username": "사용자명",
"Verbose Log": "자세한 로그",
"Verify all": "모두 검증",
"Verify and repair all files": "모든 파일 검증 및 복구",
"Warning! This will have a serious impact on performance. And the logs will not be synchronised under the default name. Please be careful with logs; they often contain your confidential information.": "경고! 이는 성능에 심각한 영향을 미칩니다. 로그는 기본 이름으로 동기화되지 않습니다. 로그에는 종종 기밀 정보가 포함되어 있으므로 주의해 주세요.",
"WATCHING": "감시 중",
"We can not use \"/\" to the device name": "기기 이름에는 \"/\"를 사용할 수 없습니다",
"We can use only Secure (HTTPS) connections on Obsidian Mobile.": "Obsidian 모바일에서는 보안(HTTPS) 연결만 사용할 수 있습니다.",
"We cannot change the device name while this feature is enabled. Please disable this feature to change the device name.": "이 기능이 활성화되어 있는 동안에는 기기 이름을 변경할 수 없습니다. 기기 이름을 변경하려면 이 기능을 비활성화하세요.",
"We have to configure the device name": "기기 이름을 설정해야 합니다",
"We recommend that you copy your Vault folder to a safe location. This will provide a safeguard in case a large number of conflicts arise, or if you accidentally synchronise with an incorrect destination.": "보관함 폴더를 안전한 위치에 복사해 두시기를 권장합니다. 충돌이 대량으로 발생하거나 실수로 잘못된 대상과 동기화한 경우에 대비할 수 있습니다.",
"We will now guide you through a few questions to simplify the synchronisation setup.": "동기화 설정을 간단히 마칠 수 있도록 몇 가지 질문으로 안내해 드리겠습니다.",
"We will now proceed with the server configuration.": "이제 서버 구성을 진행하겠습니다.",
"Welcome to Self-hosted LiveSync": "Self-hosted LiveSync에 오신 것을 환영합니다",
@@ -1130,5 +1429,8 @@
"xxhash64 (Fastest)": "xxhash64 (가장 빠름)",
"Yes, I want to add this device to my existing synchronisation": "예, 이 기기를 기존 동기화 구성에 추가하겠습니다",
"Yes, I want to set up a new synchronisation": "예, 새 동기화를 설정하겠습니다",
"You are adding this device to an existing synchronisation setup.": "이 기기를 기존 동기화 구성에 추가합니다."
"You are adding this device to an existing synchronisation setup.": "이 기기를 기존 동기화 구성에 추가합니다.",
"You can configure in the Obsidian Plugin Settings.": "Obsidian 플러그인 설정에서 구성할 수 있습니다.",
"You should create a new synchronisation destination and rebuild your data there.": "새로운 동기화 대상을 만들고 그곳에서 데이터를 재구축해야 합니다.",
"You should perform this operation only in exceptional circumstances, such as when the server data is completely corrupted, when changes on all other devices are no longer needed, or when the database size has become unusually large in comparison to the Vault size.": "이 작업은 서버 데이터가 완전히 손상된 경우, 다른 모든 기기의 변경 사항이 더 이상 필요하지 않은 경우, 또는 데이터베이스 크기가 보관함 크기에 비해 비정상적으로 커진 경우처럼 예외적인 상황에서만 수행해야 합니다."
}
-16
View File
@@ -306,7 +306,6 @@
"moduleLiveSyncMain.titleScramEnabled": "Экстренная остановка включена",
"moduleLocalDatabase.logWaitingForReady": "Ожидание готовности...",
"moduleLog.showLog": "Показать лог",
"moduleMigration.docUri": "https://github.com/vrtmrz/obsidian-livesync/blob/main/README.md#how-to-use",
"moduleMigration.fix0256.buttons.checkItLater": "Проверить позже",
"moduleMigration.fix0256.buttons.DismissForever": "Исправлено, больше не спрашивать",
"moduleMigration.fix0256.buttons.fix": "Исправить",
@@ -328,26 +327,16 @@
"moduleMigration.logRemoteTweakUnavailable": "Не удалось получить удалённые настройки",
"moduleMigration.logSetupCancelled": "Настройка отменена, Self-hosted LiveSync ожидает вашей настройки!",
"moduleMigration.msgFetchRemoteAgain": "Удалённая база данных, похоже, уже была мигрирована. Конфигурация этого устройства несовместима.",
"moduleMigration.msgInitialSetup": "Ваше устройство ещё не настроено. У вас есть Setup URI?",
"moduleMigration.msgRecommendSetupUri": "Мы рекомендуем сгенерировать Setup URI.",
"moduleMigration.msgSinceV02321": "Начиная с v0.23.21, self-hosted LiveSync изменил поведение и структуру базы данных.",
"moduleMigration.optionAdjustRemote": "Настроить под удалённую",
"moduleMigration.optionDecideLater": "Решить позже",
"moduleMigration.optionEnableBoth": "Включить оба",
"moduleMigration.optionEnableFilenameCaseInsensitive": "Включить только #1",
"moduleMigration.optionEnableFixedRevisionForChunks": "Включить только #2",
"moduleMigration.optionHaveSetupUri": "Да, есть",
"moduleMigration.optionKeepPreviousBehaviour": "Сохранить предыдущее поведение",
"moduleMigration.optionManualSetup": "Настроить всё вручную",
"moduleMigration.optionNoAskAgain": "Нет, спросить снова",
"moduleMigration.optionNoSetupUri": "Нет, нет",
"moduleMigration.optionRemindNextLaunch": "Напомнить при следующем запуске",
"moduleMigration.optionSetupViaP2P": "Использовать short_p2p_sync для настройки",
"moduleMigration.optionSetupWizard": "Перейти в мастер настройки",
"moduleMigration.optionYesFetchAgain": "Да, загрузить снова",
"moduleMigration.titleCaseSensitivity": "Чувствительность к регистру",
"moduleMigration.titleRecommendSetupUri": "Рекомендация использовать Setup URI",
"moduleMigration.titleWelcome": "Добро пожаловать в Self-hosted LiveSync",
"moduleObsidianMenu.replicate": "Реплицировать",
"More actions": "Другие действия",
"Move remotely deleted files to the trash, instead of deleting.": "Перемещать удалённые на удалённом сервере файлы в корзину вместо удаления.",
@@ -377,12 +366,10 @@
"obsidianLiveSyncSettingTab.btnEnable": "Включить",
"obsidianLiveSyncSettingTab.btnFix": "Исправить",
"obsidianLiveSyncSettingTab.btnGotItAndUpdated": "Понял и обновил.",
"obsidianLiveSyncSettingTab.btnNext": "Далее",
"obsidianLiveSyncSettingTab.btnStart": "Старт",
"obsidianLiveSyncSettingTab.btnTest": "Тест",
"obsidianLiveSyncSettingTab.btnUse": "Использовать",
"obsidianLiveSyncSettingTab.buttonFetch": "Загрузить",
"obsidianLiveSyncSettingTab.buttonNext": "Далее",
"obsidianLiveSyncSettingTab.defaultLanguage": "По умолчанию",
"obsidianLiveSyncSettingTab.descConnectSetupURI": "Это рекомендуемый способ настройки Self-hosted LiveSync с помощью Setup URI.",
"obsidianLiveSyncSettingTab.descCopySetupURI": "Идеально подходит для настройки нового устройства!",
@@ -445,7 +432,6 @@
"obsidianLiveSyncSettingTab.msgEnableCorsChttpd": "Установить chttpd.enable_cors",
"obsidianLiveSyncSettingTab.msgEnableEncryptionRecommendation": "Мы рекомендуем включить сквозное шифрование. Вы уверены, что хотите продолжить без шифрования?",
"obsidianLiveSyncSettingTab.msgFetchConfigFromRemote": "Вы хотите загрузить конфигурацию с удалённого сервера?",
"obsidianLiveSyncSettingTab.msgGenerateSetupURI": "Всё готово! Вы хотите сгенерировать Setup URI для настройки других устройств?",
"obsidianLiveSyncSettingTab.msgIfConfigNotPersistent": "Если конфигурация сервера непостоянна, значения здесь могут измениться.",
"obsidianLiveSyncSettingTab.msgInvalidPassphrase": "Ваша парольная фраза шифрования может быть недействительна.",
"obsidianLiveSyncSettingTab.msgNewVersionNote": "Вы пришли из-за уведомления об обновлении? Просмотрите историю версий.",
@@ -455,7 +441,6 @@
"obsidianLiveSyncSettingTab.msgObjectStorageWarning": "ПРЕДУПРЕЖДЕНИЕ: Эта функция в разработке.",
"obsidianLiveSyncSettingTab.msgOriginCheck": "Проверка origin: org",
"obsidianLiveSyncSettingTab.msgRebuildRequired": "Требуется перестроение баз данных для применения изменений.",
"obsidianLiveSyncSettingTab.msgSelectAndApplyPreset": "Выберите и примените любой пресет для завершения мастера.",
"obsidianLiveSyncSettingTab.msgSetCorsCredentials": "Установить cors.credentials",
"obsidianLiveSyncSettingTab.msgSetCorsOrigins": "Установить cors.origins",
"obsidianLiveSyncSettingTab.msgSetMaxDocSize": "Установить couchdb.max_document_size",
@@ -512,7 +497,6 @@
"obsidianLiveSyncSettingTab.titleActiveRemoteServer": "Активный удалённый сервер",
"obsidianLiveSyncSettingTab.titleAppearance": "Внешний вид",
"obsidianLiveSyncSettingTab.titleConflictResolution": "Разрешение конфликтов",
"obsidianLiveSyncSettingTab.titleCongratulations": "Поздравляем!",
"obsidianLiveSyncSettingTab.titleCouchDB": "Сервер CouchDB",
"obsidianLiveSyncSettingTab.titleDeletionPropagation": "Распространение удалений",
"obsidianLiveSyncSettingTab.titleEncryptionNotEnabled": "Шифрование не включено",
File diff suppressed because it is too large Load Diff
-16
View File
@@ -293,7 +293,6 @@
"moduleLiveSyncMain.titleScramEnabled": "紧急停止已启用",
"moduleLocalDatabase.logWaitingForReady": "等待就绪...",
"moduleLog.showLog": "显示日志",
"moduleMigration.docUri": "https://github.com/vrtmrz/obsidian-livesync/blob/main/docs/zh/README_zh.md#%E5%A6%82%E4%BD%95%E4%BD%BF%E7%94%A8",
"moduleMigration.fix0256.buttons.checkItLater": "稍后检查",
"moduleMigration.fix0256.buttons.DismissForever": "我已经修复了,不再询问",
"moduleMigration.fix0256.buttons.fix": "修复",
@@ -315,26 +314,16 @@
"moduleMigration.logRemoteTweakUnavailable": "无法获取远程调整值",
"moduleMigration.logSetupCancelled": "设置已取消,Self-hosted LiveSync 正在等待您的设置!",
"moduleMigration.msgFetchRemoteAgain": "您可能已经知道,Self-hosted LiveSync 更改了其默认行为和数据库结构。\n\n值得庆幸的是,在您的时间和努力下,远程数据库似乎已经迁移完成。恭喜!\n\n但是,我们还需要一点点操作。此设备的配置与远程数据库不兼容。我们需要再次从远程数据库获取。我们现在应该再次从远程获取吗?\n\n___注意:在更改配置并再次获取数据库之前,我们无法进行同步。___\n___注意2:chunks 是完全不可变的,我们只能获取元数据和差异",
"moduleMigration.msgInitialSetup": "您的设备**尚未设置**。让我引导您完成设置过程。\n\n请记住,每个对话框内容都可以复制到剪贴板。如果以后需要参考,可以将其粘贴到 Obsidian 的笔记中。您也可以使用翻译工具将其翻译成您的语言。\n\n首先,您有**设置 URI** 吗?\n\n注意:如果您不知道这是什么,请参阅[文档](${URI_DOC})",
"moduleMigration.msgRecommendSetupUri": "我们强烈建议您生成一个设置 URI 并使用它。\n如果您对此不了解,请参阅[文档](${URI_DOC})(再次抱歉,但这很重要)。\n\n您想如何手动设置?",
"moduleMigration.msgSinceV02321": "自 v0.23.21 起,Self-hosted LiveSync 更改了默认行为和数据库结构。进行了以下更改:\n\n1. **文件名的区分大小写**\n现在处理文件名时不区分大小写。这对于大多数平台来说是一个有益的更改,除了 Linux 和 iOS,它们不能有效地管理文件名的大小写敏感性。\n(在这些平台上,对于名称相同但大小写不同的文件将显示警告)。\n\n2. **chunks 的版本处理**\nchunks 是不可变的,这使得它们的版本可以固定。此更改将提高文件保存的性能。\n\n___然而,要启用这些更改中的任何一个,都需要重建远程和本地数据库。这个过程需要几分钟,我们建议您在有充足时间时进行。___\n\n- 如果您希望保持以前的行为,可以使用 `${KEEP}` 跳过此过程。\n- 如果您没有足够的时间,请选择 `${DISMISS}`。稍后会再次提示您。\n- 如果您已在另一台设备上重建了数据库,请选择 `${DISMISS}` 并尝试再次同步。由于检测到差异,系统会再次提示您",
"moduleMigration.optionAdjustRemote": "调整到远程设置",
"moduleMigration.optionDecideLater": "稍后决定",
"moduleMigration.optionEnableBoth": "启用两者",
"moduleMigration.optionEnableFilenameCaseInsensitive": "仅启用 #1",
"moduleMigration.optionEnableFixedRevisionForChunks": "仅启用 #2",
"moduleMigration.optionHaveSetupUri": "是的,我有",
"moduleMigration.optionKeepPreviousBehaviour": "保持以前的行为",
"moduleMigration.optionManualSetup": "全部手动设置",
"moduleMigration.optionNoAskAgain": "不,请稍后再次询问",
"moduleMigration.optionNoSetupUri": "不,我没有",
"moduleMigration.optionRemindNextLaunch": "下次启动时提醒我",
"moduleMigration.optionSetupViaP2P": "Use %{short_p2p_sync} to set up",
"moduleMigration.optionSetupWizard": "带我进入设置向导",
"moduleMigration.optionYesFetchAgain": "是的,再次获取",
"moduleMigration.titleCaseSensitivity": "大小写敏感性",
"moduleMigration.titleRecommendSetupUri": "推荐使用设置 URI",
"moduleMigration.titleWelcome": "欢迎使用 Self-hosted LiveSync",
"moduleObsidianMenu.replicate": "复制",
"More actions": "更多操作",
"Move remotely deleted files to the trash, instead of deleting.": "将远程删除的文件移至回收站,而不是直接删除",
@@ -363,12 +352,10 @@
"obsidianLiveSyncSettingTab.btnEnable": "启用",
"obsidianLiveSyncSettingTab.btnFix": "修复",
"obsidianLiveSyncSettingTab.btnGotItAndUpdated": "我明白了并且已更新",
"obsidianLiveSyncSettingTab.btnNext": "下一步",
"obsidianLiveSyncSettingTab.btnStart": "开始",
"obsidianLiveSyncSettingTab.btnTest": "测试",
"obsidianLiveSyncSettingTab.btnUse": "使用",
"obsidianLiveSyncSettingTab.buttonFetch": "获取",
"obsidianLiveSyncSettingTab.buttonNext": "下一步",
"obsidianLiveSyncSettingTab.defaultLanguage": "默认语言",
"obsidianLiveSyncSettingTab.descConnectSetupURI": "这是使用设置 URI 设置 Self-hosted LiveSync 的推荐方法",
"obsidianLiveSyncSettingTab.descCopySetupURI": "非常适合设置新设备!",
@@ -431,7 +418,6 @@
"obsidianLiveSyncSettingTab.msgEnableCorsChttpd": "设置 chttpd.enable_cors",
"obsidianLiveSyncSettingTab.msgEnableEncryptionRecommendation": "建议启用端到端加密和路径混淆。你确定要在未加密的情况下继续吗?",
"obsidianLiveSyncSettingTab.msgFetchConfigFromRemote": "要从远端服务器获取配置吗?",
"obsidianLiveSyncSettingTab.msgGenerateSetupURI": "全部完成!要生成设置 URI 以便配置其他设备吗?",
"obsidianLiveSyncSettingTab.msgIfConfigNotPersistent": "如果服务器配置不是持久的(例如,在 docker 上运行),此处的值可能会更改。一旦能够连接,请更新服务器 local.ini 中的设置",
"obsidianLiveSyncSettingTab.msgInvalidPassphrase": "你的加密密码短语可能无效。你确定要继续吗?",
"obsidianLiveSyncSettingTab.msgNewVersionNote": "因为升级通知来到这里?请查看版本历史。如果您满意,请点击按钮。新的更新将再次提示此信息",
@@ -441,7 +427,6 @@
"obsidianLiveSyncSettingTab.msgObjectStorageWarning": "警告:此功能仍在开发中,请注意以下几点:\n- 仅追加架构。需要重建才能缩小存储空间。\n- 有点脆弱。\n- 首次同步时,所有历史记录将从远程传输。注意数据上限和慢速。\n- 只有差异会实时同步。\n\n如果您遇到任何问题,或对此功能有任何想法,请在 GitHub 上创建 issue。\n感谢您的巨大贡献",
"obsidianLiveSyncSettingTab.msgOriginCheck": "源检查: {org}",
"obsidianLiveSyncSettingTab.msgRebuildRequired": "需要重建数据库以应用更改。请选择应用更改的方法。\n\n<details>\n<summary>图例</summary>\n\n| 符号 | 含义 |\n|: ------ :| ------- |\n| ⇔ | 最新 |\n| ⇄ | 同步以平衡 |\n| ⇐,⇒ | 传输以覆盖 |\n| ⇠,⇢ | 从另一侧传输以覆盖 |\n\n</details>\n\n## ${OPTION_REBUILD_BOTH}\n概览:📄 ⇒¹ 💻 ⇒² 🛰️ ⇢ⁿ 💻 ⇄ⁿ⁺¹ 📄\n使用此设备的现有文件重建本地和远程数据库。\n这将导致其他设备被锁定,并且它们需要执行获取操作。\n## ${OPTION_FETCH}\n概览:📄 ⇄² 💻 ⇐¹ 🛰️ ⇔ 💻 ⇔ 📄\n初始化本地数据库并使用从远程数据库获取的数据重建它。\n这种情况包括您已经重建了远程数据库的情况。\n## ${OPTION_ONLY_SETTING}\n仅存储设置。**注意:这可能导致数据损坏**;通常需要重建数据库",
"obsidianLiveSyncSettingTab.msgSelectAndApplyPreset": "请选择并应用任一预设项以完成向导。",
"obsidianLiveSyncSettingTab.msgSetCorsCredentials": "设置 cors.credentials",
"obsidianLiveSyncSettingTab.msgSetCorsOrigins": "设置 cors.origins",
"obsidianLiveSyncSettingTab.msgSetMaxDocSize": "设置 couchdb.max_document_size",
@@ -498,7 +483,6 @@
"obsidianLiveSyncSettingTab.titleActiveRemoteServer": "活动远程服务器",
"obsidianLiveSyncSettingTab.titleAppearance": "外观",
"obsidianLiveSyncSettingTab.titleConflictResolution": "冲突处理",
"obsidianLiveSyncSettingTab.titleCongratulations": "恭喜!",
"obsidianLiveSyncSettingTab.titleCouchDB": "CouchDB 服务器",
"obsidianLiveSyncSettingTab.titleDeletionPropagation": "删除传播",
"obsidianLiveSyncSettingTab.titleEncryptionNotEnabled": "尚未启用加密",
-7
View File
@@ -92,8 +92,6 @@ Normal Files: Normale Dateien
obsidianLiveSyncSettingTab:
btnApply: Anwenden
btnDisable: Deaktivieren
btnNext: Weiter
buttonNext: Weiter
defaultLanguage: Standardsprache
labelDisabled: "⏹️ : Deaktiviert"
labelEnabled: "🔁 : Aktiviert"
@@ -106,12 +104,8 @@ obsidianLiveSyncSettingTab:
und Pfadverschleierung zu aktivieren. Möchten Sie wirklich ohne
Verschlüsselung fortfahren?
msgFetchConfigFromRemote: Möchten Sie die Konfiguration vom Remote-Server abrufen?
msgGenerateSetupURI: Alles fertig! Möchten Sie eine Setup-URI erzeugen, um
andere Geräte einzurichten?
msgInvalidPassphrase: Ihre Verschlüsselungs-Passphrase könnte ungültig sein.
Möchten Sie wirklich fortfahren?
msgSelectAndApplyPreset: Bitte wählen und übernehmen Sie eine beliebige
Voreinstellung, um den Assistenten abzuschließen.
nameDisableHiddenFileSync: Synchronisation versteckter Dateien deaktivieren
nameEnableHiddenFileSync: Synchronisation versteckter Dateien aktivieren
nameHiddenFileSynchronization: Synchronisation versteckter Dateien
@@ -122,7 +116,6 @@ obsidianLiveSyncSettingTab:
optionPeriodicWithBatch: Periodisch mit Stapelverarbeitung
titleAppearance: Darstellung
titleConflictResolution: Konfliktbehandlung
titleCongratulations: Glückwunsch!
titleCouchDB: CouchDB-Server
titleDeletionPropagation: Weitergabe von Löschungen
titleEncryptionNotEnabled: Verschlüsselung ist nicht aktiviert
+33 -41
View File
@@ -725,7 +725,6 @@ moduleLocalDatabase:
moduleLog:
showLog: Show Log
moduleMigration:
docUri: https://github.com/vrtmrz/obsidian-livesync/blob/main/README.md#how-to-use
fix0256:
buttons:
checkItLater: Check it later
@@ -819,30 +818,6 @@ moduleMigration:
___Note2: The chunks are completely immutable, we can fetch only the
metadata and difference.___
msgInitialSetup: >-
Your device has **not been set up yet**. Let me guide you through the setup
process.
Please keep in mind that every dialogue content can be copied to the
clipboard. If you need to refer to it later, you can paste it into a note in
Obsidian. You can also translate it into your language using a translation
tool.
First, do you have **Setup URI**?
Note: If you do not know what it is, please refer to the
[documentation](${URI_DOC}).
msgRecommendSetupUri: >-
We strongly recommend that you generate a set-up URI and use it.
If you do not have knowledge about it, please refer to the
[documentation](${URI_DOC}) (Sorry again, but it is important).
How do you want to set it up manually?
msgSinceV02321: >-
Since v0.23.21, the self-hosted LiveSync has changed the default behaviour
and database structure. The following changes have been made:
@@ -874,18 +849,10 @@ moduleMigration:
optionEnableBoth: Enable both
optionEnableFilenameCaseInsensitive: "Enable only #1"
optionEnableFixedRevisionForChunks: "Enable only #2"
optionHaveSetupUri: Yes, I have
optionKeepPreviousBehaviour: Keep previous behaviour
optionManualSetup: Set it up all manually
optionNoAskAgain: No, please ask again
optionNoSetupUri: No, I do not have
optionRemindNextLaunch: Remind me at the next launch
optionSetupViaP2P: Use %{short_p2p_sync} to set up
optionSetupWizard: Take me into the setup wizard
optionYesFetchAgain: Yes, fetch again
titleCaseSensitivity: Case Sensitivity
titleRecommendSetupUri: Recommendation to use Setup URI
titleWelcome: Welcome to Self-hosted LiveSync
moduleObsidianMenu:
replicate: Replicate
More actions: More actions
@@ -940,12 +907,10 @@ obsidianLiveSyncSettingTab:
btnEnable: Enable
btnFix: Fix
btnGotItAndUpdated: I got it and updated.
btnNext: Next
btnStart: Start
btnTest: Test
btnUse: Use
buttonFetch: Fetch
buttonNext: Next
defaultLanguage: Default
descConnectSetupURI: This is the recommended method to set up Self-hosted
LiveSync with a Setup URI.
@@ -1018,7 +983,6 @@ obsidianLiveSyncSettingTab:
msgEnableEncryptionRecommendation: We recommend enabling End-To-End Encryption,
and Path Obfuscation. Are you sure you want to continue without encryption?
msgFetchConfigFromRemote: Do you want to fetch the config from the remote server?
msgGenerateSetupURI: All done! Do you want to generate a setup URI to set up other devices?
msgIfConfigNotPersistent: If the server configuration is not persistent (e.g.,
running on docker), the values here may change. Once you are able to
connect, please update the settings in the server's local.ini.
@@ -1098,7 +1062,6 @@ obsidianLiveSyncSettingTab:
Store only the settings. **Caution: This may lead to data corruption**;
database reconstruction is generally necessary.
msgSelectAndApplyPreset: Please select and apply any preset item to complete the wizard.
msgSetCorsCredentials: Set cors.credentials
msgSetCorsOrigins: Set cors.origins
msgSetMaxDocSize: Set couchdb.max_document_size
@@ -1158,12 +1121,17 @@ obsidianLiveSyncSettingTab:
titleActiveRemoteServer: Active Remote Server
titleAppearance: Appearance
titleConflictResolution: Conflict resolution
titleCongratulations: Congratulations!
titleCouchDB: CouchDB
titleDeletionPropagation: Deletion Propagation
titleEncryptionNotEnabled: Encryption is not enabled
titleEncryptionPassphraseInvalid: Encryption Passphrase Invalid
titleExtraFeatures: Enable extra and advanced features
titleExtraFeaturesGroup: Extra features
titleExtraMenus: Extra menus
titleHelpAndInformation: Help and information
titleHelpAndTroubleshooting: Help and troubleshooting
titleMaintenanceAndRecovery: Maintenance and recovery
titleAdvancedSettings: Advanced settings
titleFetchConfig: Fetch Config
titleFetchConfigFromRemote: Fetch config from remote server
titleFetchSettings: Fetch Settings
@@ -1177,7 +1145,8 @@ obsidianLiveSyncSettingTab:
titleRemoteConfigCheckFailed: Remote Configuration Check Failed
titleRemoteServer: Remote Server
titleReset: Reset
titleSetupOtherDevices: To setup other devices
titleSetupOtherDevices: Set up other devices
titleSynchronisation: Synchronisation
titleSynchronizationMethod: Synchronization Method
titleSynchronizationPreset: Synchronization Preset
titleSyncSettings: Sync Settings
@@ -1469,8 +1438,8 @@ Replicator:
InitialiseFatalError: No replicator is available, this is the fatal error.
Pending: Some file events are pending. Replication has been cancelled.
SomeModuleFailed: Replication has been cancelled by some module failure
VersionUpFlash: An update has been detected. Please open the Settings dialogue
and check the Change Log. Replication has been cancelled.
VersionUpFlash: Remote synchronisation is paused for compatibility review. Run the
'Review why synchronisation is paused' command for details and available actions.
Requires restart of Obsidian: Requires restart of Obsidian
Requires restart of Obsidian.: Requires restart of Obsidian.
Rerun Onboarding Wizard: Rerun Onboarding Wizard
@@ -2340,6 +2309,29 @@ Ui:
Fetch: Fetch
Overwrite: Overwrite
SetupWizard:
ApplySettingsInitialisation:
ApplyWithoutInitialisation: Apply without Initialisation
Back: Review another way to apply these settings
BypassGuidance: Applying these settings alone can make this device incompatible with its existing synchronisation data. Use this only when you have confirmed that reconstruction is unnecessary.
BypassTitle: Apply Settings without Initialisation?
ContinueFetch: Continue with Fetch
FetchOption: Reset Synchronisation on This Device
FetchOptionDesc: After restarting, rebuild this device's local database from the current remote synchronisation data. Files in the Vault will then be reconciled with that data.
FetchOptionP2PDesc: After restarting, select an online source device. This device's local LiveSync database will be rebuilt from that source.
Guidance: These setting changes alter how synchronisation data is interpreted. Apply them together with an initialisation operation after restart.
KeepEditing: Keep Editing
ProceedFetch: Restart and Fetch Synchronisation Data
ProceedFetchP2P: Restart and Select a Source Device
ProceedRebuild: Restart and Overwrite Server Data
ProceedRebuildP2P: Restart and Prepare This Device
Question: Which existing data should be used after restart?
RebuildOption: Overwrite Server Data with This Device's Files
RebuildOptionDesc: Rebuild the local and remote databases from the files currently in this Vault. Other synchronising devices must reset their local synchronisation afterwards.
RebuildOptionP2P: Prepare This Device from This Vault
RebuildOptionP2PDesc: Rebuild this device's local LiveSync database from the files currently in this Vault. This does not overwrite another device.
RemoteVerificationGuidance: The configured remote could not be verified with the current credentials and encryption settings. Continuing may make the Fetch fail after restart.
RemoteVerificationTitle: Remote Synchronisation Data Could Not Be Verified
Title: Apply Settings and Reinitialise Synchronisation
Common:
Back: No, please take me back
Cancel: Cancel
-40
View File
@@ -751,7 +751,6 @@ moduleLocalDatabase:
moduleLog:
showLog: Mostrar registro
moduleMigration:
docUri: https://github.com/vrtmrz/obsidian-livesync/blob/main/README_ES.md#how-to-use
logBulkSendCorrupted: El envío de fragmentos en bloque se ha habilitado, sin
embargo, esta función se ha corrompido. Disculpe las molestias.
Deshabilitado automáticamente.
@@ -782,30 +781,6 @@ moduleMigration:
___Nota2: Los fragmentos son completamente inmutables, solo podemos obtener
los metadatos y diferencias.___
msgInitialSetup: >-
Tu dispositivo **aún no ha sido configurado**. Permíteme guiarte a través
del proceso de configuración.
Ten en cuenta que todo el contenido del diálogo se puede copiar al
portapapeles. Si necesitas consultarlo más tarde, puedes pegarlo en una nota
en Obsidian. También puedes traducirlo a tu idioma utilizando una
herramienta de traducción.
Primero, ¿tienes **URI de configuración**?
Nota: Si no sabes qué es, consulta la [documentación](${URI_DOC}).
msgRecommendSetupUri: >-
Te recomendamos encarecidamente que generes una URI de configuración y la
utilices.
Si no tienes conocimientos al respecto, consulta la
[documentación](${URI_DOC}) (Lo siento de nuevo, pero es importante).
¿Cómo quieres configurarlo manualmente?
msgSinceV02321: >-
Desde la versión v0.23.21, Self-hosted LiveSync ha cambiado el
comportamiento predeterminado y la estructura de la base de datos. Se han
@@ -838,9 +813,7 @@ moduleMigration:
optionEnableBoth: Habilitar ambos
optionEnableFilenameCaseInsensitive: "Habilitar solo #1"
optionEnableFixedRevisionForChunks: "Habilitar solo #2"
optionHaveSetupUri: Sí, tengo
optionKeepPreviousBehaviour: Mantener comportamiento anterior
optionManualSetup: Configurarlo todo manualmente
optionNoAskAgain: No, por favor pregúntame de nuevo
fix0256:
buttons:
@@ -909,14 +882,8 @@ moduleMigration:
Nota 2: reconstruir todo y obtener los datos consume algo de tiempo y de
tráfico; hazlo en horas de poco uso y con una conexión de red estable.
title: ¡Se han encontrado chunks no seguros!
optionNoSetupUri: No, no tengo
optionRemindNextLaunch: Recordármelo en el próximo inicio
optionSetupViaP2P: Usar %{short_p2p_sync} para configurarlo
optionSetupWizard: Llévame al asistente de configuración
optionYesFetchAgain: Sí, obtener de nuevo
titleCaseSensitivity: Distinción de mayúsculas y minúsculas
titleRecommendSetupUri: Recomendación de usar un Setup URI
titleWelcome: Bienvenido a Self-hosted LiveSync
"Mostly Complete: Decision Required": "Casi terminado: se requiere una decisión"
My remote server is already set up. I want to join this device.: Mi servidor remoto ya está configurado. Quiero añadir este dispositivo.
Name: Nombre
@@ -1506,12 +1473,10 @@ obsidianLiveSyncSettingTab:
btnEnable: Activar
btnFix: Corregir
btnGotItAndUpdated: Lo entendí y actualicé.
btnNext: Siguiente
btnStart: Iniciar
btnTest: Probar
btnUse: Usar
buttonFetch: Obtener
buttonNext: Siguiente
defaultLanguage: Predeterminado
descConnectSetupURI: Este es el método recomendado para configurar Self-hosted
LiveSync con una URI de configuración.
@@ -1585,8 +1550,6 @@ obsidianLiveSyncSettingTab:
a extremo y la obfuscación de ruta. ¿Estás seguro de querer continuar sin
cifrado?
msgFetchConfigFromRemote: ¿Quieres obtener la configuración del servidor remoto?
msgGenerateSetupURI: ¡Todo listo! ¿Quieres generar un URI de configuración para
configurar otros dispositivos?
msgIfConfigNotPersistent: Si la configuración del servidor no es persistente
(por ejemplo, ejecutándose en docker), los valores aquí pueden cambiar. Una
vez que puedas conectarte, por favor actualiza las configuraciones en el
@@ -1670,8 +1633,6 @@ obsidianLiveSyncSettingTab:
Almacena solo la configuración. **Precaución: esto puede provocar corrupción
de datos**; generalmente es necesario reconstruir la base de datos.
msgSelectAndApplyPreset: Por favor, selecciona y aplica cualquier elemento
preestablecido para completar el asistente.
msgSetCorsCredentials: Configurar cors.credentials
msgSetCorsOrigins: Configurar cors.origins
msgSetMaxDocSize: Configurar couchdb.max_document_size
@@ -1729,7 +1690,6 @@ obsidianLiveSyncSettingTab:
panelSetup: Configuración
titleAppearance: Apariencia
titleConflictResolution: Resolución de conflictos
titleCongratulations: ¡Felicidades!
titleCouchDB: Servidor CouchDB
titleDeletionPropagation: Propagación de eliminación
titleEncryptionNotEnabled: El cifrado no está habilitado
-39
View File
@@ -384,7 +384,6 @@ moduleLocalDatabase:
moduleLog:
showLog: Afficher le journal
moduleMigration:
docUri: https://github.com/vrtmrz/obsidian-livesync/blob/main/README.md#how-to-use
fix0256:
buttons:
checkItLater: Vérifier plus tard
@@ -482,31 +481,6 @@ moduleMigration:
___Note 2 : Les fragments sont complètement immuables, nous ne pouvons
récupérer que les métadonnées et les différences.___
msgInitialSetup: >-
Votre appareil n'a **pas encore été configuré**. Laissez-moi vous guider
dans le processus de configuration.
Veuillez noter que chaque contenu de boîte de dialogue peut être copié
dans le presse-papiers. Si vous souhaitez vous y référer plus tard, vous
pouvez le coller dans une note d'Obsidian. Vous pouvez également le
traduire dans votre langue via un outil de traduction.
Tout d'abord, disposez-vous d'une **URI de configuration** ?
Note : Si vous ne savez pas ce que c'est, consultez la
[documentation](${URI_DOC}).
msgRecommendSetupUri: >-
Nous recommandons vivement de générer une URI de configuration et de
l'utiliser.
Si vous ne connaissez pas, veuillez consulter la
[documentation](${URI_DOC}) (Désolé encore, mais c'est important).
Comment souhaitez-vous effectuer la configuration manuellement ?
msgSinceV02321: >-
Depuis la v0.23.21, Self-hosted LiveSync a modifié son comportement par
défaut et la structure de sa base. Les changements suivants ont été
@@ -539,18 +513,10 @@ moduleMigration:
optionEnableBoth: Activer les deux
optionEnableFilenameCaseInsensitive: "Activer seulement #1"
optionEnableFixedRevisionForChunks: "Activer seulement #2"
optionHaveSetupUri: Oui, j'en ai une
optionKeepPreviousBehaviour: Conserver le comportement précédent
optionManualSetup: Tout configurer manuellement
optionNoAskAgain: Non, demandez à nouveau
optionNoSetupUri: Non, je n'en ai pas
optionRemindNextLaunch: Me rappeler au prochain lancement
optionSetupViaP2P: Utiliser %{short_p2p_sync} pour configurer
optionSetupWizard: Ouvrir l'assistant de configuration
optionYesFetchAgain: Oui, récupérer à nouveau
titleCaseSensitivity: Sensibilité à la casse
titleRecommendSetupUri: Recommandation d'utilisation de l'URI de configuration
titleWelcome: Bienvenue dans Self-hosted LiveSync
moduleObsidianMenu:
replicate: Répliquer
Move remotely deleted files to the trash, instead of deleting.: Déplacer les fichiers supprimés à distance vers la corbeille, au lieu de les supprimer.
@@ -574,12 +540,10 @@ obsidianLiveSyncSettingTab:
btnEnable: Activer
btnFix: Corriger
btnGotItAndUpdated: J'ai compris et mis à jour.
btnNext: Suivant
btnStart: Démarrer
btnTest: Tester
btnUse: Utiliser
buttonFetch: Récupérer
buttonNext: Suivant
defaultLanguage: Par défaut
descConnectSetupURI: Méthode recommandée pour configurer Self-hosted LiveSync
avec une URI de configuration.
@@ -653,7 +617,6 @@ obsidianLiveSyncSettingTab:
de bout en bout et l'obfuscation des chemins. Êtes-vous sûr de vouloir
continuer sans chiffrement ?
msgFetchConfigFromRemote: Voulez-vous récupérer la configuration depuis le serveur distant ?
msgGenerateSetupURI: Tout est prêt ! Voulez-vous générer une URI de configuration pour configurer d'autres appareils ?
msgIfConfigNotPersistent: Si la configuration du serveur n'est pas persistante
(par ex. fonctionnant sur Docker), les valeurs peuvent changer. Une fois la
connexion établie, mettez à jour les paramètres dans le local.ini du
@@ -736,7 +699,6 @@ obsidianLiveSyncSettingTab:
Ne stocker que les paramètres. **Attention : cela peut entraîner une
corruption des données** ; une reconstruction de la base est généralement
nécessaire.
msgSelectAndApplyPreset: Veuillez sélectionner et appliquer un préréglage pour terminer l'assistant.
msgSetCorsCredentials: Définir cors.credentials
msgSetCorsOrigins: Définir cors.origins
msgSetMaxDocSize: Définir couchdb.max_document_size
@@ -797,7 +759,6 @@ obsidianLiveSyncSettingTab:
titleActiveRemoteServer: Serveur distant actif
titleAppearance: Apparence
titleConflictResolution: Résolution des conflits
titleCongratulations: Félicitations !
titleCouchDB: CouchDB
titleDeletionPropagation: Propagation des suppressions
titleEncryptionNotEnabled: Le chiffrement n'est pas activé
-33
View File
@@ -344,7 +344,6 @@ moduleLocalDatabase:
moduleLog:
showLog: הצג יומן
moduleMigration:
docUri: https://github.com/vrtmrz/obsidian-livesync/blob/main/README.md#how-to-use
fix0256:
buttons:
checkItLater: בדוק מאוחר יותר
@@ -430,25 +429,6 @@ moduleMigration:
___הערה 2: הנתחים הם בלתי-ניתנים לשינוי לחלוטין, ניתן למשוך רק את המטה-נתונים
וההפרש.___
msgInitialSetup: >-
המכשיר שלך **טרם הוגדר**. אנחנו כאן לעזור לך בתהליך ההגדרה.
שים לב שניתן להעתיק את תוכן כל דיאלוג ללוח. אם צריך לחזור אליו מאוחר יותר,
ניתן להדביק אותו כפתק ב-Obsidian. ניתן גם לתרגם לשפתך בעזרת כלי תרגום.
ראשית, האם יש לך **Setup URI**?
הערה: אם אינך יודע מהו, אנא עיין ב[תיעוד](${URI_DOC}).
msgRecommendSetupUri: >-
אנו ממליצים בחום לייצר Setup URI ולהשתמש בו.
אם אין לך ידע בנושא, אנא עיין ב[תיעוד](${URI_DOC}) (מתנצלים שוב, אך זה חשוב).
כיצד ברצונך להגדיר ידנית?
msgSinceV02321: >-
מאז גרסה 0.23.21, Self-hosted LiveSync שינה את התנהגות ברירת המחדל ומבנה מסד
הנתונים. השינויים הבאים בוצעו:
@@ -478,18 +458,10 @@ moduleMigration:
optionEnableBoth: הפעל את שניהם
optionEnableFilenameCaseInsensitive: "הפעל רק #1"
optionEnableFixedRevisionForChunks: "הפעל רק #2"
optionHaveSetupUri: כן, יש לי
optionKeepPreviousBehaviour: שמור על התנהגות קודמת
optionManualSetup: הגדר הכל ידנית
optionNoAskAgain: לא, אנא שאל שוב
optionNoSetupUri: לא, אין לי
optionRemindNextLaunch: הזכר לי בהפעלה הבאה
optionSetupViaP2P: השתמש ב-%{short_p2p_sync} להגדרה
optionSetupWizard: קח אותי לאשף ההגדרה
optionYesFetchAgain: כן, משוך שוב
titleCaseSensitivity: תלות רישיות
titleRecommendSetupUri: המלצה לשימוש ב-Setup URI
titleWelcome: ברוך הבא ל-Self-hosted LiveSync
moduleObsidianMenu:
replicate: שכפל
Move remotely deleted files to the trash, instead of deleting.: העבר קבצים שנמחקו מרחוק לאשפה, במקום למחוק.
@@ -512,12 +484,10 @@ obsidianLiveSyncSettingTab:
btnEnable: הפעל
btnFix: תקן
btnGotItAndUpdated: הבנתי ועדכנתי.
btnNext: הבא
btnStart: התחל
btnTest: בדוק
btnUse: השתמש
buttonFetch: משוך
buttonNext: הבא
defaultLanguage: ברירת מחדל
descConnectSetupURI: זוהי השיטה המומלצת להגדרת Self-hosted LiveSync עם Setup URI.
descCopySetupURI: מושלם להגדרת מכשיר חדש!
@@ -586,7 +556,6 @@ obsidianLiveSyncSettingTab:
msgEnableEncryptionRecommendation: אנו ממליצים להפעיל הצפנה מקצה לקצה ואת ערפול
הנתיב. האם אתה בטוח שברצונך להמשיך ללא הצפנה?
msgFetchConfigFromRemote: האם ברצונך למשוך את התצורה מהשרת המרוחד?
msgGenerateSetupURI: הכל מוכן! האם ברצונך לייצר Setup URI להגדרת מכשירים אחרים?
msgIfConfigNotPersistent: אם תצורת השרת אינה קבועה (למשל, פועלת ב-docker), הערכים
כאן עשויים להשתנות. לאחר שתצליח להתחבר, אנא עדכן את ההגדרות ב-local.ini של השרת.
msgInvalidPassphrase: ביטוי הסיסמה להצפנה שלך עשוי להיות לא תקין. האם אתה בטוח
@@ -659,7 +628,6 @@ obsidianLiveSyncSettingTab:
שמור רק את ההגדרות. **זהירות: עלול לגרום לפגיעה בנתונים**; בנייה מחדש של מסד
הנתונים נדרשת בדרך כלל.
msgSelectAndApplyPreset: אנא בחר והחל פריט קבוע מראש כלשהו להשלמת האשף.
msgSetCorsCredentials: הגדר cors.credentials
msgSetCorsOrigins: הגדר cors.origins
msgSetMaxDocSize: הגדר couchdb.max_document_size
@@ -718,7 +686,6 @@ obsidianLiveSyncSettingTab:
titleActiveRemoteServer: שרת מרוחד פעיל
titleAppearance: מראה
titleConflictResolution: פתרון קונפליקטים
titleCongratulations: מזל טוב!
titleCouchDB: CouchDB
titleDeletionPropagation: הפצת מחיקות
titleEncryptionNotEnabled: ההצפנה אינה מופעלת
-27
View File
@@ -356,7 +356,6 @@ moduleLocalDatabase:
moduleLog:
showLog: ログを表示
moduleMigration:
docUri: https://github.com/vrtmrz/obsidian-livesync/blob/main/README.md#how-to-use
fix0256:
buttons:
checkItLater: 後で確認する
@@ -416,19 +415,6 @@ moduleMigration:
___注意: 設定が変更され、データベースが再フェッチされるまで同期できません。___
___注意2: チャンクは完全に不変なので、メタデータと差分のみフェッチできます。___
msgInitialSetup: |-
このデバイスは**まだセットアップされていません**。セットアッププロセスをご案内します。
すべてのダイアログの内容はクリップボードにコピーできます。後で参照する必要があれば、Obsidianのノートに貼り付けてください。翻訳ツールを使ってお使いの言語に翻訳することもできます。
まず、**セットアップURI**をお持ちですか?
注意: それが何か分からない場合は、[documentation](${URI_DOC})を参照してください。
msgRecommendSetupUri: |-
セットアップURIを生成して使用することを強くお勧めします。
これについて知識がない場合は、[documentation](${URI_DOC})を参照してください(重要です)。
手動でセットアップしますか?
msgSinceV02321: |-
v0.23.21以降、self-hosted LiveSyncはデフォルトの動作とデータベース構造を変更しました。以下の変更が行われました:
@@ -449,18 +435,10 @@ moduleMigration:
optionEnableBoth: 両方を有効にする
optionEnableFilenameCaseInsensitive: "#1のみ有効にする"
optionEnableFixedRevisionForChunks: "#2のみ有効にする"
optionHaveSetupUri: はい、持っています
optionKeepPreviousBehaviour: 以前の動作を維持
optionManualSetup: すべて手動でセットアップ
optionNoAskAgain: いいえ、後で確認する
optionNoSetupUri: いいえ、持っていません
optionRemindNextLaunch: 次回起動時にリマインド
optionSetupViaP2P: "%{short_p2p_sync}を使ってセットアップ"
optionSetupWizard: セットアップウィザードへ
optionYesFetchAgain: はい、再フェッチする
titleCaseSensitivity: 大文字小文字の区別
titleRecommendSetupUri: セットアップURIの使用を推奨
titleWelcome: Self-hosted LiveSyncへようこそ
moduleObsidianMenu:
replicate: レプリケート
More actions: その他の操作
@@ -486,12 +464,10 @@ obsidianLiveSyncSettingTab:
btnEnable: 有効化
btnFix: 修正
btnGotItAndUpdated: 理解しました、更新しました。
btnNext: 次へ
btnStart: 開始
btnTest: テスト
btnUse: 使用
buttonFetch: フェッチ
buttonNext: 次へ
defaultLanguage: デフォルト
descConnectSetupURI: セットアップURIを使用してSelf-hosted LiveSyncをセットアップする推奨方法です。
descCopySetupURI: 新しいデバイスのセットアップにおすすめ!
@@ -556,7 +532,6 @@ obsidianLiveSyncSettingTab:
msgEnableCorsChttpd: chttpd.enable_corsを設定
msgEnableEncryptionRecommendation: エンドツーエンド暗号化とパス難読化を有効にすることをお勧めします。暗号化なしで続行してもよろしいですか?
msgFetchConfigFromRemote: リモートサーバーから設定を取得しますか?
msgGenerateSetupURI: 完了!他のデバイスをセットアップするためのセットアップURIを生成しますか?
msgIfConfigNotPersistent: "サーバー設定が永続的でない場合(例:
Dockerで実行中)、ここの値は変更される可能性があります。接続できるようになったら、サーバーのlocal.iniの設定を更新してください。"
msgInvalidPassphrase: 暗号化パスフレーズが無効かもしれません。続行してもよろしいですか?
@@ -599,7 +574,6 @@ obsidianLiveSyncSettingTab:
リモートデータベースを再構築した場合も含まれます。
## ${OPTION_ONLY_SETTING}
設定のみを保存します。**注意: データ破損につながる可能性があります**。通常、データベースの再構築が必要です。
msgSelectAndApplyPreset: ウィザードを完了するには、プリセット項目を選択して適用してください。
msgSetCorsCredentials: cors.credentialsを設定
msgSetCorsOrigins: cors.originsを設定
msgSetMaxDocSize: couchdb.max_document_sizeを設定
@@ -656,7 +630,6 @@ obsidianLiveSyncSettingTab:
titleActiveRemoteServer: アクティブなリモートサーバー
titleAppearance: 外観
titleConflictResolution: 競合解決
titleCongratulations: おめでとうございます!
titleCouchDB: CouchDB サーバー
titleDeletionPropagation: 削除の伝播
titleEncryptionNotEnabled: 暗号化が有効になっていません
+318 -27
View File
@@ -1,34 +1,79 @@
", please select the option that best describes the current state of your Vault. The application will then check your files in the most appropriate way based on your selection.": ", 이 보관함의 현재 상태를 가장 잘 설명하는 옵션을 선택해 주세요. 선택하신 내용에 따라 가장 적절한 방식으로 파일을 확인합니다."
(Active): (활성)
(BETA) Always overwrite with a newer file: (베타) 항상 새로운 파일로 덮어쓰기
(Beta) Use ignore files: (베타) 제외 규칙 파일 사용
(Days passed, 0 to disable automatic-deletion): (지난 일수, 0으로 설정하면 자동 삭제 비활성화)
(e.g., after editing many files whilst offline): "(예: 오프라인 상태에서 많은 파일을 편집한 뒤)"
(e.g., immediately after restoring on another computer, or having recovered from a backup): "(예: 다른 컴퓨터에서 복원한 직후이거나 백업에서 복구한 경우)"
(e.g., setting up for the first time on a new smartphone, starting from a clean slate): "(예: 새 스마트폰에서 처음 설정하여 빈 상태에서 시작하는 경우)"
(ex. Read chunks online) If this option is enabled, LiveSync reads chunks online directly instead of replicating them locally. Increasing Custom chunk size is recommended.: "(예: 청크를 원격에서 읽음) 이 옵션을 활성화하면, LiveSync는 청크를 로컬에 복제하지 않고 원격에서 직접 읽습니다. 커스텀 청크 크기를 키우는 것을 권장합니다."
(MB) If this is set, changes to local and remote files that are larger than this will be skipped. If the file becomes smaller again, a newer one will be used.: (MB) 이 값이 설정되면, 이보다 큰 로컬 및 원격 파일의 변경 사항은 건너뜁니다. 파일이 다시 작아지면 더 새로운 파일이 사용됩니다.
(Mega chars): (백만 자 단위)
(Missing): (없음)
(Not recommended) If set, credentials will be stored in the file.: (권장하지 않음) 설정한 경우 자격 증명이 파일에 저장됩니다.
(Obsolete) Use an old adapter for compatibility: (사용 중단) 호환성을 위해 이전 어댑터 사용
(RegExp) Empty to sync all files. Set filter as a regular expression to limit synchronising files.: (정규식) 비워 두면 모든 파일을 동기화합니다. 정규식을 지정하면 동기화할 파일을 제한할 수 있습니다.
(RegExp) If this is set, any changes to local and remote files that match this will be skipped.: (정규식) 설정하면 이 패턴과 일치하는 로컬 및 원격 파일 변경은 모두 건너뜁니다.
(Select this if you are already using synchronisation on another computer or smartphone.) This option is suitable if you are new to LiveSync and want to set it up from scratch.: (다른 컴퓨터나 스마트폰에서 이미 동기화를 사용 중이라면 선택하세요.) 이 기기를 기존 LiveSync 구성에 추가하려는 경우에 적합합니다.
(Select this if you are configuring this device as the first synchronisation device.) This option is suitable if you are new to LiveSync and want to set it up from scratch.: (이 기기를 첫 번째 동기화 기기로 설정한다면 선택하세요.) LiveSync를 처음 사용하며 처음부터 설정하려는 경우에 적합합니다.
"↑: Overwrite Remote": "↑: 원격 덮어쓰기"
"↓: Overwrite Local": "↓: 로컬 덮어쓰기"
"⇅: Use newer": "⇅: 더 새로운 쪽 사용"
+1 week: +1주
"> [!INFO]- The connected devices have been detected as follows:\n${devices}": |-
> [!INFO]- 다음 연결된 기기가 감지되었습니다:
${devices}
⚠️ Important Notice: ⚠️ 중요 안내
⚠️ Please Confirm the Following: ⚠️ 다음 내용을 확인해 주세요
✔ SELECT: ✔ 선택
✔ SYNC: ✔ 동기화
✔ WATCH: ✔ 감시
📡 Off: 📡 꺼짐
📡 On: 📡 켜짐
🔴 Disconnected: 🔴 연결 끊김
🕵️ Diag: 🕵️ 진단
🗑 Delete: 🗑 삭제
🟢 Connected: 🟢 연결됨
${count} issue(s) detected!: ${count}개의 문제가 감지되었습니다!
A Setup URI is a single string of text containing your server address and authentication details. Using a URI, if one was generated by your server installation script, provides a simple and secure configuration.: Setup URI는 서버 주소와 인증 정보를 담은 하나의 문자열입니다. 서버 설치 스크립트가 URI를 생성했다면, 이를 사용해 간단하고 안전하게 구성할 수 있습니다.
Accept: 수락
Accept in session: 이 세션에서만 수락
ACCEPTED: 수락됨
ACCEPTED (in session): 수락됨 (이 세션에서만)
Access Key: 액세스 키
Access Key ID: 액세스 키 ID
Action: 작업
Activate: 활성화
Active Remote Configuration: 활성 원격 구성
Add default patterns: 기본 패턴 추가
Add new connection: 연결 추가
AddOn Module (ConfigSync) has not been loaded. This is very unexpected situation. Please report this issue.: 애드온 모듈(ConfigSync)이 로드되지 않았습니다. 매우 예기치 못한 상황입니다. 이 문제를 신고해 주세요.
AddOn Module (HiddenFileSync) has not been loaded. This is very unexpected situation. Please report this issue.: 애드온 모듈(HiddenFileSync)이 로드되지 않았습니다. 매우 예기치 못한 상황입니다. 이 문제를 신고해 주세요.
Advanced: 고급
Advanced Settings: 고급 설정
After restarting, the data on this device will be uploaded to the server as the 'master copy'. Please be aware that any unintended data currently on the server will be completely overwritten.: 재시작하면 이 기기의 데이터가 '원본'으로서 서버에 업로드됩니다. 현재 서버에 있는 의도치 않은 데이터는 모두 완전히 덮어써진다는 점에 유의해 주세요.
After restarting, the database on this device will be rebuilt using data from the server. If there are any unsynchronised files in this vault, conflicts may occur with the server data.: 재시작하면 서버의 데이터를 사용해 이 기기의 데이터베이스가 재구축됩니다. 이 보관함에 동기화되지 않은 파일이 있다면 서버 데이터와 충돌이 발생할 수 있습니다.
After that, synchronise to a brand new vault on each other device with the new remote one by one.: 그런 다음 다른 기기에서도 하나씩 새 원격을 사용해 완전히 새로운 보관함에 동기화하세요.
All checks passed successfully!: 모든 검사를 통과했습니다!
All devices have the same progress value (${progress}). Your devices seem to be synchronised. And be able to proceed with Garbage Collection.: 모든 기기의 진행 값이 동일합니다(${progress}). 기기들이 동기화된 것으로 보이므로 가비지 컬렉션을 진행할 수 있습니다.
All the same or non-existent: 모두 동일하거나 존재하지 않음
Allow in session: 이 세션에서만 허용
Allow permanently: 항상 허용
Also, please note that if you are using Peer-to-Peer synchronization, this configuration will be used when you switch to other methods and connect to a remote server in the future.: 또한 Peer-to-Peer 동기화를 사용 중이라면, 이 구성은 나중에 다른 방식으로 전환하여 원격 서버에 연결할 때 사용된다는 점에 유의해 주세요.
Always prompt merge conflicts: 항상 병합 충돌 알림
Analyse: 분석
Analyse database usage: 데이터베이스 사용량 분석
Analyse database usage and generate a TSV report for diagnosis yourself. You can paste the generated report with any spreadsheet you like.: 데이터베이스 사용량을 분석하고 직접 진단할 수 있도록 TSV 보고서를 생성합니다. 생성된 보고서는 원하는 스프레드시트에 붙여 넣어 확인할 수 있습니다.
Apply All Selected: 선택 항목 모두 적용
Apply Latest Change if Conflicting: 충돌 시 최신 변경 사항 적용
Apply preset configuration: 프리셋 구성 적용
Apply the settings: 설정 적용
Ask a passphrase at every launch: 시작할 때마다 패스프레이즈 묻기
Auto Connect: 자동 연결
Auto Start P2P Connection: P2P 연결 자동 시작
Automatic: 자동
Automatically Sync all files when opening Obsidian.: Obsidian을 열 때 모든 파일을 자동으로 동기화합니다.
Available Peers: 사용 가능한 피어
Back: 뒤로
Back to non-configured: 미구성 상태로 되돌리기
Batch database update: 일괄 데이터베이스 업데이트
@@ -36,20 +81,32 @@ Batch limit: 배치 개수 제한
Batch size: 배치 크기
Batch size of on-demand fetching: 필요 시 가져오기의 배치 크기
Before v0.17.16, we used an old adapter for the local database. Now the new adapter is preferred. However, it needs local database rebuilding. Please disable this toggle when you have enough time. If leave it enabled, also while fetching from the remote database, you will be asked to disable this.: v0.17.16 이전에는 로컬 데이터베이스에 이전 어댑터를 사용했습니다. 이제는 새로운 어댑터를 권장합니다. 하지만 로컬 데이터베이스 재구축이 필요합니다. 충분한 시간이 있을 때 이 토글을 비활성화해 주세요. 활성화된 상태로 두면 원격 데이터베이스에서 가져올 때도 이를 비활성화하라는 메시지가 나타납니다.
Broadcasting?: 브로드캐스트란?
Bucket Name: 버킷 이름
by resetting the remote, you will be informed on other devices.: 원격을 초기화하면 다른 기기에서 알림을 받게 됩니다.
Cancel: 취소
Cancel Garbage Collection: 가비지 컬렉션 취소
Changing the encryption algorithm will prevent access to any data previously encrypted with a different algorithm. Ensure that all your devices are configured to use the same algorithm to maintain access to your data.: 암호화 알고리즘을 변경하면 이전에 다른 알고리즘으로 암호화된 데이터에는 접근할 수 없게 됩니다. 데이터에 계속 접근할 수 있도록 모든 기기가 동일한 알고리즘을 사용하도록 설정해 주세요.
Changing this setting requires migrating existing data (a bit time may be taken) and restarting Obsidian. Please make sure to back up your data before proceeding.: 이 설정을 변경하려면 기존 데이터를 마이그레이션하고(시간이 다소 걸릴 수 있습니다) Obsidian을 재시작해야 합니다. 진행하기 전에 반드시 데이터를 백업해 주세요.
Check: 확인
Check and convert non-path-obfuscated files: 경로 난독화되지 않은 파일 검사 및 변환
Check for documents that have not been converted to path-obfuscated IDs and convert them if necessary.: 아직 경로 난독화 ID로 변환되지 않은 문서를 확인하고 필요하면 변환합니다.
Checking connection... Please wait.: 연결을 확인하는 중입니다... 잠시만 기다려 주세요.
Chunks: 청크
Close: 닫기
Close & Disconnect: 닫고 연결 끊기
Close this dialog: 이 대화 상자 닫기
"Closed:": "종료됨:"
cmdConfigSync:
showCustomizationSync: 사용자 설정 동기화 표시
Comma separated `.gitignore, .dockerignore`: 쉼표로 구분된 `.gitignore, .dockerignore`
Command: 명령
Communicating: 통신 중
Compaction in progress on remote database...: 원격 데이터베이스에서 압축 정리를 진행 중입니다...
Compaction on remote database completed successfully.: 원격 데이터베이스 압축 정리가 성공적으로 완료되었습니다.
Compaction on remote database failed.: 원격 데이터베이스 압축 정리에 실패했습니다.
Compaction on remote database timed out.: 원격 데이터베이스 압축 정리 시간이 초과되었습니다.
Compare file: 파일 비교
Compare the content of files between on local database and storage. If not matched, you will be asked which one you want to keep.: 로컬 데이터베이스와 저장소 간의 파일 내용을 비교합니다. 일치하지 않으면 어떤 쪽을 유지할지 묻게 됩니다.
Compatibility (Conflict Behaviour): 호환성 (충돌 동작)
Compatibility (Database structure): 호환성 (데이터베이스 구조)
@@ -58,43 +115,69 @@ Compatibility (Metadata): 호환성 (메타데이터)
Compatibility (Remote Database): 호환성 (원격 데이터베이스)
Compatibility (Trouble addressed): 호환성 (문제 대응)
Compute revisions for chunks: 청크에 대한 리비전 계산
Configuration: 구성
Configuration Encryption: 구성 암호화
Configure: 설정
Configure And Change Remote: 원격 구성 및 변경
Configure E2EE: E2EE 구성
Configure Remote: 원격 구성
Configure the same server information as your other devices again, manually, very advanced users only.: 다른 기기와 동일한 서버 정보를 다시 직접 입력합니다. 숙련된 사용자 전용입니다.
Connect: 연결
"Connected to Signaling Server (as Peer ID: ${peerId})": "시그널링 서버에 연결되었습니다 (피어 ID: ${peerId})"
"Connected:": "연결됨:"
Connection Method: 연결 방법
Connection Settings: 연결 설정
"Connection:": "연결:"
Continue anyway: 무시하고 계속
Continue to CouchDB setup: CouchDB 설정으로 계속
Continue to Peer-to-Peer only setup: Peer-to-Peer 전용 설정으로 계속
Continue to S3/MinIO/R2 setup: S3/MinIO/R2 설정으로 계속
Copy: 복사
Copy Report to clipboard: 보고서를 클립보드에 복사
CouchDB Configuration: CouchDB 구성
CouchDB Connection Tweak: CouchDB 연결 조정
Create P2P remote: P2P 원격 만들기
Cross-platform: 크로스 플랫폼
"Current adapter: {adapter}": "현재 어댑터: {adapter}"
Custom Headers: 사용자 지정 헤더
Customization Sync: 사용자 설정 동기화
Customization Sync (Beta3): 사용자 설정 동기화 (Beta3)
Data Compression: 데이터 압축
Data to Copy: 복사할 데이터
Database -> Storage: 데이터베이스 -> 스토리지
Database Adapter: 데이터베이스 어댑터
Database Name: 데이터베이스 이름
Database suffix: 데이터베이스 접미사
Date: 날짜
Default: 기본값
Delay conflict resolution of inactive files: 비활성 파일의 충돌 해결 지연
Delay merge conflict prompt for inactive files.: 비활성 파일의 병합 충돌 프롬프트 지연.
Delete: 삭제
Delete all customization sync data: 모든 사용자 설정 동기화 데이터 삭제
Delete all data on the remote server.: 원격 서버의 모든 데이터를 삭제합니다.
Delete All of: 모두 삭제할 대상
Delete local database to reset or uninstall Self-hosted LiveSync: Self-hosted LiveSync를 초기화하거나 제거하기 위해 로컬 데이터베이스를 삭제
Delete old metadata of deleted files on start-up: 시작 시 삭제된 파일의 오래된 메타데이터 삭제
Delete Remote Configuration: 원격 구성 삭제
Delete remote configuration '{name}'?: "'{name}' 원격 구성을 삭제할까요?"
Delete remote configuration '${name}'?: "'${name}' 원격 구성을 삭제할까요?"
DENIED: 거부됨
DENIED (in session): 거부됨 (이 세션에서만)
Deny: 거부
Deny in session: 이 세션에서만 거부
Deny permanently: 항상 거부
Deselect all: 선택 모두 해제
desktop: 데스크톱
Detected Peers: 감지된 피어
Developer: 개발자
Device: 기기
device name: 기기 이름
Device name: 기기 이름
Device name to identify the device. Please use shorter one for the stable peer detection, i.e., "iphone-16" or "macbook-2021".: 기기를 식별하기 위한 기기 이름입니다. 피어를 안정적으로 감지할 수 있도록 "iphone-16"이나 "macbook-2021"처럼 짧은 이름을 사용해 주세요.
Device Peer ID: 기기 피어 ID
Device Setup Method: 기기 설정 방법
"Devices:": "기기:"
Diagnostic RTCPeerConnection is enabled: 진단용 RTCPeerConnection이 활성화되어 있습니다
dialog:
yourLanguageAvailable:
_value: |-
@@ -105,8 +188,12 @@ dialog:
편하게 사용하실 수 있기를 바랍니다!
btnRevertToDefault: "%{lang-def} 유지"
Title: " 번역을 사용할 수 있습니다!"
Diff: 차이
Different: 다름
Disables all synchronization and restart.: 모든 동기화를 비활성화하고 재시작합니다.
Disables logging, only shows notifications. Please disable if you report an issue.: 로깅을 비활성화하고 알림만 표시합니다. 문제를 신고하는 경우 비활성화해 주세요.
Disconnect: 연결 끊기
Dismiss: 닫기
Display Language: 표시 언어
Display name: 표시 이름
Do not check configuration mismatch before replication: 복제 전 구성 불일치 확인 안 함
@@ -177,33 +264,70 @@ Enable advanced features: 고급 기능 활성화
Enable customization sync: 사용자 설정 동기화 활성화
Enable Developers' Debug Tools.: 개발자 디버그 도구 활성화
Enable edge case treatment features: 특수 사례 처리 기능 활성화
Enable P2P Replicator: P2P 복제기 활성화
Enable poweruser features: 파워 유저 기능 활성화
Enable this if your Object Storage doesn't support CORS: 객체 스토리지가 CORS를 지원하지 않는 경우 활성화하세요
Enable this option to automatically apply the most recent change to documents even when it conflicts: 이 옵션을 활성화하면 충돌이 있어도 문서에 가장 최근 변경 사항을 자동으로 적용합니다
Enabled: 활성화됨
Enabling end-to-end encryption ensures that your data is encrypted on your device before being sent to the remote server. This means that even if someone gains access to the server, they won't be able to read your data without the passphrase. Make sure to remember your passphrase, as it will be required to decrypt your data on other devices.: 종단 간 암호화를 활성화하면 데이터가 원격 서버로 전송되기 전에 기기에서 암호화됩니다. 따라서 누군가 서버에 접근하더라도 패스프레이즈 없이는 데이터를 읽을 수 없습니다. 다른 기기에서 데이터를 복호화할 때 필요하므로 패스프레이즈를 반드시 기억해 두세요.
Encrypt contents on the remote database. If you use the plugin's synchronization feature, enabling this is recommended.: 원격 데이터베이스의 내용을 암호화합니다. 플러그인의 동기화 기능을 사용하는 경우 활성화를 권장합니다.
Encrypting sensitive configuration items: 민감한 구성 항목 암호화
Encryption Algorithm: 암호화 알고리즘
Encryption phassphrase. If changed, you should overwrite the server's database with the new (encrypted) files.: 암호화에 사용할 패스프레이즈입니다. 변경한 경우, 새로 암호화된 파일로 서버의 데이터베이스를 덮어써야 합니다.
End-to-End Encryption: 종단 간 암호화
Endpoint URL: 엔드포인트 URL
Enhance chunk size: 청크 크기 확대
Enter a folder prefix (optional): 폴더 접두사를 입력하세요 (선택 사항)
Enter Server Information: 서버 정보 입력
Enter Setup URI: Setup URI 입력
Enter the server information manually: 서버 정보를 수동으로 입력
Enter TURN credential: TURN 자격 증명을 입력하세요
Enter TURN username: TURN 사용자 이름을 입력하세요
Enter your Access Key ID: 액세스 키 ID를 입력하세요
Enter your Bucket Name: 버킷 이름을 입력하세요
Enter your database name: 데이터베이스 이름을 입력하세요
Enter your JWT Key ID: JWT 키 ID를 입력하세요
Enter your JWT secret or private key: JWT 시크릿 또는 개인 키를 입력하세요
Enter your JWT Subject (CouchDB Username): JWT 주체(CouchDB 사용자 이름)를 입력하세요
Enter your passphrase: 패스프레이즈를 입력하세요
Enter your password: 비밀번호를 입력하세요
Enter your Region (e.g., us-east-1, auto for R2): "리전을 입력하세요 (예: us-east-1, R2는 auto)"
Enter your Secret Access Key: 시크릿 액세스 키를 입력하세요
Enter your username: 사용자 이름을 입력하세요
"Error during connection test: ${reason}": "연결 테스트 중 오류가 발생했습니다: ${reason}"
"Error during testAndFixSettings: ${reason}": "testAndFixSettings 실행 중 오류가 발생했습니다: ${reason}"
Experimental Settings: 실험적 설정
Export: 내보내기
Failed to connect to remote for compaction.: 압축 정리를 위해 원격 데이터베이스에 연결하지 못했습니다.
Failed to connect to remote for compaction. ${reason}: 압축 정리를 위해 원격 데이터베이스에 연결하지 못했습니다. ${reason}
"Failed to connect to the server: ${reason}": "서버에 연결하지 못했습니다: ${reason}"
Failed to connect to the server. Please check your settings.: 서버에 연결하지 못했습니다. 설정을 확인해 주세요.
"Failed to connect to the signalling relay: ${reason}": "시그널링 중계 서버에 연결하지 못했습니다: ${reason}"
Failed to create replicator instance.: 복제기 인스턴스를 생성하지 못했습니다.
Failed to parse Setup-URI.: Setup-URI를 해석하지 못했습니다.
Failed to start one-shot replication before Garbage Collection. Garbage Collection Cancelled.: 가비지 컬렉션 전에 일회성 복제를 시작하지 못했습니다. 가비지 컬렉션을 취소합니다.
Failed to start replication after Garbage Collection.: 가비지 컬렉션 후 복제를 시작하지 못했습니다.
"Failed:": "실패:"
Fetch: 가져오기
Fetch chunks on demand: 필요 시 청크 원격 가져오기
Fetch database with previous behaviour: 이전 동작으로 데이터베이스 가져오기
Fetch remote settings: 원격 설정 가져오기
FETCHING: 가져오는 중
Fetching status...: 상태를 가져오는 중입니다...
File integrity: 파일 무결성
File to resolve conflict: 충돌을 해결할 파일
File to view History: 기록을 볼 파일
Filename: 파일명
"Final Confirmation: Overwrite Server Data with This Device's Files": "최종 확인: 이 기기의 파일로 서버 데이터 덮어쓰기"
First, please select the option that best describes your current situation.: 먼저 현재 상황에 가장 잘 맞는 항목을 선택해 주세요.
Fix: 수정
Flag and restart: 표시 후 재시작
Flagged Selective: 플래그 지정 선택적
Folder Prefix: 폴더 접두사
For HS256/HS512 algorithms, provide the shared secret key. For ES256/ES512 algorithms, provide the pkcs8 PEM-formatted private key.: HS256/HS512 알고리즘에는 공유 시크릿 키를, ES256/ES512 알고리즘에는 pkcs8 PEM 형식의 개인 키를 입력하세요.
Forces the file to be synced when opened.: 파일을 열 때 강제로 동기화합니다.
Fresh Start Wipe: 초기화 후 새로 시작
Furthermore, if conflicts are already present in the server data, they will be synchronised to this device as they are, and you will need to resolve them locally.: 또한 서버 데이터에 이미 충돌이 존재한다면 그 상태 그대로 이 기기에 동기화되므로, 로컬에서 직접 해결해야 합니다.
Garbage Collection cancelled by user.: 사용자가 가비지 컬렉션을 취소했습니다.
"Garbage Collection completed. Deleted chunks: ${deletedChunks} / ${totalChunks}. Time taken: ${seconds} seconds.": "가비지 컬렉션이 완료되었습니다. 삭제된 청크: ${deletedChunks} / ${totalChunks}. 소요 시간: ${seconds}초."
Garbage Collection Confirmation: 가비지 컬렉션 확인
@@ -211,15 +335,33 @@ Garbage Collection V3 (Beta): 가비지 컬렉션 V3 (Beta)
"Garbage Collection: Found ${unusedChunks} unused chunks to delete.": "가비지 컬렉션: 삭제할 미사용 청크 ${unusedChunks}개를 찾았습니다."
"Garbage Collection: Scanned ${scanned} / ~${docCount}": "가비지 컬렉션: ${scanned} / ~${docCount} 검사함"
"Garbage Collection: Scanning completed. Total chunks: ${totalChunks}, Used chunks: ${usedChunks}": "가비지 컬렉션: 검사 완료. 전체 청크 수: ${totalChunks}, 사용 중인 청크 수: ${usedChunks}"
Gathering information...: 정보를 수집하는 중입니다...
Generate Random ID: 임의 ID 생성
Group ID: 그룹 ID
Handle files as Case-Sensitive: 파일을 대소문자 구분으로 처리
Have you created a backup before proceeding?: 진행하기 전에 백업을 만드셨나요?
Hidden file synchronization have been temporarily disabled. Please enable them after the fetching, if you need them.: 숨김 파일 동기화가 일시적으로 비활성화되었습니다. 필요하다면 가져오기가 끝난 뒤에 다시 활성화해 주세요.
Hidden Files: 숨김 파일
Hide completely: 완전히 숨기기
Hide not applicable items: 해당 없는 항목 숨기기
Higher (${local} > ${remote}): 더 높음 (${local} > ${remote})
Highlight diff: 차이 강조
How to display network errors when the sync server is unreachable.: 동기화 서버에 연결할 수 없을 때 네트워크 오류를 어떻게 표시할지 설정합니다.
How would you like to configure the connection to your server?: 서버 연결을 어떻게 구성하시겠습니까?
However, This should not be enabled if you want to increase your secrecy more.: 다만 기밀성을 더 높이고 싶다면 이 옵션은 활성화하지 않는 것이 좋습니다.
I am adding a device to an existing synchronisation setup: 기존 동기화 구성에 기기를 추가합니다
I am setting this up for the first time: 처음으로 설정합니다
I am setting up a new server for the first time / I want to reset my existing server.: 새 서버를 처음 설정하거나, 기존 서버를 초기화하려고 합니다.
I am unable to create a backup of my Vault.: 보관함을 백업할 수 없습니다.
I am unable to create a backup of my Vaults.: 보관함들을 백업할 수 없습니다.
I have created a backup of my Vault.: 보관함을 백업했습니다.
I know my server details, let me enter them: 서버 정보를 알고 있으니 직접 입력하겠습니다
I understand that all changes made on other smartphones or computers possibly could be lost.: 다른 스마트폰이나 컴퓨터에서 변경한 내용이 모두 사라질 수 있다는 점을 이해합니다.
I understand that other devices will no longer be able to synchronise, and will need to be reset the synchronisation information.: 다른 기기는 더 이상 동기화할 수 없게 되며, 동기화 정보를 초기화해야 한다는 점을 이해합니다.
I understand that this action is irreversible once performed.: 이 작업은 한 번 수행하면 되돌릴 수 없다는 점을 이해합니다.
I understand the risks and will proceed without a backup.: 위험을 이해하며 백업 없이 진행하겠습니다.
I Understand, Overwrite Server: 이해했습니다, 서버 덮어쓰기
If "Auto Start P2P Connection" is enabled, the P2P connection will be started automatically when the plug-in launches.: '"P2P 연결 자동 시작"이 활성화되어 있으면 플러그인이 시작될 때 P2P 연결이 자동으로 시작됩니다.'
If disabled(toggled), chunks will be split on the UI thread (Previous behaviour).: 비활성화(토글)되면 청크는 UI 스레드에서 분할됩니다 (이전 동작).
If enabled per-filed efficient customization sync will be used. We need a small migration when enabling this. And all devices should be updated to v0.23.18. Once we enabled this, we lost a compatibility with old versions.: 활성화하면 파일별 효율적인 사용자 설정 동기화가 사용됩니다. 이를 활성화할 때 소규모 데이터 구조 전환이 필요합니다. 모든 기기를 v0.23.18로 업데이트해야 합니다. 이를 활성화하면 이전 버전과의 호환성이 사라집니다.
If enabled, chunks will be split into no more than 100 items. However, dedupe is slightly weaker.: 활성화하면 청크는 최대 100개 항목으로 분할됩니다. 하지만 중복 제거 기능이 약간 약해집니다.
@@ -232,16 +374,38 @@ If this enabled, All files are handled as case-Sensitive (Previous behaviour).:
If this enabled, chunks will be split into semantically meaningful segments. Not all platforms support this feature.: 이 옵션을 활성화하면 청크가 문단이나 의미 단위로 나뉘어 저장됩니다. 단, 이 기능은 일부 플랫폼에서는 지원되지 않을 수 있습니다.
If this is set, changes to local files which are matched by the ignore files will be skipped. Remote changes are determined using local ignore files.: 이 옵션을 활성화하면, 제외 규칙 파일에 일치하는 로컬 파일의 변경 사항은 건너뜁니다. 원격 변경 여부 또한 로컬의 제외 규칙 파일에 따라 판단됩니다.
If this option is enabled, PouchDB will hold the connection open for 60 seconds, and if no change arrives in that time, close and reopen the socket, instead of holding it open indefinitely. Useful when a proxy limits request duration but can increase resource usage.: 이 옵션이 활성화되면 PouchDB는 연결을 더이상 무한히 열어두지 않고 60초 동안 유지합니다. 그 시간 내에 변경 사항이 없으면 소켓을 닫고 다시 엽니다. 프록시가 요청 지속 시간을 제한할 때 유용하지만 리소스 사용량이 증가할 수 있습니다.
If you cannot avoid CORS issues, you might want to try this option. It uses Obsidian's internal API to communicate with the CouchDB server. Not compliant with web standards, but works. Note that this might break in future Obsidian versions.: CORS 문제를 피할 수 없다면 이 옵션을 시도해 볼 수 있습니다. Obsidian의 내부 API를 사용해 CouchDB 서버와 통신합니다. 웹 표준을 따르지는 않지만 동작합니다. 향후 Obsidian 버전에서 동작하지 않을 수 있다는 점에 유의하세요.
If you cannot avoid CORS issues, you might want to try this option. It uses Obsidian's internal API to communicate with the S3 server. Not compliant with web standards, but works. Note that this might break in future Obsidian versions.: CORS 문제를 피할 수 없다면 이 옵션을 시도해 볼 수 있습니다. Obsidian의 내부 API를 사용해 S3 서버와 통신합니다. 웹 표준을 따르지는 않지만 동작합니다. 향후 Obsidian 버전에서 동작하지 않을 수 있다는 점에 유의하세요.
If you have unsynchronised changes in your Vault on this device, they will likely diverge from the server's versions after the reset. This may result in a large number of file conflicts.: 이 기기의 보관함에 동기화되지 않은 변경 사항이 있다면, 초기화 후 서버의 버전과 어긋나기 쉽습니다. 그 결과 많은 파일에서 충돌이 발생할 수 있습니다.
If you reached the payload size limit when using IBM Cloudant, please decrease batch size and batch limit to a lower value.: IBM Cloudant를 사용하다가 페이로드 크기 제한에 도달했다면, 배치 크기와 배치 개수 제한을 더 낮은 값으로 줄여 주세요.
If you understand the risks and still wish to proceed, select so.: 위험을 이해하고도 진행하려면 그렇게 선택해 주세요.
If you want to store the data in a specific folder within the bucket, you can specify a folder prefix here. Otherwise, leave it blank to store data at the root of the bucket.: 버킷 안의 특정 폴더에 데이터를 저장하려면 여기에 폴더 접두사를 지정할 수 있습니다. 그렇지 않다면 비워 두어 버킷 최상위에 데이터를 저장하세요.
If you want to use `LiveSync`, you should broadcast changes. All `watching` peers which detects this will start the replication for fetching.: "`LiveSync`를 사용하려면 변경 사항을 브로드캐스트해야 합니다. 이를 감지한 모든 `watching` 상태의 피어가 가져오기를 위한 복제를 시작합니다."
Ignore: 무시
Ignore and Proceed: 무시하고 계속
Ignore files: 제외 규칙 파일
Ignore patterns: 무시 패턴
Import connection: 연결 가져오기
IMPORTANT: 중요
In most cases, you should stick with the default algorithm (${algorithm}), This setting is only required if you have an existing Vault encrypted in a different format.: 대부분의 경우 기본 알고리즘(${algorithm})을 그대로 사용하는 것이 좋습니다. 이 설정은 기존 보관함이 다른 형식으로 암호화되어 있는 경우에만 필요합니다.
In this scenario, Self-hosted LiveSync will recreate metadata for every file and deliberately generate conflicts. Where the file content is identical, these conflicts will be resolved automatically.: 이 경우 Self-hosted LiveSync는 모든 파일의 메타데이터를 다시 만들면서 의도적으로 충돌을 발생시킵니다. 파일 내용이 동일한 경우 이러한 충돌은 자동으로 해결됩니다.
"Incoming:": "수신:"
Incubate Chunks in Document: 문서 내 청크 임시 보관
Initial Action: 초기 작업
Initialise all journal history, On the next sync, every item will be received and sent.: 모든 저널 기록을 초기화합니다. 다음 동기화 때 모든 항목을 다시 받고 다시 보냅니다.
Initialise journal received history. On the next sync, every item except this device sent will be downloaded again.: 저널 수신 기록을 초기화합니다. 다음 동기화 때 이 기기가 보낸 항목을 제외한 모든 항목을 다시 내려받습니다.
Initialise journal sent history. On the next sync, every item except this device received will be sent again.: 저널 송신 기록을 초기화합니다. 다음 동기화 때 이 기기가 받은 항목을 제외한 모든 항목을 다시 보냅니다.
Interval (sec): 간격 (초)
INVERTED: 반전됨
"Issue detection log:": "문제 감지 로그:"
It is strongly advised to create a backup before proceeding. Continuing without a backup may lead to data loss.: 진행하기 전에 백업을 만드시기를 강력히 권장합니다. 백업 없이 진행하면 데이터가 손실될 수 있습니다.
Just for a minute, please!: 잠시만 기다려 주세요!
JWT (JSON Web Token) authentication allows you to securely authenticate with the CouchDB server using tokens. Ensure that your CouchDB server is configured to accept JWTs and that the provided key and settings match the server's configuration. Incidentally, I have not verified it very thoroughly.: JWT(JSON Web Token) 인증을 사용하면 토큰으로 CouchDB 서버에 안전하게 인증할 수 있습니다. CouchDB 서버가 JWT를 받아들이도록 설정되어 있는지, 그리고 입력한 키와 설정이 서버 구성과 일치하는지 확인해 주세요. 참고로 이 기능은 그리 철저하게 검증되지는 않았습니다.
JWT Algorithm: JWT 알고리즘
JWT Expiration Duration (minutes): JWT 만료 시간(분)
JWT Key: JWT 키
JWT Key ID (kid): JWT 키 ID (kid)
JWT Subject (sub): JWT 주체 (sub)
K:
exp: 실험 기능
long_p2p_sync: "%{title_p2p_sync} (%{exp})"
@@ -264,6 +428,7 @@ lang-zh: 简体中文
lang-zh-tw: 繁體中文
Later: 나중에
"Limit: {datetime} ({timestamp})": "제한: {datetime} ({timestamp})"
live: 실시간
LiveSync could not handle multiple vaults which have same name without different prefix, This should be automatically configured.: LiveSync는 접두사로 구분되지 않은 동일한 이름의 보관함을 여러 개 처리할 수 없습니다. 이 값은 자동으로 구성되어야 합니다.
liveSyncReplicator:
beforeLiveSync: LiveSync 전에 OneShot을 먼저 시작합니다...
@@ -297,6 +462,7 @@ liveSyncSetting:
liveSyncSettings:
btnApply: 적용
Local Database Tweak: 로컬 데이터베이스 조정
Local only: 로컬에만 있음
Lock: 잠금
Lock Server: 서버 잠금
Lock the remote server to prevent synchronization with other devices.: 다른 기기와의 동기화를 방지하기 위해 원격 서버를 잠급니다.
@@ -306,6 +472,9 @@ logPane:
pause: 일시 정지
title: Self-hosted LiveSync 로그
wrap: 줄 바꿈
Lower (${local} < ${remote}): 더 낮음 (${local} < ${remote})
Maintenance Commands: 유지보수 명령
Maintenance mode: 유지보수 모드
Maximum delay for batch database updating: 일괄 데이터베이스 업데이트 최대 지연
Maximum file size: 최대 파일 크기
Maximum Incubating Chunk Size: 임시 보관 청크의 최대 크기
@@ -318,6 +487,7 @@ Memory cache size (by total items): 메모리 캐시 크기 (총 항목 수)
Merge: 병합
Minimum delay for batch database updating: 일괄 데이터베이스 업데이트 최소 지연
Minimum interval for syncing: 동기화 최소 간격
Mixed: 혼합
moduleCheckRemoteSize:
logCheckingStorageSizes: 스토리지 크기 확인 중
logCurrentStorageSize: "원격 스토리지 크기: ${measuredSize}"
@@ -401,7 +571,6 @@ moduleLocalDatabase:
moduleLog:
showLog: 로그 표시
moduleMigration:
docUri: https://github.com/vrtmrz/obsidian-livesync/blob/main/README.md#how-to-use
fix0256:
buttons:
checkItLater: 나중에 확인
@@ -461,19 +630,6 @@ moduleMigration:
___참고: 설정이 변경되고 데이터베이스를 다시 불러오기 전까지는 동기화가 불가능합니다.___
___참고2: 청크는 변경이 불가능한 구조이므로, 메타데이터와 차이점만 가져올 수 있습니다.___
msgInitialSetup: |-
이 기기는 **아직 초기 설정이 완료되지 않았습니다**. 지금부터 설정 과정을 안내해 드리겠습니다.
모든 대화 내용은 클립보드에 복사할 수 있습니다. 나중에 참고하려면 Obsidian 노트에 붙여넣거나 번역 도구를 활용해 번역하셔도 됩니다.
먼저, **Setup URI**를 가지고 계신가요?
참고: Setup URI가 무엇인지 잘 모르시겠다면 [문서](${URI_DOC})를 참고해 주세요.
msgRecommendSetupUri: |-
Setup URI를 생성해 사용하는 것을 강력히 권장합니다.
Setup URI가 무엇인지 잘 모르시겠다면 [문서](${URI_DOC})를 참고해 주세요. 중요한 내용이니 꼭 확인하시기 바랍니다.
직접 수동 설정을 진행하시겠습니까?
msgSinceV02321: |-
v0.23.21부터 Self-hosted LiveSync의 기본 동작 방식과 데이터베이스 구조가 변경되었습니다. 변경 내용은 다음과 같습니다:
@@ -494,38 +650,46 @@ moduleMigration:
optionEnableBoth: 둘 다 활성화
optionEnableFilenameCaseInsensitive: "#1만 활성화"
optionEnableFixedRevisionForChunks: "#2만 활성화"
optionHaveSetupUri: 예, 있습니다
optionKeepPreviousBehaviour: 이전 동작 유지
optionManualSetup: 모든 것을 수동으로 설정
optionNoAskAgain: 아니요 (나중에 다시 물어보기)
optionNoSetupUri: 아니요, 없습니다
optionRemindNextLaunch: 다음 시작 시 알림
optionSetupViaP2P: "%{short_p2p_sync}를 사용하여 설정"
optionSetupWizard: 설정 마법사로 안내
optionYesFetchAgain: 예 (다시 가져오기)
titleCaseSensitivity: 대소문자 구분
titleRecommendSetupUri: Setup URI 사용 권장
titleWelcome: Self-hosted LiveSync에 오신 것을 환영합니다
moduleObsidianMenu:
replicate: 복제
More actions: 추가 작업
"Mostly Complete: Decision Required": "거의 완료: 결정이 필요합니다"
Move remotely deleted files to the trash, instead of deleting.: 원격에서 삭제된 파일을 삭제하는 대신 휴지통으로 이동합니다.
My remote server is already set up. I want to join this device.: 원격 서버가 이미 설정되어 있습니다. 이 기기를 참여시키고 싶습니다.
Name: 이름
Network warning style: 네트워크 경고 표시 방식
NEW: 신규
New Remote: 새 원격
Newer (${diff}): 더 새로움 (${diff})
No checks have been performed yet.: 아직 검사를 수행하지 않았습니다.
No connected device information found. Cancelling Garbage Collection.: 연결된 기기 정보를 찾을 수 없습니다. 가비지 컬렉션을 취소합니다.
No Connection: 연결 없음
No devices available. Waiting for other devices to connect...: 사용 가능한 기기가 없습니다. 다른 기기가 연결되기를 기다리는 중입니다...
No Items.: 항목이 없습니다.
No limit configured: 제한이 설정되지 않음
NO PREVIEW: 미리 보기 없음
No, please take me back: 아니요, 이전으로 돌아가겠습니다
Node ID: 노드 ID
Node Information Missing: 노드 정보 누락
Non-Synchronising files: 동기화하지 않는 파일
Normal Files: 일반 파일
Not all messages have been translated. And, please revert to "Default" when reporting errors.: 모든 메시지가 번역되지 않았습니다. 오류 신고 시 "기본값"으로 되돌려 주세요.
Not configured: 구성되지 않음
Not now: 나중에
Note that the Group ID is not limited to the generated format; you can use any string as the Group ID.: 그룹 ID는 생성된 형식으로 제한되지 않으며, 어떤 문자열이든 그룹 ID로 사용할 수 있습니다.
Note that you can generate a new Setup URI by running the "Copy settings as a new Setup URI" command in the command palette.: 명령 팔레트에서 "Copy settings as a new Setup URI" 명령을 실행하면 새 Setup URI를 생성할 수 있습니다.
Notify all setting files: 모든 설정 파일 알림
Notify customized: 사용자 설정 알림
Notify when other device has newly customized.: 다른 기기에서 새로운 사용자 설정이 있을 때 알림을 받습니다.
Notify when the estimated remote storage size exceeds on start up: 시작 시 예상 원격 스토리지 크기가 초과되면 알림
Number of batches to process at a time. Defaults to 40. Minimum is 2. This along with batch size controls how many docs are kept in memory at a time.: 한 번에 처리할 배치 개수입니다. 기본값은 40이고 최소값은 2입니다. 배치 크기와 함께 한 번에 메모리에 보관되는 문서 수를 결정합니다.
Number of changes to sync at a time. Defaults to 50. Minimum is 2.: 한 번에 동기화할 변경 사항의 수입니다. 기본값은 50입니다. 최소값은 2입니다.
Obfuscate Properties: 속성 난독화
Obfuscating properties (e.g., path of file, size, creation and modification dates) adds an additional layer of security by making it harder to identify the structure and names of your files and folders on the remote server. This helps protect your privacy and makes it more difficult for unauthorized users to infer information about your data.: "속성(예: 파일 경로, 크기, 생성 및 수정 날짜)을 난독화하면 원격 서버에서 파일과 폴더의 구조 및 이름을 파악하기 어려워져 보안이 한층 강화됩니다. 이는 개인 정보를 보호하는 데 도움이 되며, 권한 없는 사용자가 데이터에 대한 정보를 추측하기 어렵게 만듭니다."
Obsidian version: Obsidian 버전
obsidianLiveSyncSettingTab:
btnApply: 적용
@@ -536,12 +700,10 @@ obsidianLiveSyncSettingTab:
btnEnable: 활성화
btnFix: 수정
btnGotItAndUpdated: 알겠습니다. 업데이트했습니다.
btnNext: 다음
btnStart: 시작
btnTest: 테스트
btnUse: 사용
buttonFetch: 가져오기
buttonNext: 다음
defaultLanguage: 기본값
descConnectSetupURI: 이것은 Setup URI로 Self-hosted LiveSync를 설정하는 권장 방법입니다.
descCopySetupURI: 새 기기 설정에 완벽합니다!
@@ -607,7 +769,6 @@ obsidianLiveSyncSettingTab:
msgEnableCorsChttpd: chttpd.enable_cors 설정
msgEnableEncryptionRecommendation: 종단 간 암호화와 경로 난독화를 활성화하는 것을 권장합니다. 정말로 암호화 없이 계속하시겠습니까?
msgFetchConfigFromRemote: 원격 서버에서 구성을 가져오시겠습니까?
msgGenerateSetupURI: 모든 작업이 완료되었습니다! 다른 기기를 설정하기 위해 Setup URI를 생성하시겠습니까?
msgIfConfigNotPersistent: "서버 설정이 영구적으로 저장되지 않는 환경(예: Docker에서 실행 중)에서는 이곳의 값들이 변경될 수 있습니다. 연결이 가능해지면 서버의 local.ini 파일에서 설정을 수동으로 업데이트해 주세요."
msgInvalidPassphrase: 암호화 패스프레이즈가 유효하지 않을 수 있습니다. 정말로 계속하시겠습니까?
msgNewVersionNote: 업그레이드 알림으로 여기에 오셨나요? 버전 기록을 검토해 주세요. 만족하신다면 버튼을 클릭하세요. 새로운 업데이트 시 다시 안내됩니다.
@@ -649,7 +810,6 @@ obsidianLiveSyncSettingTab:
원격 데이터베이스를 이미 재구축한 경우도 여기에 해당합니다.
## ${OPTION_ONLY_SETTING}
설정만 저장합니다. **주의: 데이터가 손상될 수 있습니다.** 일반적으로는 데이터베이스 재구축이 필요합니다.
msgSelectAndApplyPreset: 마법사를 완료하려면 프리셋 항목을 선택하고 적용해 주세요.
msgSetCorsCredentials: cors.credentials 설정
msgSetCorsOrigins: cors.origins 설정
msgSetMaxDocSize: couchdb.max_document_size 설정
@@ -706,7 +866,6 @@ obsidianLiveSyncSettingTab:
titleActiveRemoteServer: 활성 원격 서버
titleAppearance: 모양
titleConflictResolution: 충돌 해결
titleCongratulations: 축하합니다!
titleCouchDB: CouchDB
titleDeletionPropagation: 삭제 전파
titleEncryptionNotEnabled: 암호화가 활성화되지 않음
@@ -733,11 +892,22 @@ obsidianLiveSyncSettingTab:
titleUpdateThinning: 업데이트 솎아내기
warnCorsOriginUnmatched: ⚠ CORS 출처가 일치하지 않습니다 ${from}->${to}
warnNoAdmin: ⚠ 관리자 권한이 없습니다.
Of course, we can back up the data before proceeding.: 물론 진행하기 전에 데이터를 백업할 수 있습니다.
Off: 꺼짐
Ok: 확인
Old Algorithm: 이전 알고리즘
Older (${diff}): 더 오래됨 (${diff})
Older fallback (Slow, W/O WebAssembly): 이전 대체 방식 (느림, WebAssembly 없음)
On: 켜짐
On the source device, from the command palette, run the 'Show settings as a QR code' command.: 원본 기기에서 명령 팔레트를 열고 'Show settings as a QR code' 명령을 실행합니다.
On the source device, open Obsidian.: 원본 기기에서 Obsidian을 엽니다.
On this device, please keep this Vault open.: 이 기기에서는 이 보관함을 계속 열어 두세요.
On this device, switch to the camera app or use a QR code scanner to scan the displayed QR code.: 이 기기에서 카메라 앱으로 전환하거나 QR 코드 스캐너를 사용해 화면에 표시된 QR 코드를 스캔합니다.
Open: 열기
Open connection: 연결 열기
Open P2P Setup...: P2P 설정 열기...
Open the dialog: 대화상자 열기
Other files: 기타 파일
Overwrite: 덮어쓰기
Overwrite patterns: 덮어쓰기 패턴
Overwrite remote: 원격 덮어쓰기
@@ -770,42 +940,67 @@ P2P:
SyncAlreadyRunning: P2P 동기화가 이미 실행 중입니다.
SyncCompleted: P2P 동기화가 완료되었습니다.
SyncStartedWith: ${name}과의 P2P 동기화가 시작되었습니다.
P2P Configuration: P2P 구성
P2P Status: P2P 상태
paneMaintenance:
markDeviceResolvedAfterBackup: 백업 후 이 기기를 해결됨으로 표시
remoteLockedAndDeviceNotAccepted: 원격 데이터베이스가 잠겨 있으며 이 기기는 아직 승인되지 않았습니다.
remoteLockedResolvedDevice: 원격 데이터베이스가 잠겨 있지만 이 기기는 이미 승인되었습니다.
unlockDatabaseReady: 데이터베이스 잠금 해제
Passphrase: 패스프레이즈
Passphrase is required.: 패스프레이즈가 필요합니다.
Passphrase of sensitive configuration items: 민감한 구성 항목의 패스프레이즈
password: 비밀번호
Password: 비밀번호
Paste a connection string: 연결 문자열 붙여넣기
Paste the Setup URI generated from one of your active devices.: 사용 중인 기기 중 하나에서 생성한 Setup URI를 붙여 넣으세요.
Path: 경로
Path Obfuscation: 경로 난독화
Patterns to match files for overwriting instead of merging: 병합 대신 덮어쓸 파일을 판별하는 패턴
Patterns to match files for syncing: 동기화할 파일을 판별하는 패턴
"Peer ID:": "피어 ID:"
Peer to Peer Replicator: Peer to Peer 복제기
Peer-to-Peer only: Peer-to-Peer 전용
Peer-to-Peer Synchronisation: 피어 투 피어(P2P) 동기화
Peers: 피어
Per-file-saved customization sync: 파일별 저장 사용자 설정 동기화
Perform: 실행
Perform cleanup: 정리 실행
Perform Garbage Collection: 가비지 컬렉션 실행
Perform Garbage Collection to remove unused chunks and reduce database size.: 사용하지 않는 청크를 제거하고 데이터베이스 크기를 줄이기 위해 가비지 컬렉션을 실행합니다.
Periodic Sync interval: 주기적 동기화 간격
PERMANENT: 영구
Pick a file to resolve conflict: 충돌을 해결할 파일 선택
Pick a file to show history: 기록을 표시할 파일 선택
Please be aware that the End-to-End Encryption passphrase is not validated until the synchronisation process actually commences. This is a security measure designed to protect your data.: 종단 간 암호화 패스프레이즈는 동기화가 실제로 시작되기 전까지 검증되지 않는다는 점에 유의해 주세요. 이는 데이터를 보호하기 위한 보안 조치입니다.
Please configure your end-to-end encryption settings.: 종단 간 암호화 설정을 구성해 주세요.
Please disable 'Read chunks online' in settings to use Garbage Collection.: 가비지 컬렉션을 사용하려면 설정에서 'Read chunks online'을 비활성화해 주세요.
Please enable 'Compute revisions for chunks' in settings to use Garbage Collection.: 가비지 컬렉션을 사용하려면 설정에서 'Compute revisions for chunks'를 활성화해 주세요.
Please enter the CouchDB server information below.: 아래에 CouchDB 서버 정보를 입력해 주세요.
Please enter the details required to connect to your S3/MinIO/R2 compatible object storage service.: S3/MinIO/R2 호환 오브젝트 스토리지 서비스에 연결하는 데 필요한 정보를 입력해 주세요.
Please enter the Peer-to-Peer Synchronisation information below.: 아래에 Peer-to-Peer 동기화 정보를 입력해 주세요.
Please enter the Setup URI that was generated during server installation or on another device, along with the vault passphrase.: 서버 설치 과정이나 다른 기기에서 생성된 Setup URI를 보관함 패스프레이즈와 함께 입력해 주세요.
Please follow the steps below to import settings from your existing device.: 기존 기기에서 설정을 가져오려면 아래 단계를 따라 주세요.
PLEASE NOTE: 유의 사항
Please select 'Cancel' explicitly to cancel this operation.: 이 작업을 취소하려면 반드시 "취소"를 명시적으로 선택해 주세요.
Please select a method to import the settings from another device.: 다른 기기에서 설정을 가져올 방법을 선택해 주세요.
Please select an active P2P remote configuration to change P2P sync targets.: P2P 동기화 대상을 변경하려면 활성 P2P 원격 구성을 선택해 주세요.
Please select an option to proceed: 계속하려면 항목을 선택해 주세요
Please select the button below to restart and proceed to the data fetching confirmation.: 아래 버튼을 선택하면 재시작 후 데이터 가져오기 확인 단계로 진행합니다.
Please select the button below to restart and proceed to the final confirmation.: 아래 버튼을 선택하면 재시작 후 최종 확인 단계로 진행합니다.
Please select the type of server to which you are connecting.: 연결할 서버 유형을 선택해 주세요.
Please select your situation.: 현재 상황을 선택해 주세요.
Please set device name to identify this device. This name should be unique among your devices. While not configured, we cannot enable this feature.: 이 기기를 식별할 기기 이름을 설정해 주세요. 이 이름은 기기 간에 고유해야 합니다. 설정하기 전까지는 이 기능을 활성화할 수 없습니다.
Please set this device name: 이 기기의 이름을 설정해 주세요
Please understand that this is intended behaviour.: 이는 의도된 동작이라는 점을 이해해 주세요.
Plug-in version: 플러그인 버전
Plugins: 플러그인
Prepare the 'report' to create an issue: 이슈 생성을 위한 '보고서' 준비
Presets: 프리셋
Prevent fetching configuration from server: 서버에서 구성 가져오기 방지
Proceed: 진행
Proceed Garbage Collection: 가비지 컬렉션 계속
Proceed to the next step.: 다음 단계로 진행합니다.
Proceed with Setup URI: Setup URI로 계속
Proceeding with Garbage Collection, ignoring missing nodes.: 누락된 노드를 무시하고 가비지 컬렉션을 계속 진행합니다.
Proceeding with Garbage Collection.: 가비지 컬렉션을 진행합니다.
@@ -869,15 +1064,22 @@ RedFlag:
Title: 원격 구성 가져오기
Reduces storage space by discarding all non-latest revisions. This requires the same amount of free space on the remote server and the local client.: 최신 버전이 아닌 모든 리비전을 제거하여 저장 공간을 줄입니다. 이 작업을 수행하려면 원격 서버와 로컬 클라이언트에 동일한 양의 여유 공간이 필요합니다.
Reducing the frequency with which on-disk changes are reflected into the DB: 디스크 변경 사항이 데이터베이스에 반영되는 빈도를 줄입니다
Refresh: 새로 고침
Region: 지역
Relay settings: 중계 서버 설정
Reload: 다시 불러오기
Remediation: 복구 조치
Remediation Setting Changed: 복구 설정이 변경됨
Remote Database Tweak (In sunset): 원격 데이터베이스 조정 (폐기 예정)
Remote Databases: 원격 데이터베이스
Remote name: 원격 이름
Remote only: 원격에만 있음
Remote server type: 원격 서버 유형
Remote Type: 원격 유형
Rename: 이름 바꾸기
Replicate now: 지금 복제
Replicating: 복제 중
Replicating...: 복제 중입니다...
Replicator:
Dialogue:
Locked:
@@ -917,6 +1119,7 @@ Resend all chunks to the remote.: 모든 청크를 원격으로 다시 보냅니
Reset: 재설정
Reset all: 모두 재설정
Reset all journal counter: 모든 저널 카운터 재설정
Reset and Resume Synchronisation: 동기화 초기화 후 재개
Reset journal received history: 저널 수신 기록 재설정
Reset journal sent history: 저널 송신 기록 재설정
Reset notification threshold and check the remote database usage: 알림 임계값을 초기화하고 원격 데이터베이스 사용량 확인
@@ -929,14 +1132,26 @@ Resolve All: 모두 해결
Resolve all conflicted files: 충돌한 모든 파일 해결
Resolve All conflicted files by the newer one: 충돌한 모든 파일을 최신 버전으로 해결
"Resolve all conflicted files by the newer one. Caution: This will overwrite the older one, and cannot resurrect the overwritten one.": "충돌한 모든 파일을 더 최신 버전으로 해결합니다. 주의: 이전 버전은 덮어써지며 복원할 수 없습니다."
Restart and Fetch Data: 재시작 후 데이터 가져오기
Restart and Initialise Server: 재시작 후 서버 초기화
Restart Now: 지금 재시작
Restarting Obsidian is strongly recommended. Until restart, some changes may not take effect, and display may be inconsistent. Are you sure to restart now?: Obsidian을 재시작하는 것을 강력히 권장합니다. 재시작하기 전까지는 일부 변경 사항이 적용되지 않거나 화면이 일관되지 않게 표시될 수 있습니다. 지금 재시작하시겠습니까?
Restore or reconstruct local database from remote.: 원격에서 로컬 데이터베이스를 복원하거나 재구축합니다.
Rev: 리비전
Revert changes: 변경 사항 되돌리기
Revoke: 철회
Room ID: 룸 ID
"Room ID suffix:": "룸 ID 접미사:"
Run Doctor: 진단 실행
S3/MinIO/R2 Configuration: S3/MinIO/R2 구성
S3/MinIO/R2 Object Storage: S3/MinIO/R2 객체 스토리지
Same: 동일
Same or local only: 동일하거나 로컬에만 있음
Save and Apply: 저장 후 적용
Save settings to a markdown file. You will be notified when new settings arrive. You can set different files by the platform.: 설정을 마크다운 파일에 저장합니다. 새로운 설정이 도착하면 알림을 받게 됩니다. 플랫폼별로 다른 파일을 설정할 수 있습니다.
Saving will be performed forcefully after this number of seconds.: 이 시간(초) 후에 강제로 저장이 수행됩니다.
Scan a QR Code (Recommended for mobile): QR 코드 스캔(모바일 권장)
Scan changes: 변경 사항 검사
Scan changes on customization sync: 사용자 설정 동기화 시 변경 사항 검색
Scan customization automatically: 사용자 설정 자동 검색
Scan customization before replicating.: 복제하기 전에 사용자 설정을 검색합니다.
@@ -945,17 +1160,28 @@ Scan customization periodically: 주기적으로 사용자 설정 검색
Scan for Broken files: 손상된 파일 검사
Scan for hidden files before replication: 복제 전 숨김 파일 검사
Scan hidden files periodically: 주기적으로 숨김 파일 검사
Scan QR Code: QR 코드 스캔
Scan the QR code displayed on an active device using this device's camera.: 이 기기의 카메라로 사용 중인 기기에 표시된 QR 코드를 스캔하세요.
Schedule and Restart: 예약 후 재시작
Scram Switches: 긴급 정지 스위치
Scram!: 긴급 정지
Seconds, 0 to disable: 초 단위, 0으로 설정하면 비활성화
Seconds. Saving to the local database will be delayed until this value after we stop typing or saving.: 초 단위입니다. 타이핑이나 저장을 중단한 후 이 시간동안 로컬 데이터베이스 저장이 지연됩니다.
Secret Access Key: 시크릿 액세스 키
Secret Key: 시크릿 키
Select active P2P remote: 활성 P2P 원격 선택
Select All Shiny: 새 항목 모두 선택
Select Flagged Shiny: 플래그된 새 항목 선택
Select P2P remote...: P2P 원격 선택...
Select the database adapter to use.: 사용할 데이터베이스 어댑터를 선택합니다.
Selecting this option will result in the current data on this device being used to initialise the server. Any existing data on the server will be completely overwritten.: 이 옵션을 선택하면 이 기기의 현재 데이터로 서버를 초기화합니다. 서버에 있는 기존 데이터는 모두 완전히 덮어써집니다.
Selecting this option will result in this device joining the existing server. You need to fetching the existing synchronisation data from the server to this device.: 이 옵션을 선택하면 이 기기가 기존 서버에 참여합니다. 서버에 있는 기존 동기화 데이터를 이 기기로 가져와야 합니다.
Selective: 선택적
Send: 보내기
Send chunks: 청크 보내기
SENDING: 전송 중
Server URI: 서버 URI
SESSION: 세션
Setting:
GenerateKeyPair:
Desc: |+
@@ -1003,6 +1229,8 @@ Setting:
SettingTab:
Message:
AskRebuild: 변경 사항을 적용하려면 원격 데이터베이스에서 가져와야 합니다. 계속 진행하시겠습니까?
"Setup Complete: Preparing to Fetch Synchronisation Data": "설정 완료: 동기화 데이터를 가져올 준비 중"
"Setup Complete: Preparing to Initialise Server": "설정 완료: 서버를 초기화할 준비 중"
Setup URI dialog cancelled.: Setup URI 대화 상자가 취소되었습니다.
Setup:
Apply:
@@ -1098,6 +1326,7 @@ Setup:
PlaceholderPassphrase: 보관함 패스프레이즈를 입력하세요
Title: Setup URI 입력
ValidInfo: Setup URI가 유효하며 사용할 준비가 되었습니다.
Setup-URI: Setup-URI
Should we keep folders that don't have any files inside?: 내부에 파일이 없는 폴더를 유지하시겠습니까?
Should we only check for conflicts when a file is opened?: 파일을 열 때만 충돌을 확인하시겠습니까?
Should we prompt you about conflicting files when a file is opened?: 파일을 열 때 충돌하는 파일에 대해 알림을 표시하시겠습니까?
@@ -1111,18 +1340,31 @@ Show status icon instead of file warnings banner: 파일 경고 배너 대신
Show status inside the editor: 편집기 내부에 상태 표시
Show status on the status bar: 상태 표시줄에 상태 표시
Show verbose log. Please enable if you report an issue.: 자세한 로그를 표시합니다. 문제를 신고하는 경우 활성화해 주세요.
Signaling Server Connection: 시그널링 서버 연결
Signalling Status: 시그널링 상태
Skip and close: 건너뛰고 닫기
Snippets: 스니펫
"Some devices have differing progress values (max: ${maxProgress}, min: ${minProgress}).\nThis may indicate that some devices have not completed synchronisation, which could lead to conflicts. Strongly recommend confirming that all devices are synchronised before proceeding.": |-
일부 기기의 진행 값이 다릅니다(최대: ${maxProgress}, 최소: ${minProgress}).
이는 일부 기기가 동기화를 완료하지 않았음을 의미할 수 있으며, 충돌로 이어질 수 있습니다. 계속 진행하기 전에 모든 기기가 동기화되었는지 반드시 확인하는 것을 강력히 권장합니다.
Start Broadcasting: 브로드캐스트 시작
Start change-broadcasting on Connect: 연결 시 변경 사항 브로드캐스트 시작
Start Sync & Close: 동기화 시작 후 닫기
Starts synchronisation when a file is saved.: 파일이 저장될 때 동기화를 시작합니다.
Stat: 상태
Stats: 통계
Stop ⚡: 중지 ⚡
Stop Broadcasting: 브로드캐스트 중지
Stop reflecting database changes to storage files.: 데이터베이스 변경 사항을 스토리지 파일에 반영하는 것을 중단합니다.
Stop watching for file changes.: 파일 변경 사항 감시를 중단합니다.
Storage -> Database: 스토리지 -> 데이터베이스
Strongly Recommended: 적극 권장
Suppress notification of hidden files change: 숨김 파일 변경 알림 억제
Suspend database reflecting: 데이터베이스 반영 일시 중단
Suspend file watching: 파일 감시 일시 중단
Switch to IDB: IDB로 전환
Switch to IndexedDB: IndexedDB로 전환
Sync: 동기화
Sync after merging file: 파일 병합 후 동기화
Sync automatically after merging files: 파일 병합 후 자동으로 동기화
Sync Mode: 동기화 모드
@@ -1130,30 +1372,61 @@ Sync on Editor Save: 편집기 저장 시 동기화
Sync on File Open: 파일 열기 시 동기화
Sync on Save: 저장 시 동기화
Sync on Startup: 시작 시 동기화
Sync once: 한 번 동기화
Synchronisation utilising journal files. You must have set up an S3/MinIO/R2 compatible object storage.: 저널 파일을 활용하는 동기화 방식입니다. S3/MinIO/R2 호환 객체 스토리지를 미리 구성해 두어야 합니다.
Synchronising files: 동기화할 파일
Syncing: 동기화
Syncing...: 동기화 중입니다...
Target patterns: 대상 패턴
Test Settings and Continue: 설정 테스트 후 계속
Testing only - Resolve file conflicts by syncing newer copies of the file, this can overwrite modified files. Be Warned.: 테스트 전용 - 파일의 새로운 사본을 동기화하여 파일 충돌을 해결하며, 수정된 파일을 덮어쓸 수 있습니다. 주의하세요.
The connection to the server has been configured successfully. As the next step,: 서버 연결이 성공적으로 구성되었습니다. 다음 단계로,
The delay for consecutive on-demand fetches: 연속 청크 요청 간 대기 시간
The files in this Vault are almost identical to the server's.: 이 보관함의 파일은 서버의 파일과 거의 동일합니다.
"The following accepted nodes are missing its node information:\n- ${missingNodes}\n\nThis indicates that they have not been connected for some time or have been left on an older version.\nIt is preferable to update all devices if possible. If you have any devices that are no longer in use, you can clear all accepted nodes by locking the remote once.": |-
다음 승인된 노드에는 노드 정보가 없습니다:
- ${missingNodes}
이는 해당 노드가 한동안 연결되지 않았거나 이전 버전에 머물러 있음을 의미합니다.
가능하다면 먼저 모든 기기를 업데이트하는 것이 좋습니다. 더 이상 사용하지 않는 기기가 있다면 원격을 한 번 잠가 승인된 노드를 모두 정리할 수 있습니다.
The Group ID and passphrase are used to identify your group of devices. Make sure to use the same Group ID and passphrase on all devices you want to synchronise.: 그룹 ID와 패스프레이즈는 기기 그룹을 식별하는 데 사용됩니다. 동기화하려는 모든 기기에서 동일한 그룹 ID와 패스프레이즈를 사용해야 합니다.
The Hash algorithm for chunk IDs: 청크 ID용 해시 알고리즘
The IndexedDB adapter often offers superior performance in certain scenarios, but it has been found to cause memory leaks when used with LiveSync mode. When using LiveSync mode, please use IDB adapter instead.: IndexedDB 어댑터는 특정 상황에서 더 나은 성능을 보이는 경우가 많지만, LiveSync 모드에서 사용하면 메모리 누수를 일으키는 것으로 확인되었습니다. LiveSync 모드를 사용할 때는 IDB 어댑터를 사용해 주세요.
the latest synchronisation data will be downloaded from the server to this device.: 서버의 최신 동기화 데이터를 이 기기로 내려받습니다.
the local database, that is to say the synchronisation information, must be reconstituted.: 로컬 데이터베이스, 즉 동기화 정보를 다시 구성해야 합니다.
The maximum duration for which chunks can be incubated within the document. Chunks exceeding this period will graduate to independent chunks.: 청크를 문서 안에 임시 보관할 수 있는 최대 기간입니다. 이 기간을 넘긴 청크는 독립된 청크로 분리됩니다.
The maximum number of chunks that can be incubated within the document. Chunks exceeding this number will immediately graduate to independent chunks.: 문서 안에 임시 보관할 수 있는 청크의 최대 개수입니다. 이 개수를 넘긴 청크는 즉시 독립된 청크로 분리됩니다.
The maximum total size of chunks that can be incubated within the document. Chunks exceeding this size will immediately graduate to independent chunks.: 문서 안에 임시 보관할 수 있는 청크의 최대 총 크기입니다. 이 크기를 넘긴 청크는 즉시 독립된 청크로 분리됩니다.
The minimum interval for automatic synchronisation on event.: 이벤트 발생 시 자동 동기화의 최소 간격입니다.
The remote is already set up, and the configuration is compatible (or got compatible by this operation).: 원격은 이미 설정되어 있으며, 구성도 호환됩니다(또는 이번 작업으로 호환되었습니다).
The Setup-URI does not appear to be valid. Please check that you have copied it correctly.: Setup-URI가 유효하지 않은 것으로 보입니다. 올바르게 복사했는지 확인해 주세요.
The Setup-URI is valid and ready to use.: Setup-URI가 유효하며 사용할 준비가 되었습니다.
the single, authoritative master copy: 유일하고 확실한 원본이 됩니다
the synchronisation data on the server will be built based on the current data on this device.: 이 기기의 현재 데이터를 바탕으로 서버의 동기화 데이터를 구축합니다.
Themes: 테마
There is a way to resolve this on other devices.: 다른 기기에서 이를 해결할 방법이 있습니다.
There may be differences between the files in this Vault and the server.: 이 보관함의 파일과 서버의 파일 사이에 차이가 있을 수 있습니다.
Therefore, we ask that you exercise extreme caution when configuring server information manually. If an incorrect passphrase is entered, the data on the server will become corrupted.: 따라서 서버 정보를 수동으로 구성할 때에는 각별히 주의해 주시기 바랍니다. 잘못된 패스프레이즈를 입력하면 서버의 데이터가 손상됩니다.
This can isolate your connections between devices. Use the same Room ID for the same devices.: 이를 통해 기기 간 연결을 서로 격리할 수 있습니다. 같은 기기들끼리는 동일한 룸 ID를 사용하세요.
This device: 이 기기
This device name: 이 기기의 이름
This feature enables direct synchronisation between devices. No server is required, but both devices must be online at the same time for synchronisation to occur, and some features may be limited. Internet connection is only required to signalling (detecting peers) and not for data transfer.: 기기 간에 직접 동기화하는 기능입니다. 서버는 필요 없지만 동기화가 이루어지려면 두 기기가 동시에 온라인 상태여야 하며, 일부 기능은 제한될 수 있습니다. 인터넷 연결은 시그널링(피어 탐지)에만 필요하고 데이터 전송에는 필요하지 않습니다.
This is an advanced option for users who do not have a URI or who wish to configure detailed settings.: URI가 없거나 세부 설정을 직접 구성하려는 사용자를 위한 고급 옵션입니다.
This is an extremely powerful operation. We strongly recommend that you copy your Vault folder to a safe location.: 이는 매우 강력한 작업입니다. 보관함 폴더를 안전한 위치에 복사해 두시기를 강력히 권장합니다.
This is the most suitable synchronisation method for the design. All functions are available. You must have set up a CouchDB instance.: 이 설계에 가장 적합한 동기화 방식입니다. 모든 기능을 사용할 수 있습니다. CouchDB 인스턴스를 미리 구성해 두어야 합니다.
This passphrase will not be copied to another device. It will be set to `Default` until you configure it again.: 이 패스프레이즈는 다른 기기로 복사되지 않습니다. 다시 구성할 때까지 `기본값`으로 설정됩니다.
This password is used to encrypt the connection. Use something long enough.: 이 비밀번호는 연결을 암호화하는 데 사용됩니다. 충분히 긴 값을 사용하세요.
This procedure will first delete all existing synchronisation data from the server. Following this, the server data will be completely rebuilt, using the current state of your Vault on this device (including its local database) as: 이 절차는 먼저 서버에서 기존 동기화 데이터를 모두 삭제합니다. 그다음 이 기기에 있는 보관함의 현재 상태(로컬 데이터베이스 포함)를 바탕으로 서버 데이터를 완전히 재구축합니다. 즉, 이 기기의 데이터가
This setting must be the same even when connecting to multiple synchronisation destinations.: 이 설정은 여러 동기화 대상에 연결하는 경우에도 동일해야 합니다.
This Vault is empty, or contains only new files that are not on the server.: 이 보관함은 비어 있거나, 서버에 없는 새 파일만 포함하고 있습니다.
This will rebuild the local database on this device using the most recent data from the server. This action is designed to resolve synchronisation inconsistencies and restore correct functionality.: 서버의 최신 데이터를 사용해 이 기기의 로컬 데이터베이스를 재구축합니다. 이 작업은 동기화 불일치를 해결하고 정상적인 동작을 복구하기 위한 것입니다.
This will recreate chunks for all files. If there were missing chunks, this may fix the errors.: 모든 파일의 청크를 다시 생성합니다. 누락된 청크가 있었다면 이 작업으로 오류가 해결될 수 있습니다.
To minimise the creation of new conflicts: 새로운 충돌 발생을 최소화하기 위해
Transfer Tweak: 전송 조정
TURN Credential: TURN 자격 증명
TURN server settings are only necessary if you are behind a strict NAT or firewall that prevents direct P2P connections. In most cases, you can leave these fields blank.: TURN 서버 설정은 직접적인 P2P 연결을 막는 엄격한 NAT나 방화벽 뒤에 있는 경우에만 필요합니다. 대부분의 경우 이 항목들은 비워 두어도 됩니다.
TURN Server URLs (comma-separated): TURN 서버 URL (쉼표로 구분)
TURN Username: TURN 사용자 이름
TweakMismatchResolve:
Action:
DisableAutoAcceptCompatible: 자동 수용 비활성화
@@ -1523,21 +1796,36 @@ Ui:
ProceedP2P: Peer-to-Peer 전용 설정으로 계속
Title: 서버 정보 입력
Unique name between all synchronized devices. To edit this setting, please disable customization sync once.: 모든 동기화된 기기 간 고유 이름입니다. 이 설정을 편집하려면 사용자 설정 동기화를 한 번 비활성화해 주세요.
Unless you are certain, selecting this options is bit dangerous. It assumes that the server configuration is compatible with this device. If this is not the case, data loss may occur. Please ensure you know what you are doing.: 확신이 없다면 이 옵션을 선택하는 것은 다소 위험합니다. 이 옵션은 서버 구성이 이 기기와 호환된다고 가정합니다. 그렇지 않은 경우 데이터가 손실될 수 있습니다. 무엇을 하려는지 충분히 이해한 상태에서 진행해 주세요.
Updating list...: 목록을 갱신하는 중입니다...
URL: URL
Use a custom passphrase: 사용자 지정 패스프레이즈 사용
Use a Setup URI (Recommended): Setup URI 사용(권장)
Use Custom HTTP Handler: 커스텀 HTTP 핸들러 사용
Use Diagnostic RTCPeerConnection for statistics: 통계에 진단용 RTCPeerConnection 사용
Use dynamic iteration count: 동적 반복 횟수 사용
Use internal API: 내부 API 사용
Use Internal API: 내부 API 사용
Use JWT Authentication: JWT 인증 사용
Use Path-Style Access: 경로 방식(Path-Style) 접근 사용
Use Random Number: 임의의 숫자 사용
Use Segmented-splitter: 의미 기반 분할 사용
Use splitting-limit-capped chunk splitter: 분할 제한 상한 청크 분할기 사용
Use the trash bin: 휴지통 사용
Use timeouts instead of heartbeats: 하트비트 대신 타임아웃 사용
Use vrtmrz's relay: vrtmrz의 중계 서버 사용
username: 사용자명
Username: 사용자명
Verbose Log: 자세한 로그
Verify all: 모두 검증
Verify and repair all files: 모든 파일 검증 및 복구
Warning! This will have a serious impact on performance. And the logs will not be synchronised under the default name. Please be careful with logs; they often contain your confidential information.: 경고! 이는 성능에 심각한 영향을 미칩니다. 로그는 기본 이름으로 동기화되지 않습니다. 로그에는 종종 기밀 정보가 포함되어 있으므로 주의해 주세요.
WATCHING: 감시 중
We can not use "/" to the device name: 기기 이름에는 "/"를 사용할 수 없습니다
We can use only Secure (HTTPS) connections on Obsidian Mobile.: Obsidian 모바일에서는 보안(HTTPS) 연결만 사용할 수 있습니다.
We cannot change the device name while this feature is enabled. Please disable this feature to change the device name.: 이 기능이 활성화되어 있는 동안에는 기기 이름을 변경할 수 없습니다. 기기 이름을 변경하려면 이 기능을 비활성화하세요.
We have to configure the device name: 기기 이름을 설정해야 합니다
We recommend that you copy your Vault folder to a safe location. This will provide a safeguard in case a large number of conflicts arise, or if you accidentally synchronise with an incorrect destination.: 보관함 폴더를 안전한 위치에 복사해 두시기를 권장합니다. 충돌이 대량으로 발생하거나 실수로 잘못된 대상과 동기화한 경우에 대비할 수 있습니다.
We will now guide you through a few questions to simplify the synchronisation setup.: 동기화 설정을 간단히 마칠 수 있도록 몇 가지 질문으로 안내해 드리겠습니다.
We will now proceed with the server configuration.: 이제 서버 구성을 진행하겠습니다.
Welcome to Self-hosted LiveSync: Self-hosted LiveSync에 오신 것을 환영합니다
@@ -1549,3 +1837,6 @@ xxhash64 (Fastest): xxhash64 (가장 빠름)
Yes, I want to add this device to my existing synchronisation: 예, 이 기기를 기존 동기화 구성에 추가하겠습니다
Yes, I want to set up a new synchronisation: 예, 새 동기화를 설정하겠습니다
You are adding this device to an existing synchronisation setup.: 이 기기를 기존 동기화 구성에 추가합니다.
You can configure in the Obsidian Plugin Settings.: Obsidian 플러그인 설정에서 구성할 수 있습니다.
You should create a new synchronisation destination and rebuild your data there.: 새로운 동기화 대상을 만들고 그곳에서 데이터를 재구축해야 합니다.
You should perform this operation only in exceptional circumstances, such as when the server data is completely corrupted, when changes on all other devices are no longer needed, or when the database size has become unusually large in comparison to the Vault size.: 이 작업은 서버 데이터가 완전히 손상된 경우, 다른 모든 기기의 변경 사항이 더 이상 필요하지 않은 경우, 또는 데이터베이스 크기가 보관함 크기에 비해 비정상적으로 커진 경우처럼 예외적인 상황에서만 수행해야 합니다.
-16
View File
@@ -413,7 +413,6 @@ moduleLocalDatabase:
moduleLog:
showLog: Показать лог
moduleMigration:
docUri: https://github.com/vrtmrz/obsidian-livesync/blob/main/README.md#how-to-use
fix0256:
buttons:
checkItLater: Проверить позже
@@ -441,8 +440,6 @@ moduleMigration:
logSetupCancelled: Настройка отменена, Self-hosted LiveSync ожидает вашей настройки!
msgFetchRemoteAgain: Удалённая база данных, похоже, уже была мигрирована.
Конфигурация этого устройства несовместима.
msgInitialSetup: Ваше устройство ещё не настроено. У вас есть Setup URI?
msgRecommendSetupUri: Мы рекомендуем сгенерировать Setup URI.
msgSinceV02321: Начиная с v0.23.21, self-hosted LiveSync изменил поведение и
структуру базы данных.
optionAdjustRemote: Настроить под удалённую
@@ -450,18 +447,10 @@ moduleMigration:
optionEnableBoth: Включить оба
optionEnableFilenameCaseInsensitive: "Включить только #1"
optionEnableFixedRevisionForChunks: "Включить только #2"
optionHaveSetupUri: Да, есть
optionKeepPreviousBehaviour: Сохранить предыдущее поведение
optionManualSetup: Настроить всё вручную
optionNoAskAgain: Нет, спросить снова
optionNoSetupUri: Нет, нет
optionRemindNextLaunch: Напомнить при следующем запуске
optionSetupViaP2P: Использовать short_p2p_sync для настройки
optionSetupWizard: Перейти в мастер настройки
optionYesFetchAgain: Да, загрузить снова
titleCaseSensitivity: Чувствительность к регистру
titleRecommendSetupUri: Рекомендация использовать Setup URI
titleWelcome: Добро пожаловать в Self-hosted LiveSync
moduleObsidianMenu:
replicate: Реплицировать
More actions: Другие действия
@@ -492,12 +481,10 @@ obsidianLiveSyncSettingTab:
btnEnable: Включить
btnFix: Исправить
btnGotItAndUpdated: Понял и обновил.
btnNext: Далее
btnStart: Старт
btnTest: Тест
btnUse: Использовать
buttonFetch: Загрузить
buttonNext: Далее
defaultLanguage: По умолчанию
descConnectSetupURI: Это рекомендуемый способ настройки Self-hosted LiveSync с помощью Setup URI.
descCopySetupURI: Идеально подходит для настройки нового устройства!
@@ -562,7 +549,6 @@ obsidianLiveSyncSettingTab:
msgEnableEncryptionRecommendation: Мы рекомендуем включить сквозное шифрование.
Вы уверены, что хотите продолжить без шифрования?
msgFetchConfigFromRemote: Вы хотите загрузить конфигурацию с удалённого сервера?
msgGenerateSetupURI: Всё готово! Вы хотите сгенерировать Setup URI для настройки других устройств?
msgIfConfigNotPersistent: Если конфигурация сервера непостоянна, значения здесь могут измениться.
msgInvalidPassphrase: Ваша парольная фраза шифрования может быть недействительна.
msgNewVersionNote: Вы пришли из-за уведомления об обновлении? Просмотрите историю версий.
@@ -572,7 +558,6 @@ obsidianLiveSyncSettingTab:
msgObjectStorageWarning: "ПРЕДУПРЕЖДЕНИЕ: Эта функция в разработке."
msgOriginCheck: "Проверка origin: org"
msgRebuildRequired: Требуется перестроение баз данных для применения изменений.
msgSelectAndApplyPreset: Выберите и примените любой пресет для завершения мастера.
msgSetCorsCredentials: Установить cors.credentials
msgSetCorsOrigins: Установить cors.origins
msgSetMaxDocSize: Установить couchdb.max_document_size
@@ -629,7 +614,6 @@ obsidianLiveSyncSettingTab:
titleActiveRemoteServer: Активный удалённый сервер
titleAppearance: Внешний вид
titleConflictResolution: Разрешение конфликтов
titleCongratulations: Поздравляем!
titleCouchDB: Сервер CouchDB
titleDeletionPropagation: Распространение удалений
titleEncryptionNotEnabled: Шифрование не включено
File diff suppressed because it is too large Load Diff
-27
View File
@@ -358,7 +358,6 @@ moduleLocalDatabase:
moduleLog:
showLog: 显示日志
moduleMigration:
docUri: https://github.com/vrtmrz/obsidian-livesync/blob/main/docs/zh/README_zh.md#%E5%A6%82%E4%BD%95%E4%BD%BF%E7%94%A8
fix0256:
buttons:
checkItLater: 稍后检查
@@ -420,19 +419,6 @@ moduleMigration:
___注意:在更改配置并再次获取数据库之前,我们无法进行同步。___
___注意2:chunks 是完全不可变的,我们只能获取元数据和差异
msgInitialSetup: |-
您的设备**尚未设置**。让我引导您完成设置过程。
请记住,每个对话框内容都可以复制到剪贴板。如果以后需要参考,可以将其粘贴到 Obsidian 的笔记中。您也可以使用翻译工具将其翻译成您的语言。
首先,您有**设置 URI** 吗?
注意:如果您不知道这是什么,请参阅[文档](${URI_DOC})
msgRecommendSetupUri: |-
我们强烈建议您生成一个设置 URI 并使用它。
如果您对此不了解,请参阅[文档](${URI_DOC})(再次抱歉,但这很重要)。
您想如何手动设置?
msgSinceV02321: |-
自 v0.23.21 起,Self-hosted LiveSync 更改了默认行为和数据库结构。进行了以下更改:
@@ -453,18 +439,10 @@ moduleMigration:
optionEnableBoth: 启用两者
optionEnableFilenameCaseInsensitive: "仅启用 #1"
optionEnableFixedRevisionForChunks: "仅启用 #2"
optionHaveSetupUri: 是的,我有
optionKeepPreviousBehaviour: 保持以前的行为
optionManualSetup: 全部手动设置
optionNoAskAgain: 不,请稍后再次询问
optionNoSetupUri: 不,我没有
optionRemindNextLaunch: 下次启动时提醒我
optionSetupViaP2P: Use %{short_p2p_sync} to set up
optionSetupWizard: 带我进入设置向导
optionYesFetchAgain: 是的,再次获取
titleCaseSensitivity: 大小写敏感性
titleRecommendSetupUri: 推荐使用设置 URI
titleWelcome: 欢迎使用 Self-hosted LiveSync
moduleObsidianMenu:
replicate: 复制
More actions: 更多操作
@@ -490,12 +468,10 @@ obsidianLiveSyncSettingTab:
btnEnable: 启用
btnFix: 修复
btnGotItAndUpdated: 我明白了并且已更新
btnNext: 下一步
btnStart: 开始
btnTest: 测试
btnUse: 使用
buttonFetch: 获取
buttonNext: 下一步
defaultLanguage: 默认语言
descConnectSetupURI: 这是使用设置 URI 设置 Self-hosted LiveSync 的推荐方法
descCopySetupURI: 非常适合设置新设备!
@@ -560,7 +536,6 @@ obsidianLiveSyncSettingTab:
msgEnableCorsChttpd: 设置 chttpd.enable_cors
msgEnableEncryptionRecommendation: 建议启用端到端加密和路径混淆。你确定要在未加密的情况下继续吗?
msgFetchConfigFromRemote: 要从远端服务器获取配置吗?
msgGenerateSetupURI: 全部完成!要生成设置 URI 以便配置其他设备吗?
msgIfConfigNotPersistent: 如果服务器配置不是持久的(例如,在 docker 上运行),此处的值可能会更改。一旦能够连接,请更新服务器 local.ini 中的设置
msgInvalidPassphrase: 你的加密密码短语可能无效。你确定要继续吗?
msgNewVersionNote: 因为升级通知来到这里?请查看版本历史。如果您满意,请点击按钮。新的更新将再次提示此信息
@@ -602,7 +577,6 @@ obsidianLiveSyncSettingTab:
这种情况包括您已经重建了远程数据库的情况。
## ${OPTION_ONLY_SETTING}
仅存储设置。**注意:这可能导致数据损坏**;通常需要重建数据库
msgSelectAndApplyPreset: 请选择并应用任一预设项以完成向导。
msgSetCorsCredentials: 设置 cors.credentials
msgSetCorsOrigins: 设置 cors.origins
msgSetMaxDocSize: 设置 couchdb.max_document_size
@@ -659,7 +633,6 @@ obsidianLiveSyncSettingTab:
titleActiveRemoteServer: 活动远程服务器
titleAppearance: 外观
titleConflictResolution: 冲突处理
titleCongratulations: 恭喜!
titleCouchDB: CouchDB 服务器
titleDeletionPropagation: 删除传播
titleEncryptionNotEnabled: 尚未启用加密
+6
View File
@@ -27,6 +27,12 @@ describe("LiveSync-owned translation catalogue", () => {
expect(translateLiveSyncMessage("Active Remote Type")).toBe(englishMessageTranslator("Active Remote Type"));
});
it("directs a compatibility pause to the dedicated review workflow", () => {
expect(translateLiveSyncMessage("Replicator.Message.VersionUpFlash")).toBe(
"Remote synchronisation is paused for compatibility review. Run the 'Review why synchronisation is paused' command for details and available actions."
);
});
it("uses LiveSync-owned provisional English without extending Commonlib's message contract", () => {
expect($msg("This file has unresolved conflicts.")).toBe("This file has unresolved conflicts.");
expect($msg("More actions for ${DEVICE}", { DEVICE: "phone" })).toBe("More actions for phone");
+1
View File
@@ -16,6 +16,7 @@ export {
requestUrl,
sanitizeHTMLToDom,
Setting,
SettingPage,
stringifyYaml,
TAbstractFile,
TextAreaComponent,
@@ -4,7 +4,6 @@ vi.mock("@/deps.ts", () => ({
addIcon: vi.fn(),
diff_match_patch: class DiffMatchPatch {},
normalizePath: vi.fn((path: string) => path),
Notice: class Notice {},
parseYaml: vi.fn(),
Platform: {},
}));
@@ -30,13 +29,10 @@ vi.mock("@/common/types.ts", () => ({
PERIODIC_PLUGIN_SWEEP: 60,
}));
vi.mock("@/common/utils.ts", () => ({
cancelTask: vi.fn(),
EVEN: Symbol("even"),
disposeMemoObject: vi.fn(),
isCustomisationSyncMetadata: vi.fn(),
isPluginMetadata: vi.fn(),
memoIfNotExist: vi.fn(),
memoObject: vi.fn(),
retrieveMemoObject: vi.fn(),
scheduleTask: vi.fn(),
}));
vi.mock("@/common/PeriodicProcessor.ts", () => ({
@@ -55,6 +51,7 @@ vi.mock("@/common/obsidianCommunityPlugins.ts", () => ({
getObsidianCommunityPluginManager: vi.fn(),
}));
import { cancelTask } from "@/common/utils.ts";
import { ConfigSync } from "./CmdConfigSync";
describe("ConfigSync commands", () => {
@@ -93,4 +90,24 @@ describe("ConfigSync commands", () => {
expect(command?.checkCallback?.(false)).toBe(true);
expect(showPluginSyncModal).toHaveBeenCalledOnce();
});
it("cancels the pending configuration Notice before releasing its owned UI", () => {
const notices = { hide: vi.fn() };
const periodicPluginSweepProcessor = { disable: vi.fn() };
const configSync = Object.create(ConfigSync.prototype) as ConfigSync;
Object.assign(configSync, {
core: {
services: {
context: { notices },
},
},
periodicPluginSweepProcessor,
});
configSync.onunload();
expect(cancelTask).toHaveBeenCalledWith("config-sync:updated-configuration");
expect(notices.hide).toHaveBeenCalledWith("config-sync:updated-configuration");
expect(periodicPluginSweepProcessor.disable).toHaveBeenCalledOnce();
});
});
+10 -36
View File
@@ -1,7 +1,6 @@
import { writable } from "svelte/store";
import type PouchDB from "pouchdb-core";
import {
Notice,
type PluginManifest,
parseYaml,
normalizePath,
@@ -53,21 +52,11 @@ import {
import { serialized, shareRunningResult } from "octagonal-wheels/concurrency/lock";
import { LiveSyncCommands } from "@/features/LiveSyncCommands.ts";
import { stripAllPrefixes } from "@vrtmrz/livesync-commonlib/compat/string_and_binary/path";
import {
EVEN,
disposeMemoObject,
isCustomisationSyncMetadata,
isPluginMetadata,
memoIfNotExist,
memoObject,
retrieveMemoObject,
scheduleTask,
} from "@/common/utils.ts";
import { cancelTask, EVEN, isCustomisationSyncMetadata, isPluginMetadata, scheduleTask } from "@/common/utils.ts";
import { PeriodicProcessor } from "@/common/PeriodicProcessor.ts";
import { JsonResolveModal } from "@/features/HiddenFileCommon/JsonResolveModal.ts";
import { QueueProcessor } from "octagonal-wheels/concurrency/processor";
import { pluginScanningCount } from "@vrtmrz/livesync-commonlib/compat/mock_and_interop/stores";
import type ObsidianLiveSyncPlugin from "@/main.ts";
import { base64ToArrayBuffer, base64ToString } from "octagonal-wheels/binary/base64";
import { ConflictResolveModal } from "@/modules/features/InteractiveConflictResolving/ConflictResolveModal.ts";
import { Semaphore } from "octagonal-wheels/concurrency/semaphore";
@@ -82,6 +71,7 @@ import { getObsidianCommunityPluginManager } from "@/common/obsidianCommunityPlu
const d = "\u200b";
const d2 = "\n";
const UPDATED_CONFIGURATION_NOTICE_KEY = "config-sync:updated-configuration";
function serialize(data: PluginDataEx): string {
// For higher performance, create custom plug-in data strings.
@@ -393,8 +383,8 @@ export type PluginDataEx = {
};
export class ConfigSync extends LiveSyncCommands {
constructor(plugin: ObsidianLiveSyncPlugin, core: LiveSyncCore) {
super(plugin, core);
constructor(core: LiveSyncCore) {
super(core);
pluginScanningCount.onChanged((e) => {
const total = e.value;
pluginIsEnumerating.set(total != 0);
@@ -428,7 +418,7 @@ export class ConfigSync extends LiveSyncCommands {
if (this.pluginDialog) {
this.pluginDialog.open();
} else {
this.pluginDialog = new PluginDialogModal(this.app, this.plugin);
this.pluginDialog = new PluginDialogModal(this.app, this.services.context.liveSyncPlugin);
this.pluginDialog.open();
}
}
@@ -440,8 +430,10 @@ export class ConfigSync extends LiveSyncCommands {
}
}
onunload() {
cancelTask(UPDATED_CONFIGURATION_NOTICE_KEY);
this.hidePluginSyncModal();
this.periodicPluginSweepProcessor?.disable();
this.services.context.notices.hide(UPDATED_CONFIGURATION_NOTICE_KEY);
}
addRibbonIcon = this.services.API.addRibbonIcon.bind(this.services.API);
onload() {
@@ -1196,22 +1188,9 @@ export class ConfigSync extends LiveSyncCommands {
});
});
const updatedPluginKey = "popupUpdated-plugins";
scheduleTask(updatedPluginKey, 1000, async () => {
const popup = await memoIfNotExist(updatedPluginKey, () => new Notice(fragment, 0));
//@ts-ignore -- retained for compatibility with Obsidian versions before Notice.messageEl.
const isShown = popup?.noticeEl?.isShown();
if (!isShown) {
memoObject(updatedPluginKey, new Notice(fragment, 0));
}
scheduleTask(updatedPluginKey + "-close", 20000, () => {
const popup = retrieveMemoObject<Notice>(updatedPluginKey);
if (!popup) return;
//@ts-ignore -- retained for compatibility with Obsidian versions before Notice.messageEl.
if (popup?.noticeEl?.isShown()) {
popup.hide();
}
disposeMemoObject(updatedPluginKey);
scheduleTask(UPDATED_CONFIGURATION_NOTICE_KEY, 1000, () => {
this.services.context.notices.show(UPDATED_CONFIGURATION_NOTICE_KEY, fragment, {
durationMs: 20_000,
});
});
}
@@ -1716,8 +1695,6 @@ export class ConfigSync extends LiveSyncCommands {
}
async configureHiddenFileSync(mode: OptionalSyncFeatureMode) {
if (mode == "DISABLE") {
// this.plugin.settings.usePluginSync = false;
// await this.plugin.saveSettings();
await this.core.services.setting.applyPartial(
{
usePluginSync: false,
@@ -1758,9 +1735,6 @@ export class ConfigSync extends LiveSyncCommands {
}
this.services.setting.setDeviceAndVaultName(name);
}
// this.core.settings.usePluginSync = true;
// this.core.settings.useAdvancedMode = true;
// await this.core.saveSettings();
await this.core.services.setting.applyPartial(
{
usePluginSync: true,
+2 -11
View File
@@ -8,7 +8,6 @@ import {
type FilePathWithPrefix,
type LOG_LEVEL,
} from "@vrtmrz/livesync-commonlib/compat/common/types";
import type ObsidianLiveSyncPlugin from "@/main.ts";
import { MARK_DONE } from "@/modules/features/ModuleLog.ts";
import type { LiveSyncCore } from "@/main.ts";
// import { __$checkInstanceBinding } from "@vrtmrz/livesync-commonlib/compat/dev/checks";
@@ -16,13 +15,9 @@ import { createInstanceLogFunction } from "@vrtmrz/livesync-commonlib/compat/ser
let noticeIndex = 0;
export abstract class LiveSyncCommands {
/**
* @deprecated This class is deprecated. Please use core
*/
plugin: ObsidianLiveSyncPlugin;
core: LiveSyncCore;
get app() {
return this.plugin.app;
return this.services.context.app;
}
get settings() {
return this.core.settings;
@@ -34,9 +29,6 @@ export abstract class LiveSyncCommands {
return this.core.services;
}
// id2path(id: DocumentID, entry?: EntryHasPath, stripPrefix?: boolean): FilePathWithPrefix {
// return this.plugin.$$id2path(id, entry, stripPrefix);
// }
async path2id(filename: FilePathWithPrefix | FilePath, prefix?: string): Promise<DocumentID> {
return await this.services.path.path2id(filename, prefix);
}
@@ -45,8 +37,7 @@ export abstract class LiveSyncCommands {
return this.services.path.getPath(entry);
}
constructor(plugin: ObsidianLiveSyncPlugin, core: LiveSyncCore) {
this.plugin = plugin;
constructor(core: LiveSyncCore) {
this.core = core;
this.onBindFunction(this.core, this.core.services);
this._log = createInstanceLogFunction(this.constructor.name, this.services.API);
@@ -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);
@@ -35,7 +35,7 @@ export class LocalDatabaseMaintenance extends LiveSyncCommands {
}
onload(): void | Promise<void> {
// NO OP.
this.plugin.addCommand({
this.services.API.addCommand({
id: "analyse-database",
name: "Analyse Database Usage (advanced)",
icon: "database-search",
@@ -47,7 +47,7 @@ export class LocalDatabaseMaintenance extends LiveSyncCommands {
return true;
},
});
this.plugin.addCommand({
this.services.API.addCommand({
id: "gc-v3",
name: "Garbage Collection V3 (advanced, beta)",
icon: "trash-2",
@@ -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();
}
}
@@ -18,10 +18,13 @@ vi.mock("@vrtmrz/livesync-commonlib/compat/common/utils", async (importOriginal)
});
vi.mock("@/features/LiveSyncCommands", () => ({
LiveSyncCommands: class LiveSyncCommands {
core!: { settings: unknown };
core!: { settings: unknown; services: unknown };
get settings() {
return this.core.settings;
}
get services() {
return this.core.services;
}
},
}));
vi.mock("@/common/events", () => ({
@@ -76,11 +79,13 @@ describe("LocalDatabaseMaintenance prerequisites", () => {
};
const maintenance = Object.create(LocalDatabaseMaintenance.prototype) as LocalDatabaseMaintenance;
Object.assign(maintenance, {
plugin: {
addCommand: vi.fn((command) => commands.push(command)),
},
core: {
settings,
services: {
API: {
addCommand: vi.fn((command) => commands.push(command)),
},
},
},
_isDatabaseReady: vi.fn(() => true),
});
@@ -222,6 +227,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 +249,7 @@ describe("LocalDatabaseMaintenance Garbage Collection V3", () => {
"Compaction on remote database completed successfully.",
"gc-compact"
);
expect(remoteDatabase.close).toHaveBeenCalledOnce();
});
it.each([
+2 -5
View File
@@ -120,6 +120,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
UI: services.UI,
vault: services.vault,
fileHandler: fileHandler,
fileProcessing: services.fileProcessing,
storageAccess: storageAccess,
control: services.control,
});
@@ -169,11 +170,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
return extraModules;
},
(core) => {
const addOns = [
new ConfigSync(this, core),
new HiddenFileSync(this, core),
new LocalDatabaseMaintenance(this, core),
];
const addOns = [new ConfigSync(core), new HiddenFileSync(core), new LocalDatabaseMaintenance(core)];
return addOns;
},
(core) => {
+5 -1
View File
@@ -2,7 +2,11 @@ import type { LiveSyncCore } from "@/main";
import type ObsidianLiveSyncPlugin from "@/main";
import { AbstractModule } from "./AbstractModule.ts";
export abstract class AbstractObsidianModule extends AbstractModule {
export abstract class AbstractObsidianModule extends AbstractModule<LiveSyncCore> {
override get services() {
return this.core.services;
}
get app() {
return this.plugin.app;
}
+30 -23
View File
@@ -22,6 +22,7 @@ import { UnresolvedErrorManager } from "@vrtmrz/livesync-commonlib/compat/servic
import { clearHandlers } from "@vrtmrz/livesync-commonlib/compat/replication/SyncParamsHandler";
import type { NecessaryServices } from "@vrtmrz/livesync-commonlib/compat/interfaces/ServiceModule";
import { MARK_LOG_NETWORK_ERROR } from "@vrtmrz/livesync-commonlib/compat/services/lib/logUtils";
import { usesLegacyIndexedDBAdapter } from "@/common/compatibilitySettings.ts";
function isOnlineAndCanReplicate(
errorManager: UnresolvedErrorManager,
@@ -145,10 +146,12 @@ export class ModuleReplicator extends AbstractModule {
}
/**
* Reconciles local chunks when an older IndexedDB client reports that the remote database was cleaned.
* This compatibility path remains reachable while those clients can still set `remoteCleaned`.
* @deprecated v0.24.17
* @param showMessage If true, show message to the user.
* Reconciles an IndexedDB-backed local database after replication reports that the remote was cleaned.
*
* The remote milestone remains a supported compatibility signal. The user can either fetch the remote
* database again, or purge unreferenced local chunks before accepting this device again.
*
* @param showMessage Whether to show the recovery choices as user-facing notices.
*/
async cleaned(showMessage: boolean) {
Logger(`The remote database has been cleaned.`, showMessage ? LOG_LEVEL_NOTICE : LOG_LEVEL_INFO);
@@ -187,27 +190,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" }
@@ -226,7 +233,7 @@ Even if you choose to clean up, you will see this option again if you exit Obsid
await this.services.tweakValue.askResolvingMismatched(activeReplicator.preferredTweakValue);
} else {
if (activeReplicator.remoteLockedAndDeviceNotAccepted) {
if (activeReplicator.remoteCleaned && this.settings.useIndexedDBAdapter) {
if (activeReplicator.remoteCleaned && usesLegacyIndexedDBAdapter(this.settings)) {
await this.cleaned(showMessage);
} else {
const message = $msg("Replicator.Dialogue.Locked.Message");
@@ -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]
);
});
});
@@ -4,20 +4,33 @@ import {
TweakValuesShouldMatchedTemplate,
TweakValuesTemplate,
IncompatibleChanges,
confName,
configurationNames,
statusDisplay,
type TweakValues,
type ObsidianLiveSyncSettings,
type RemoteDBSettings,
IncompatibleChangesInSpecificPattern,
CompatibleButLossyChanges,
type RemotePreferredTweakResult,
RemotePreferredTweakStatuses,
} from "@vrtmrz/livesync-commonlib/compat/common/types";
import { escapeMarkdownValue } from "@vrtmrz/livesync-commonlib/compat/common/utils";
import { AbstractModule } from "@/modules/AbstractModule.ts";
import { $msg } from "@/common/translation";
import { $msg, translateIfAvailable } from "@/common/translation";
import type { InjectableServiceHub } from "@vrtmrz/livesync-commonlib/compat/services/implements/injectable/InjectableServiceHub";
import type { LiveSyncCore } from "@/main.ts";
import { REMOTE_P2P } from "@vrtmrz/livesync-commonlib/compat/common/models/setting.const";
/**
* Localised counterpart of Commonlib's `confName()`, which takes no translator.
* Same shape: label plus status suffix, and an empty string for an unknown key.
*/
function localisedConfName(key: keyof ObsidianLiveSyncSettings): string {
const info = configurationNames[key];
if (!info) return "";
return `${translateIfAvailable(info.name)}${statusDisplay(info.status)}`;
}
function valueToString(value: string | number | boolean | object | undefined): string {
if (typeof value === "boolean") {
return value ? "true" : "false";
@@ -156,7 +169,7 @@ export class ModuleResolvingMismatchedTweaks extends AbstractModule {
// table += `| ${confName(key)} | ${valueMine} | ${valuePreferred} | \n`;
tableRows.push(
$msg("TweakMismatchResolve.Table.Row", {
name: confName(key),
name: localisedConfName(key),
self: valueToString(valueMine),
remote: valueToString(valuePreferred),
})
@@ -256,22 +269,21 @@ export class ModuleResolvingMismatchedTweaks extends AbstractModule {
return "IGNORE";
}
async _fetchRemotePreferredTweakValues(trialSetting: RemoteDBSettings): Promise<TweakValues | false> {
const replicator = await this.services.replicator.getNewReplicator(trialSetting);
if (!replicator) {
this._log("The remote type is not supported for fetching preferred tweak values.", LOG_LEVEL_NOTICE);
return false;
}
if (await replicator.tryConnectRemote(trialSetting)) {
const preferred = await replicator.getRemotePreferredTweakValues(trialSetting);
if (preferred) {
return preferred;
async _fetchRemotePreferredTweakValues(trialSetting: RemoteDBSettings): Promise<RemotePreferredTweakResult> {
try {
const replicator = await this.services.replicator.getNewReplicator(trialSetting);
if (!replicator) {
this._log("The remote type does not support preferred tweak values.", LOG_LEVEL_NOTICE);
return { status: RemotePreferredTweakStatuses.UNSUPPORTED };
}
this._log("Failed to get the preferred tweak values from the remote server.", LOG_LEVEL_NOTICE);
return false;
return await replicator.getRemotePreferredTweakValues(trialSetting);
} catch (ex) {
this._log("Failed to get the preferred tweak values from the remote.", LOG_LEVEL_NOTICE);
return {
status: RemotePreferredTweakStatuses.UNAVAILABLE,
error: ex,
};
}
this._log("Failed to connect to the remote server.", LOG_LEVEL_NOTICE);
return false;
}
async _checkAndAskUseRemoteConfiguration(
@@ -281,8 +293,8 @@ export class ModuleResolvingMismatchedTweaks extends AbstractModule {
return { result: false, requireFetch: false };
}
const preferred = await this.services.tweakValue.fetchRemotePreferred(trialSetting);
if (preferred) {
return await this.services.tweakValue.askUseRemoteConfiguration(trialSetting, preferred);
if (preferred.status === RemotePreferredTweakStatuses.AVAILABLE) {
return await this.services.tweakValue.askUseRemoteConfiguration(trialSetting, preferred.values);
}
return { result: false, requireFetch: false };
}
@@ -341,7 +353,7 @@ export class ModuleResolvingMismatchedTweaks extends AbstractModule {
}
tableRows.push(
$msg("TweakMismatchResolve.Table.Row", {
name: confName(key),
name: localisedConfName(key),
self: currentValueForDisplay,
remote: remoteValueForDisplay,
})
@@ -1,4 +1,4 @@
import { describe, expect, it, vi } from "vitest";
import { afterEach, describe, expect, it, vi } from "vitest";
import {
DEFAULT_SETTINGS,
REMOTE_COUCHDB,
@@ -6,6 +6,7 @@ import {
type TweakValues,
} from "@vrtmrz/livesync-commonlib/compat/common/types";
import { ModuleResolvingMismatchedTweaks } from "./ModuleResolveMismatchedTweaks";
import { setLang } from "@/common/translation";
function createModule(settingsOverride: Partial<typeof DEFAULT_SETTINGS> = {}) {
const askSelectStringDialogue = vi.fn(async (..._args: unknown[]): Promise<string | undefined> => undefined);
@@ -54,6 +55,36 @@ function createModule(settingsOverride: Partial<typeof DEFAULT_SETTINGS> = {}) {
}
describe("ModuleResolvingMismatchedTweaks", () => {
it("returns an unconfigured remote result without a separate connection preflight", async () => {
const { module, core } = createModule();
const tryConnectRemote = vi.fn(async () => true);
const getRemotePreferredTweakValues = vi.fn(async () => ({
status: "not-configured" as const,
reason: "milestone-missing" as const,
}));
core._services.replicator = {
getNewReplicator: vi.fn(async () => ({ tryConnectRemote, getRemotePreferredTweakValues })),
};
await expect(module._fetchRemotePreferredTweakValues(core.settings)).resolves.toEqual({
status: "not-configured",
reason: "milestone-missing",
});
expect(getRemotePreferredTweakValues).toHaveBeenCalledOnce();
expect(tryConnectRemote).not.toHaveBeenCalled();
});
it("returns unsupported when no replicator implements the remote type", async () => {
const { module, core } = createModule();
core._services.replicator = {
getNewReplicator: vi.fn(async () => undefined),
};
await expect(module._fetchRemotePreferredTweakValues(core.settings)).resolves.toEqual({
status: "unsupported",
});
});
it("should enable and auto-accept compatible mismatches when the preference is undefined", async () => {
const { module, core, askSelectStringDialogue, applyPartial } = createModule({
autoAcceptCompatibleTweak: undefined,
@@ -225,3 +256,43 @@ describe("ModuleResolvingMismatchedTweaks", () => {
expect(calls).toEqual(["save", "reinitialise", "set-preferred"]);
});
});
describe("ModuleResolvingMismatchedTweaks setting labels", () => {
afterEach(() => setLang("def"));
async function renderMismatchTable() {
const { module, askSelectStringDialogue } = createModule({
autoAcceptCompatibleTweak: true,
hashAlg: "xxhash64",
encrypt: false,
tweakModified: 100,
});
const preferred = {
...(DEFAULT_SETTINGS as unknown as TweakValues),
hashAlg: "xxhash32",
encrypt: true,
tweakModified: 200,
} as Partial<TweakValues>;
await module._checkAndAskResolvingMismatchedTweaks(preferred);
return String(askSelectStringDialogue.mock.calls[0]?.[0] ?? "");
}
it("localises the setting names and keeps the status suffix", async () => {
setLang("zh-tw");
const message = await renderMismatchTable();
expect(message).toContain("chunk ID 的雜湊演算法 (Experimental)");
expect(message).toContain("端對端加密");
expect(message).not.toContain("The Hash algorithm for chunk IDs");
});
it("leaves English unchanged", async () => {
const message = await renderMismatchTable();
expect(message).toContain("The Hash algorithm for chunk IDs (Experimental)");
expect(message).toContain("End-to-End Encryption");
});
});
+3 -58
View File
@@ -4,14 +4,7 @@ import {
LOG_LEVEL_VERBOSE,
Logger,
} from "@vrtmrz/livesync-commonlib/compat/common/logger";
import {
EVENT_REQUEST_OPEN_P2P,
EVENT_REQUEST_OPEN_SETTING_WIZARD,
EVENT_REQUEST_OPEN_SETTINGS,
EVENT_REQUEST_RUN_DOCTOR,
EVENT_REQUEST_RUN_FIX_INCOMPLETE,
eventHub,
} from "@/common/events.ts";
import { EVENT_REQUEST_RUN_DOCTOR, EVENT_REQUEST_RUN_FIX_INCOMPLETE, eventHub } from "@/common/events.ts";
import { AbstractModule } from "@/modules/AbstractModule.ts";
import { $msg } from "@/common/translation";
import { performDoctorConsultation, RebuildOptions } from "@vrtmrz/livesync-commonlib/compat/common/configForDoc";
@@ -31,6 +24,7 @@ import {
runConfiguredStartupLifecycle,
runStartupEntryLifecycle,
} from "@/serviceFeatures/configuredStartupLifecycle.ts";
import { disableLegacyBulkChunkPreSend } from "@/common/compatibilitySettings.ts";
type ErrorInfo = {
path: string;
@@ -84,10 +78,8 @@ export class ModuleMigration extends AbstractModule<LiveSyncCore> {
}
async migrateDisableBulkSend() {
if (this.settings.sendChunksBulk) {
if (disableLegacyBulkChunkPreSend(this.settings)) {
this._log($msg("moduleMigration.logBulkSendCorrupted"), LOG_LEVEL_NOTICE);
this.settings.sendChunksBulk = false;
this.settings.sendChunksBulkMaxSize = 1;
await this.saveSettings();
}
}
@@ -95,53 +87,6 @@ export class ModuleMigration extends AbstractModule<LiveSyncCore> {
initialMessage() {
const manager = this.core.getModule(SetupManager);
showOnboardingInvitation(this.core, manager);
/*
const message = $msg("moduleMigration.msgInitialSetup", {
URI_DOC: $msg("moduleMigration.docUri"),
});
const USE_SETUP = $msg("moduleMigration.optionHaveSetupUri");
const NEXT = $msg("moduleMigration.optionNoSetupUri");
const ret = await this.core.confirm.askSelectStringDialogue(message, [USE_SETUP, NEXT], {
title: $msg("moduleMigration.titleWelcome"),
defaultAction: USE_SETUP,
});
if (ret === USE_SETUP) {
eventHub.emitEvent(EVENT_REQUEST_OPEN_SETUP_URI);
return false;
} else if (ret == NEXT) {
return true;
}
return false;
*/
}
async askAgainForSetupURI() {
const message = $msg("moduleMigration.msgRecommendSetupUri", { URI_DOC: $msg("moduleMigration.docUri") });
const USE_MINIMAL = $msg("moduleMigration.optionSetupWizard");
const USE_P2P = $msg("moduleMigration.optionSetupViaP2P");
const USE_SETUP = $msg("moduleMigration.optionManualSetup");
const NEXT = $msg("moduleMigration.optionRemindNextLaunch");
const ret = await this.core.confirm.askSelectStringDialogue(message, [USE_MINIMAL, USE_SETUP, USE_P2P, NEXT], {
title: $msg("moduleMigration.titleRecommendSetupUri"),
defaultAction: USE_MINIMAL,
});
if (ret === USE_MINIMAL) {
eventHub.emitEvent(EVENT_REQUEST_OPEN_SETTING_WIZARD);
return false;
}
if (ret === USE_P2P) {
eventHub.emitEvent(EVENT_REQUEST_OPEN_P2P);
return false;
}
if (ret === USE_SETUP) {
eventHub.emitEvent(EVENT_REQUEST_OPEN_SETTINGS);
return false;
} else if (ret == NEXT) {
return false;
}
return false;
}
async hasIncompleteDocs(force: boolean = false): Promise<boolean> {
@@ -18,7 +18,10 @@ async function* failedDocumentScan() {
throw new Error("scan failed");
}
function createMigration(findAllNormalDocs: typeof noDocuments | typeof failedDocumentScan = noDocuments) {
function createMigration(
findAllNormalDocs: typeof noDocuments | typeof failedDocumentScan = noDocuments,
settings = { sendChunksBulk: false, sendChunksBulkMaxSize: 1 }
) {
const noticeGroups = {
setItem: vi.fn(),
finish: vi.fn(() => true),
@@ -32,6 +35,7 @@ function createMigration(findAllNormalDocs: typeof noDocuments | typeof failedDo
registerProtocolHandler: vi.fn(),
},
context: { noticeGroups },
setting: { saveSettingData: vi.fn(async () => undefined) },
vault: { isTargetFile: vi.fn(async () => true) },
path: { getPath: vi.fn() },
};
@@ -44,13 +48,37 @@ function createMigration(findAllNormalDocs: typeof noDocuments | typeof failedDo
},
localDatabase: { findAllNormalDocs },
storageAccess: {},
settings,
};
return {
migration: new ModuleMigration(core as never),
noticeGroups,
saveSettingData: services.setting.saveSettingData,
};
}
describe("ModuleMigration obsolete-setting migration", () => {
it("persists the removal of an enabled automatic bulk chunk pre-send setting", async () => {
const settings = { sendChunksBulk: true, sendChunksBulkMaxSize: 16 };
const { migration, saveSettingData } = createMigration(noDocuments, settings);
await migration.migrateDisableBulkSend();
expect(settings).toEqual({ sendChunksBulk: false, sendChunksBulkMaxSize: 1 });
expect(saveSettingData).toHaveBeenCalledOnce();
});
it("does not persist an already disabled automatic bulk chunk pre-send setting", async () => {
const settings = { sendChunksBulk: false, sendChunksBulkMaxSize: 16 };
const { migration, saveSettingData } = createMigration(noDocuments, settings);
await migration.migrateDisableBulkSend();
expect(settings).toEqual({ sendChunksBulk: false, sendChunksBulkMaxSize: 16 });
expect(saveSettingData).not.toHaveBeenCalled();
});
});
describe("ModuleMigration incomplete-document notice", () => {
it("keeps the check and its result in one persistent named group", async () => {
const { migration, noticeGroups } = createMigration();

Some files were not shown because too many files have changed in this diff Show More