From 572b66650b4c9171339a4bfbb7c979f7a8502e94 Mon Sep 17 00:00:00 2001 From: Iisyourdad Date: Thu, 11 Jun 2026 10:47:32 -0500 Subject: [PATCH] Fix settings button on library view --- core/util.js | 6 ++++-- docs/CHANGELOG.md | 5 +++++ tests/unit/placeholders.test.js | 30 ++++++++++++++++++++++++++++++ 3 files changed, 39 insertions(+), 2 deletions(-) diff --git a/core/util.js b/core/util.js index 65b9d6e..f94ee79 100644 --- a/core/util.js +++ b/core/util.js @@ -32,7 +32,9 @@ function atomicWriteFileSync(file, data) { } function writeJsonSync(file, obj) { - atomicWriteFileSync(file, JSON.stringify(obj, null, 2) + '\n'); + const json = JSON.stringify(obj, null, 2); + if (json === undefined) throw new TypeError(`writeJsonSync: value for ${file} is not JSON-serializable`); + atomicWriteFileSync(file, json + '\n'); } function readJsonSync(file) { @@ -43,7 +45,7 @@ function readJsonIfExists(file, fallback) { try { return readJsonSync(file); } catch (err) { - if (err.code === 'ENOENT') return fallback; + if (err.code === 'ENOENT' || err instanceof SyntaxError) return fallback; throw err; } } diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 1777e27..6a9c2e5 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -78,6 +78,11 @@ Initial release. earlier) and use it for the click-marker placement, and the click-capture cache is armed as soon as recording starts so the very first click is captured instantly. +- Settings no longer fails to open if `app-settings.json` or + `placeholders.json` was previously corrupted (e.g. left containing the + literal text "undefined" by an old bug); a corrupted file is now + treated as empty instead of crashing the dialog, and is overwritten + with valid JSON the next time settings are saved. ### Added (initial feature set) diff --git a/tests/unit/placeholders.test.js b/tests/unit/placeholders.test.js index 3e372d9..2b15ac3 100644 --- a/tests/unit/placeholders.test.js +++ b/tests/unit/placeholders.test.js @@ -3,12 +3,16 @@ const test = require('node:test'); const assert = require('node:assert/strict'); +const fs = require('node:fs'); +const path = require('node:path'); + const { systemPlaceholders, resolveScopes, expandPlaceholders, listPlaceholders, collectGuidePlaceholders, } = require('../../core/placeholders'); const { createGuide, createStep } = require('../../core/schema'); const { Settings, DEFAULT_SETTINGS } = require('../../core/settings'); +const { writeJsonSync } = require('../../core/util'); const { makeTmpDir, rmrf } = require('./helpers'); test('placeholder expansion respects guide > global > system precedence', () => { @@ -61,3 +65,29 @@ test('settings persist, deep-merge with defaults, and store global placeholders' assert.equal(s2.get('capture.clickMarker'), DEFAULT_SETTINGS.capture.clickMarker); assert.deepEqual(s2.getGlobalPlaceholders(), { Company: 'Acme', Author: 'Tyler' }); }); + +test('a corrupted placeholders/settings file falls back instead of crashing the settings dialog', (t) => { + const dir = makeTmpDir('settings-corrupt'); + t.after(() => rmrf(dir)); + + // Simulate a file left over from a past bug: literal "undefined" instead of JSON. + fs.writeFileSync(path.join(dir, 'placeholders.json'), 'undefined\n'); + fs.writeFileSync(path.join(dir, 'app-settings.json'), 'undefined\n'); + + const s = new Settings(dir); + assert.deepEqual(s.getGlobalPlaceholders(), {}); + assert.equal(s.get('appearance'), DEFAULT_SETTINGS.appearance); + + // Saving afterwards overwrites the corrupted file with valid JSON. + s.setGlobalPlaceholders({ Author: 'Tyler' }); + assert.deepEqual(s.getGlobalPlaceholders(), { Author: 'Tyler' }); +}); + +test('writeJsonSync refuses to write a non-JSON value instead of writing the literal string "undefined"', () => { + const dir = makeTmpDir('write-json-guard'); + try { + assert.throws(() => writeJsonSync(path.join(dir, 'bad.json'), undefined), TypeError); + } finally { + rmrf(dir); + } +});