feat: expand companion platform, device controls, and memoir workflows
ci / verify (push) Canceled after 0s

Add customer and operator login, BLE provisioning, device fleet controls, call memory, memoir generation, and refreshed family and maintenance portals. Include API contracts, migrations, firmware sources, tests, assets, and deployment documentation.
This commit is contained in:
Codex Local
2026-10-08 10:06:44 +08:00
parent 98fb2965d0
commit bf1233d5df
861 changed files with 94614 additions and 3209 deletions
+39
View File
@@ -0,0 +1,39 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import { memoirTimeline, readMemoirMode, saveMemoirMode } from '../src/pages/memoir-reading.ts';
const chapter = (event_time, story_id) => ({ event_time, story_id, title: story_id, paragraphs: ['正文'], caption: '' });
test('timeline uses explicit years, keeps chapter indices and groups multiple stories without changing the book', () => {
const chapters = [chapter('1982年', 'a'), chapter('约1964年', 'b'), chapter('1968年夏天', 'c'), chapter('小时候', 'd')];
const original = structuredClone(chapters);
assert.deepEqual(memoirTimeline(chapters).map(group => [group.label, group.indices]), [['1960 年代', [1, 2]], ['1980 年代', [0]], ['时间未明确', [3]]]);
assert.deepEqual(chapters, original);
});
test('cross-decade ranges and unknown dates never become invented exact years or life stages', () => {
const groups = memoirTimeline([chapter('1968年至1982年', 'a'), chapter('年轻的时候', 'b'), chapter('', 'c'), chapter('1972—1976年', 'd')]);
assert.deepEqual(groups.map(group => [group.label, group.indices]), [['1970 年代', [3]], ['跨年代往事', [0]], ['时间未明确', [1, 2]]]);
assert.deepEqual(memoirTimeline([]), []);
});
test('mode preferences are scoped by elder and invalid stored values fall back to book', () => {
const previous = Object.getOwnPropertyDescriptor(globalThis, 'localStorage');
const values = new Map();
Object.defineProperty(globalThis, 'localStorage', { configurable: true, value: { getItem: key => values.get(key), setItem: (key, value) => values.set(key, value) } });
try {
saveMemoirMode('elder-a', 'timeline');
assert.equal(readMemoirMode('elder-a'), 'timeline');
assert.equal(readMemoirMode('elder-b'), 'book');
values.set('memoir-reading-mode:v1:elder-a', 'bad-mode');
assert.equal(readMemoirMode('elder-a'), 'book');
assert.deepEqual([...values.keys()], ['memoir-reading-mode:v1:elder-a']);
} finally { if (previous) Object.defineProperty(globalThis, 'localStorage', previous); else delete globalThis.localStorage; }
});
test('unavailable browser storage does not block reading or switching', () => {
const previous = Object.getOwnPropertyDescriptor(globalThis, 'localStorage');
Object.defineProperty(globalThis, 'localStorage', { configurable: true, get: () => { throw new Error('blocked'); } });
try { assert.equal(readMemoirMode('elder'), 'book'); assert.doesNotThrow(() => saveMemoirMode('elder', 'scroll')); }
finally { if (previous) Object.defineProperty(globalThis, 'localStorage', previous); else delete globalThis.localStorage; }
});