Files
autodoc/core/settings.js
T
Iisyourdad 5b89b5c927
Template tests / tests (push) Successful in 1m58s
Template tests / tests (pull_request) Successful in 1m47s
Capture the screen slightly before each click; record milestone in CHANGELOG
Real-world recording now saves every click with exact markers; the only
remaining nit was screenshots feeling a touch late. Add a configurable
click-lead (capture.clickLeadMs, default 120ms) that targets the screen
just before the hook timestamp, and tighten the stream sampling cadence to
50ms so a frame near that target always exists. Verified end to end: frames
now land ~120-160ms before the click (was 25-57ms), markers stay at 0.00%
offset, and the 8-click burst still saves all 8.

Also document the milestone in docs/CHANGELOG.md and remove an accidental
paste of Gitea commit-page text from it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 08:12:13 -05:00

110 lines
3.2 KiB
JavaScript

'use strict';
const path = require('node:path');
const { writeJsonSync, readJsonIfExists, deepClone } = require('./util');
const DEFAULT_SETTINGS = {
schemaVersion: 1,
appearance: 'system', // system | light | dark
language: 'en',
spellcheck: true,
capture: {
delayMs: 0,
mode: 'fullscreen', // fullscreen | window | region
includeCursor: true,
clickMarker: true,
clickMarkerColor: '#E5484D',
hotkeyCapture: 'CommandOrControl+Shift+1',
hotkeyPauseResume: 'CommandOrControl+Shift+2',
captureOutsideClicks: true,
confirmSimpleCapture: false,
autoIntervalSec: 5, // session fallback when click capture is unavailable
// Strict click timing: a step never uses a frame whose grab started
// after the click. Turn off only if captures are too slow to keep a
// pre-click frame buffered (re-enables the legacy slack heuristics).
strictClickFrames: true,
// Off-main-process frame recorder (hidden worker window sampling a
// desktop media stream). Falls back to the in-process loop when false
// or when streams cannot start on this desktop.
streamCapture: true,
frameSampleMs: 50, // stream backend sampling cadence (finer = fresher frames)
// Target the screen this many ms *before* each click. The hook fires on
// button-down but the UI/cursor often start reacting within a frame, and
// stream pixels lag slightly; a small lead keeps the saved screenshot
// clear of the click's onset. Raise it if screenshots still feel late.
clickLeadMs: 120,
},
editor: {
focusedViewDefaultForNewSteps: false,
autoTitleTemplate: '[[Mode]] capture [[Time]]',
},
exports: {
previewStepCount: 3,
openFolderAfterExport: true,
lastOutputDirs: {}, // format -> dir
},
library: {
sortBy: 'updatedAt',
},
backups: {
automatic: true,
keepLast: 10,
everyNSaves: 25,
},
};
class Settings {
constructor(settingsDir) {
this.file = path.join(settingsDir, 'app-settings.json');
this.globalPlaceholdersFile = path.join(settingsDir, 'placeholders.json');
this.data = this.load();
}
load() {
const stored = readJsonIfExists(this.file, {});
return mergeDeep(deepClone(DEFAULT_SETTINGS), stored);
}
save() {
writeJsonSync(this.file, this.data);
return this.data;
}
get(keyPath) {
return keyPath.split('.').reduce((o, k) => (o == null ? undefined : o[k]), this.data);
}
set(keyPath, value) {
const keys = keyPath.split('.');
let obj = this.data;
for (const k of keys.slice(0, -1)) {
if (typeof obj[k] !== 'object' || obj[k] === null) obj[k] = {};
obj = obj[k];
}
obj[keys[keys.length - 1]] = value;
return this.save();
}
getGlobalPlaceholders() {
return readJsonIfExists(this.globalPlaceholdersFile, {});
}
setGlobalPlaceholders(values) {
writeJsonSync(this.globalPlaceholdersFile, values);
return values;
}
}
function mergeDeep(base, extra) {
for (const [k, v] of Object.entries(extra || {})) {
if (v && typeof v === 'object' && !Array.isArray(v) && base[k] && typeof base[k] === 'object' && !Array.isArray(base[k])) {
mergeDeep(base[k], v);
} else {
base[k] = v;
}
}
return base;
}
module.exports = { Settings, DEFAULT_SETTINGS };