Skip to content

Favorites 1.4.2: stop preset re-sync throwing a JS exception per preset - #763

Merged
dwertheimer merged 2 commits into
mainfrom
favorites-preset-resync-noise
Jul 30, 2026
Merged

Favorites 1.4.2: stop preset re-sync throwing a JS exception per preset#763
dwertheimer merged 2 commits into
mainfrom
favorites-preset-resync-noise

Conversation

@dwertheimer

Copy link
Copy Markdown
Collaborator

Re-syncing Favorites presets threw a TypeError twice per preset — 40 JS exceptions in the log for a 20-preset install.

What was happening

rememberPresetsAfterInstall() called savePluginCommand() once per preset. Each call assigns DataStore.settings, and every assignment makes NotePlan write settings.json and re-enter the plugin via onSettingsUpdated mid-statement. The still-pending assignment expression then gets evaluated in a context where DataStore is unbound:

Executing function 'onSettingsUpdated'
JS Exception: TypeError: undefined is not an object (evaluating 'DataStore.settings = {
    ...settings, ...{ [jsFunction]: fields } }')

Note DataStore.settings is read successfully one line earlier, which is why "undefined" only makes sense as a re-entrancy artifact.

The presets themselves always came back correctly — verified on a live install, plugin.json held all 20 custom names after the run. So this was log noise and 20x redundant work, not data loss. But it buried real errors and left the restore depending on re-entrancy ordering.

The fix

Presets are read from settings, so there is nothing to write back to settings. rememberPresetsAfterInstall() now reads plugin.json once, applies every preset, writes once, and never touches DataStore.settings.

savePluginCommand() is unchanged, so np.ThemeChooser and the single-preset paths behave exactly as before.

Verified against the live install

Rebuilt the plugin (which writes a fresh plugin.json with generic hidden names) and fired the re-sync, so the restore path is genuinely exercised:

before after
JS Exceptions 40 0
onSettingsUpdated re-entries 20 0
plugin.settings[26] has no valid key WARN 1 0
presets restored 20/20 20/20

Tests

3 regression guards added, each confirmed to fail against the previous implementation:

  • writes plugin.json only once regardless of preset count
  • never assigns DataStore.settings (counts assignments via a property setter — comparing before/after would pass spuriously, since the old code wrote back a deep-equal object)
  • skips preset settings that are empty strings or lack a jsFunction

2413 passing across helpers + Favorites + ThemeChooser. ESLint clean. Flow unchanged (the one pre-existing presetChosen error is present on main too).

Also here

plugin.settings had a section heading with no key and no type, logging has no valid key; skipping on every settings update — now marked separator, like the entry below it. The PRESETS BELOW THIS LINE divider in plugin.commands is marked the same way, so the app can identify dividers explicitly instead of inferring from a missing jsFunction.

A companion PR does the same separator marking across the other 8 plugins.

🤖 Generated with Claude Code

dwertheimer and others added 2 commits July 30, 2026 11:20
rememberPresetsAfterInstall() called savePluginCommand() once per preset. Each call
assigned DataStore.settings, and every assignment makes NotePlan write settings.json
and re-enter the plugin via onSettingsUpdated mid-statement -- so the pending
assignment expression was re-evaluated in a context where DataStore is unbound and
threw "undefined is not an object (evaluating 'DataStore.settings = ...')".

A 20-preset re-sync logged 20 onSettingsUpdated invocations and 40 JS exceptions, and
re-read plus re-wrote plugin.json 20 times. The presets themselves always came back
correctly (verified against the live install: plugin.json held all 20 custom names
after the run), so this was noise and wasted work rather than data loss -- but it
buried real errors in the log and left the restore depending on re-entrancy ordering.

Presets are read *from* settings, so there is nothing to write back to settings. Now
reads plugin.json once, applies every preset, writes once, and never touches
DataStore.settings. savePluginCommand() is unchanged, so np.ThemeChooser and the
single-preset paths behave exactly as before.

Also marks the "DEBUGGING SETTINGS" heading in plugin.settings as a separator; without
a key or type it logged "plugin.settings[26] has no valid key; skipping" on every
settings update.

Tests: 3 regression guards added (single write, no settings assignment, skips empty and
malformed presets); all three fail against the previous implementation. 2413 passing
across helpers + Favorites + ThemeChooser.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Gives the app a reliable way to tell a comment/divider entry apart from a real command,
rather than having to infer it from a missing jsFunction.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@dwertheimer
dwertheimer merged commit 14e113f into main Jul 30, 2026
4 checks passed
@dwertheimer
dwertheimer deleted the favorites-preset-resync-noise branch July 30, 2026 18:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant