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
+72
View File
@@ -0,0 +1,72 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import fs from 'node:fs';
import path from 'node:path';
import { createRequire } from 'node:module';
import { fileURLToPath } from 'node:url';
import { privacyDeletionProgress } from '../src/pages/privacy-detail.ts';
const webRoot = fileURLToPath(new URL('..', import.meta.url));
const requireDependency = createRequire(path.join(webRoot, 'package.json'));
const ts = requireDependency('typescript');
const React = requireDependency('react');
const { renderToStaticMarkup } = requireDependency('react-dom/server');
const source = fs.readFileSync(process.env.PRIVACY_CARD_SOURCE || path.join(webRoot, 'src/pages/SaaSWorkspace.tsx'), 'utf8');
// Exercise the production card without importing the entire workspace or its
// unrelated browser-only integrations. Its display helpers are small stubs.
const cardSource = source.slice(source.indexOf('function PrivacyRequestCard('), source.indexOf('function Info('));
assert.ok(cardSource, 'the privacy card must exist');
const compiled = ts.transpileModule(cardSource + '\nexports.PrivacyRequestCard = PrivacyRequestCard;', { compilerOptions: { module: ts.ModuleKind.CommonJS, jsx: ts.JsxEmit.ReactJSX, target: ts.ScriptTarget.ES2022 } });
const module = { exports: {} };
new Function('require', 'exports', 'useState', 'useRef', 'privacyDeletionProgress', 'formatDate', 'privacyRequestTypeLabel', 'privacyRequestStatus', 'privacyStatusLabel', 'privacyCategoryLabel', 'privacyRequestSummary', 'privacySubsystemLabel', 'privacySubtaskStatusLabel', 'Info', compiled.outputText)(
requireDependency, module.exports, React.useState, React.useRef, privacyDeletionProgress,
value => value, () => '删除数据', item => item.status, value => value, value => value, () => '', value => value, value => value,
({ label, value }) => React.createElement('div', {}, label, value),
);
const tasks = ['COMPLETED', 'SKIPPED', 'COMPLETED', 'COMPLETED', 'FAILED', 'PENDING'].map((status, index) => ({ id: `private-task-${index}`, subsystem: ['redis', 'export_files', 'object_storage', 'pgvector', 'provider_index', 'postgresql'][index], status, attempts: status === 'FAILED' ? 8 : 0, last_error: status === 'FAILED' ? 'HTTP 429 synthetic-stack' : undefined, next_attempt_at: status === 'FAILED' ? '2026-10-04T14:48:45Z' : undefined }));
const request = { id: 'private-request-id', subject_id: 'private-subject-id', request_type: 'DELETE', status: 'EXECUTING', version: 2, created_at: '2026-10-04T10:00:00Z', updated_at: '2026-10-04T14:48:15Z', subtasks: tasks, result_summary: {} };
const render = overrides => renderToStaticMarkup(React.createElement(module.exports.PrivacyRequestCard, { request, subjectName: '测试长者', selected: false, loading: false, onRefresh() {}, ...overrides }));
const visibleSummary = markup => markup.slice(0, markup.indexOf('<details')).replace(/<[^>]*>/g, '');
test('normal view keeps evidence count and controls while technical data stays collapsed', () => {
const markup = render({ readAt: '2026-10-06T00:00:00Z' });
const summary = visibleSummary(markup);
assert.match(summary, /尚未全部清除/);
assert.match(summary, /已核对 4\/6/);
assert.match(summary, /最近核验:2026-10-06T00:00:00Z/);
assert.match(summary, /刷新/);
assert.match(summary, /复制请求编号/);
assert.doesNotMatch(summary, /private-|HTTP 429|provider_index|v2|已尝试/);
assert.match(markup, /<details class="cp-details"><summary>诊断详情/);
assert.doesNotMatch(markup, /<details[^>]* open/);
assert.match(markup, /HTTP 429 synthetic-stack/);
assert.match(markup, /2026-10-04T14:48:45Z/);
assert.match(markup, /不代表仍会自动重试/);
});
test('list-only state does not misrepresent an empty array as verified progress', () => {
const summary = visibleSummary(render({ request: { ...request, subtasks: [] } }));
assert.match(summary, /详情尚未读取或已更新/);
assert.match(summary, /最近核验:尚未读取详情/);
assert.doesNotMatch(summary, /0\/0|2026-10-04T14:48:15Z/);
});
test('failed refresh keeps its original verification time and hides the technical exception', () => {
const markup = render({ readAt: '2026-10-05T00:00:00Z', detailError: 'synthetic-detail-502' });
assert.match(visibleSummary(markup), /最近核验:2026-10-05T00:00:00Z(最新刷新失败,保留上次结果)/);
assert.doesNotMatch(visibleSummary(markup), /synthetic-detail-502/);
assert.match(markup, /最近读取异常:synthetic-detail-502/);
});
test('loading refresh disables the read action without offering deletion retry', () => {
const markup = render({ loading: true });
assert.match(markup, /disabled="">正在核验/);
assert.doesNotMatch(visibleSummary(markup), /重新删除|重试删除|重新入队/);
});
test('a completed parent with failed children stays incomplete in both badge and summary', () => {
const summary = visibleSummary(render({ request: { ...request, status: 'COMPLETED' } }));
assert.match(summary, /尚未全部清除/);
assert.match(summary, /核验信息不一致/);
assert.doesNotMatch(summary, /已核对完成/);
});