You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix(core): merge user config over defaults so an absent key keeps its C++ default
ConfigMgr2::init() read vnotex.json raw and handed it straight to
MainConfig::fromJson(). The IConfig read helpers are not presence-aware, so any
key the file did not carry - one added by a newer VNote, or lost to a truncated
file - was read back as false/0/"" and then persisted. The docs claimed the
opposite.
The user's document is now applied as an RFC 7386 merge patch on top of the
default-constructed MainConfig's toJson(), from a single read, so every key is
present by the time fromJson() runs. This retires the bug class instead of
patching one key: the presence-aware readBool overload added for
autoFoldPreviewedBlocks is reverted.
Three deliberate exceptions:
- widget.newNoteDefaultTemplates is owned wholesale by the user (a present but
empty object means "none"), so it is restored from the raw snapshot after the
merge.
- A config that exists but cannot be read - unparseable, or valid JSON that is
not an object - now suppresses every main-config write for the session instead
of yielding {} and letting defaults overwrite it.
- session.json stays unmerged: SessionConfig uses isUndefinedKey() to tell
absent from present-and-false.
Gate: a generic test drops each leaf key of the defaults document in turn,
reloads through a real ConfigMgr2::init(), and asserts the default survives.
1.**Default merging** — `ConfigMgr2::init()` loads the `vnotex.json` config file via `ConfigCoreService::getConfigByName()`, then calls `MainConfig::fromJson()`. The `MainConfig` constructor first calls `initDefaults()` on all child configs, then `fromJson()` overlays only the keys present in the JSON. Missing keys keep their C++ defaults.
117
+
1.**Default merging** — `ConfigMgr2::init()` reads `vnotex.json` ONCE (`ConfigCoreService::getConfigByName`, with the `bool *p_ok` out-param) and applies it as an RFC 7386 merge patch on top of the default-constructed `MainConfig`'s `toJson()` (objects merge per key, a JSON `null` deletes the key, arrays and scalars replace wholesale). The JSON handed to `MainConfig::fromJson()` therefore always carries **every** key, which is what makes a key that a user's older `vnotex.json` never had — or one lost to a truncated file — keep its C++ default. The read helpers in `IConfig` (`readBool`, `readInt`, `readReal`, `readString`) are NOT presence-aware: absent → `false`/`0`/`""`. Their correctness depends entirely on this merge, which is why a throwaway `MainConfig` built from raw `getConfigByName()` JSON (the anti-pattern above) wipes defaults. Three deliberate exceptions:
118
+
-**Objects the user owns wholesale** (`kUserOwnedObjects` in `configmgr2.cpp`, currently `widget.newNoteDefaultTemplates`) are restored from the raw document after the merge: for them a present-but-empty object means "none", and per-key merging would resurrect the bundled entries.
119
+
-**A config that exists but cannot be read** (unparseable, or valid JSON that is not an object) sets `m_mainConfigReadFailed`, which suppresses every main-config write for the session so defaults can never overwrite it. An absent or empty file is NOT a failure.
120
+
-**`session.json` is not merged**: `SessionConfig` uses `isUndefinedKey()` to distinguish "absent" from "present and false".
121
+
122
+
Regression gates: `testAbsentKeyKeepsTheCppDefaultForEveryField`, `testAnExplicitlyEmptiedUserOwnedMapIsNotResurrected`, `testAnUnreadableConfigIsNeverOverwritten` in `tests/core/test_configmgr2.cpp`.
118
123
2.**Debounced persistence** — Changes to config objects auto-save via 500ms debounced timers.
119
124
3.**Path resolution** — `getFileFromConfigFolder()` resolves relative paths against the app data directory.
120
125
4.**Version upgrade** — `upgradeMainConfigOnVersionChange()` runs the config migration (`doVersionSpecificOverride` then stamping `c_version`) when the persisted version differs.
0 commit comments