Add core domain layer: schema, store, sanitizer, placeholders, settings

- Folder-based GuideStore with atomic writes, trash/restore, duplicate,
  substep reparenting, folders/favorites, working-image crop/reset
- Allowlist HTML sanitizer applied on every store write
- Placeholder scopes (guide > global > system) and collection
- Persisted app settings with deep default merge
- 16 workflow tests exercising real on-disk round-trips

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Iisyourdad
2026-06-10 16:34:15 -05:00
parent 70d812007f
commit 2a602d7477
10 changed files with 1160 additions and 0 deletions
+63
View File
@@ -0,0 +1,63 @@
'use strict';
const test = require('node:test');
const assert = require('node:assert/strict');
const {
systemPlaceholders, resolveScopes, expandPlaceholders,
listPlaceholders, collectGuidePlaceholders,
} = require('../../core/placeholders');
const { createGuide, createStep } = require('../../core/schema');
const { Settings, DEFAULT_SETTINGS } = require('../../core/settings');
const { makeTmpDir, rmrf } = require('./helpers');
test('placeholder expansion respects guide > global > system precedence', () => {
const guide = createGuide({ title: 'Install VPN', placeholders: { Author: 'Alice' } });
const system = systemPlaceholders(guide, { now: new Date(2026, 5, 10, 9, 5), stepCount: 4 });
const values = resolveScopes({ guide, globals: { Author: 'Bob', Company: 'Acme' }, system });
const out = expandPlaceholders(
'Guide [[Guide_Title]] by [[Author]] at [[Company]] on [[Date]] ([[Step_Count]] steps)',
values
);
assert.equal(out, 'Guide Install VPN by Alice at Acme on 2026-06-10 (4 steps)');
});
test('unknown placeholders stay visible instead of disappearing', () => {
const out = expandPlaceholders('Hello [[Nobody]] and [[Author]]', { Author: 'A' });
assert.equal(out, 'Hello [[Nobody]] and A');
});
test('placeholders used across a guide are collected from every surface', () => {
const guide = createGuide({ title: '[[Product]] setup', descriptionHtml: '<p>[[Company]]</p>' });
const steps = [
createStep({ title: 'Login as [[Author]]' }),
createStep({
textBlocks: [{ title: 'Warning [[Severity]]', descriptionHtml: '<p>[[Company]]</p>' }],
annotations: [{ type: 'text', x: 0, y: 0, w: 0.1, h: 0.1, text: 'See [[Doc_Ref]]' }],
}),
];
assert.deepEqual(
collectGuidePlaceholders(guide, steps),
['Author', 'Company', 'Doc_Ref', 'Product', 'Severity']
);
assert.deepEqual(listPlaceholders('no tokens here'), []);
});
test('settings persist, deep-merge with defaults, and store global placeholders', (t) => {
const dir = makeTmpDir('settings');
t.after(() => rmrf(dir));
const s1 = new Settings(dir);
assert.equal(s1.get('appearance'), DEFAULT_SETTINGS.appearance);
s1.set('appearance', 'dark');
s1.set('capture.delayMs', 1500);
s1.setGlobalPlaceholders({ Company: 'Acme', Author: 'Tyler' });
// A fresh instance reads back the changed values merged over defaults.
const s2 = new Settings(dir);
assert.equal(s2.get('appearance'), 'dark');
assert.equal(s2.get('capture.delayMs'), 1500);
assert.equal(s2.get('capture.clickMarker'), DEFAULT_SETTINGS.capture.clickMarker);
assert.deepEqual(s2.getGlobalPlaceholders(), { Company: 'Acme', Author: 'Tyler' });
});