refactor(fp): WP-31 — shared store helpers (dedupe brief/org-template/stamdata)

Audit "apply high-value": extract four shared helpers into shared/application/ and
rewire the editor stores (behaviour unchanged, existing specs are the gate):
- action-state.ts: ActionState/SaveState (were duplicated in both brief stores).
- history.ts: createHistory<T> (extracted from BriefStore's WP-27 undo/redo; WP-32 reuses).
- debounced-save.ts: createDebouncedSave (the 600ms timer/PendingSave dance, was 2×+).
- machine-remote-data.ts: machineRemoteData (the loading/failed/loaded→RemoteData switch, 3×).
Each helper has a co-located spec. Deferred DDD findings (contracts/ inconsistency, a
parse* traverse combinator, the 6× Seed boilerplate) are reported in the WP file, not built.

npm run ci green; 323 tests (+13 helper specs); brief/org-template/stamdata specs unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
eho
2026-07-22 15:40:02 +02:00
co-authored by Claude Opus 4.8
parent 13b3e5e663
commit ac3e9a9399
11 changed files with 380 additions and 138 deletions
@@ -0,0 +1,59 @@
import { describe, it, expect } from 'vitest';
import { createHistory } from './history';
describe('createHistory', () => {
it('starts empty; undo/redo are no-ops', () => {
const h = createHistory<number>();
expect(h.canUndo()).toBe(false);
expect(h.canRedo()).toBe(false);
expect(h.undo(1)).toBeUndefined();
expect(h.redo(1)).toBeUndefined();
});
it('records pre-edit snapshots, then undoes and redoes through them', () => {
const h = createHistory<string>();
// document went a -> b (record a) -> c (record b); current is 'c'
h.record('a');
h.record('b');
expect(h.canUndo()).toBe(true);
expect(h.undo('c')).toBe('b'); // current 'c' pushed to redo
expect(h.canRedo()).toBe(true);
expect(h.undo('b')).toBe('a');
expect(h.canUndo()).toBe(false);
expect(h.redo('a')).toBe('b');
expect(h.redo('b')).toBe('c');
expect(h.canRedo()).toBe(false);
});
it('record() clears the redo stack (no dead redo after a fresh edit)', () => {
const h = createHistory<string>();
h.record('a');
h.undo('b'); // redo now holds 'b'
expect(h.canRedo()).toBe(true);
h.record('x');
expect(h.canRedo()).toBe(false);
});
it('caps the stack depth', () => {
const h = createHistory<number>(3);
for (let i = 0; i < 5; i++) h.record(i);
let undos = 0;
let cur = 99;
while (h.canUndo()) {
cur = h.undo(cur)!;
undos++;
}
expect(undos).toBe(3);
});
it('clear() empties both stacks', () => {
const h = createHistory<number>();
h.record(1);
h.undo(2);
h.clear();
expect(h.canUndo()).toBe(false);
expect(h.canRedo()).toBe(false);
});
});