feat(report): replace the reader edition with the full-data report

Generate the full-data edition (pl9_personal_long_report.v4) and remove the reader edition. English still contains Han, so the API withholds it (BUG-1272, investigating). This commit is not pushed to staging.
This commit is contained in:
jesse-ux
2026-10-08 17:59:48 +08:00
parent 1a8c97f64a
commit 2a43fc9598
32 changed files with 4039 additions and 133 deletions
@@ -77,6 +77,29 @@ test("markdown view skips HTML, blocks remote resources, wraps tables, and lazy-
assert.match(markup, /aria-label="报告目录"/);
});
test("long markdown pages one chapter and keeps the first paint small", () => {
const chapters = Array.from({ length: 80 }, (_, index) => {
const body = index === 0 ? `第一章正文${"字".repeat(80)}` : `秘密正文-${index}`;
return `## 第${index}章\n\n${body}\n`;
});
for (const size of [1_000_000, 3_500_000]) {
const markdown = `# 标题\n\n${chapters.join("\n")}${"补".repeat(size)}`;
const before = process.memoryUsage().heapUsed;
const started = Date.now();
const markup = renderToStaticMarkup(React.createElement(PersonalReportMarkdownView, { markdown }));
const elapsed = Date.now() - started;
const delta = process.memoryUsage().heapUsed - before;
assert.ok(elapsed < 1500, `size ${size} first paint ${elapsed}ms heap ${delta}`);
assert.match(markup, /第一章正文/);
assert.match(markup, /上一章/);
assert.match(markup, /下一章/);
assert.match(markup, /1 \/ 80/);
assert.doesNotMatch(markup, /秘密正文-79/);
assert.doesNotMatch(markup, /type="search"|placeholder="搜索"/);
assert.ok(delta < 120 * 1024 * 1024, `size ${size} heap delta ${delta}`);
}
});
test("export filename includes the report date", () => {
assert.equal(personalReportMarkdownFilename("2026-09-06T12:00:00.000Z"), "个人报告-2026-09-06");
});
@@ -175,7 +175,11 @@ test("article markdown trees are memoized and hold no scroll state", () => {
const viewEnd = src.indexOf("\nfunction TocList", viewStart);
assert.ok(viewStart >= 0 && viewEnd > viewStart);
const view = src.slice(viewStart, viewEnd);
assert.doesNotMatch(view, /\buseState\b/);
// 原值: 这个函数里不得出现 useState。新值: 只允许章节序号和打印标记两处。原因: 超过 20 万字要按章翻页,滚动位置仍不放在这里。
assert.deepEqual(view.match(/\buseState\b/g), ["useState", "useState"]);
assert.match(view, /const \[chapter, setChapter\] = useState/);
assert.match(view, /const \[printing, setPrinting\] = useState\(false\)/);
assert.doesNotMatch(view, /useState\([\s\S]{0,80}scroll/i);
const lines = src.split("\n");
lines.forEach((line, index) => {
if (!line.includes("renderMarkdown(")) return;
@@ -101,12 +101,13 @@ function executeReadyReportExport(cached: boolean) {
};
}
test("new longform generation requests the reader_main edition", () => {
test("new longform generation requests the full_data edition", () => {
const generateSource = readFileSync(
new URL("../src/lib/personal-report-longform-generate.ts", import.meta.url),
"utf8",
);
assert.match(generateSource, /edition:\s*"reader_main"/);
assert.match(generateSource, /edition:\s*"full_data"/);
assert.doesNotMatch(generateSource, /edition:\s*"reader_main"/);
assert.match(generateSource, /include_fact_tables:\s*true/);
});
@@ -127,7 +127,9 @@ test("switching editions keeps the reader on the same chapter (BUG-1108)", () =>
assert.match(page, /setDrawnThrough\(chapter\);/);
assert.match(page, /drawnThrough=\{drawnThrough\}/);
const view = read("../src/components/personal-report/personal-report-markdown-view.tsx");
assert.match(view, /drawn=\{drawnThrough !== null && chapter <= drawnThrough\}/);
// 原值: drawn={drawnThrough !== null && chapter <= drawnThrough}。新值: 循环变量改为 index,超长报告的初始章取 drawnThrough。原因: 懒加载路径改名;按章翻页仍从切换前的章号打开。
assert.match(view, /drawn=\{drawnThrough !== null && index <= drawnThrough\}/);
assert.match(view, /Math\.min\(Math\.max\(drawnThrough, 0\), Math\.max\(chapters\.length - 1, 0\)\)/);
assert.match(view, /useState\(section\.eager \|\| drawn\)/);
});