docs(reports): English edition — design, voice, glossary, changelog, progress, device checklist; golden pair test
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01N4f2nya58RoRu4yEmJgRGE
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
419e1f4d37
commit
ed3b223b1c
@@ -587,6 +587,8 @@ one skeleton, none of them carrying the sidebar.
|
||||
|
||||
- **Structure:** inside the app shell, like every other secondary page — `/reports/[reportId]` was the last standalone full-screen route, and opening a report used to drop the reader out of the app entirely. 「个人报告」 sits in the 46px header; below it the reader owns its own scroll boundary, carrying the sticky screen chrome (back and the labelled 「导出」 button) and then the longform Markdown body beside a persistent TOC rail. Every phase — loading, generating, timed-out, and each error — renders inside the same shell, so the way back is always the rail. The five-chapter writer document is storage-only and is not rendered. Reports without Markdown show “旧版本报告,请重新生成”.
|
||||
- **TOC levels (2026-09-29):** chapters (`##`) are `--color-ink` at weight 600 with `--space-3` above each new chapter; sections (`###`) are indented `--space-5` and stay `--color-ink-secondary`. The two levels read apart without relying on the current-item pill.
|
||||
- **中文 / English (2026-09-29):** reports generated since the English edition carry both editions; the action bar shows a two-segment pill (`.report-language-switch`, 40px segments, selected = `--color-canvas-muted` + ink + 600) before 「导出」. Reports without an English edition show no switch at all rather than a disabled one. The edition lives in the URL (`?lang=en`, read by the server page, written with `history.replaceState`), so refresh and shared links keep it. Switching remounts the article, TOC and fact tables for that edition and sets `lang` on the reader body; chrome (header, rail, 到底部) stays Chinese. Charts draw the same glyphs; only the legend reads `Su Mo …` / 「bar = retrograde」.
|
||||
- **导出框里的英文版提示:** reading Chinese with an English edition available, the drawer header adds one `--color-canvas-muted` band (`.report-export-ai-hint`) with the AI sentence and an outline 「切到 English」; it stacks below 767px. Old reports get one caption line (`.report-export-edition-note`) instead. The drawer is keyed by edition, so switching resets the selection to that edition's blocks; the file name gains `-EN`.
|
||||
- **到底部 / 回到顶部:** one floating pill (`.report-edge-jump`, fixed bottom-right, same look as the chat's 跳到最新). It reads 「到底部」 until the reader is within 480px of the end, then 「回到顶部」 — never both. Sections below the fold mount lazily, so the click first mounts every section synchronously (`flushSync`) and then scrolls to `scrollHeight`, landing on the real end in one click. It is not a scroll follower and owns no anchor; it is hidden on paper.
|
||||
- **TOC rail:** a persistent right-hand column built from the outline's own `##`/`###` ids — no second slugger, and nothing that touches the chart-grid rehype pass. It is sticky under the report's own action bar, highlights the section in view via `IntersectionObserver`, and marks it with `aria-current="location"` plus a 2px `--color-action` bar. Placement is explicit (`grid-column: 2`) rather than DOM-ordered, so the narrow-screen drawer can stay first in the source. Below 860px the rail is gone and what remains is the collapsed 「目录」 drawer.
|
||||
- **Paper stays paper:** the rail reads in the app palette because it is chrome. `--report-paper` / `--report-rule` / `--report-accent` are the document's own, deliberately out of step with the app accent, and nothing in this surface puts them on chrome or takes chrome colour onto the page. D11, continuing D3.
|
||||
|
||||
@@ -141,13 +141,16 @@ Jyotisha 的可见文案是产品的一部分。正确性红线(真实性、
|
||||
|
||||
## 报告分块导出与删除
|
||||
|
||||
- 列表已完成行只留「查看报告」。阅读页操作条只留「返回报告中心」与导出图标;图标读屏名「导出报告」,tooltip「导出」。不再显示「下载原始附录」或「打印」入口文案;浏览器 Ctrl+P 排版仍保留。
|
||||
- 列表已完成行只留「查看报告」。阅读页操作条只留「返回报告中心」、「中文 / English」切换(只在有英文版的报告上出现)与带字的「导出」按钮(读屏名「导出报告」,2026-09-29 起不再是只有图标的按钮)。不再显示「下载原始附录」或「打印」入口文案;浏览器 Ctrl+P 排版仍保留。
|
||||
- 导出对话框标题仍为「分块导出」。说明:「勾选要带走的章节,导出成一个 .md 文件。全选就是整篇。」这里全选包含完整正文及另选的盘 / 表;与旧整篇逐字节相同的套餐是「常用」(全部正文)。
|
||||
- 套餐:「常用 / 全部分盘 / 仅核对表」;动作:「全选 / 取消全选」「导出选中 (N)」。成功才提示「已导出 N 块」,下载失败或微信限制不提示成功。本轮 D5 保持既有微信系统浏览器提示原文,不借删除打印入口顺带改它。
|
||||
- 「共 N 块 · X 字 · 已选 n/N · Y 字」来自实际可导出块及 `Array.from(text).length`;包含安全 SVG / Markdown 标记,不是估计文章字数。此为第 2 条的可直接计算文本统计,不是口算领域数字;列表页仍不杜撰章数或盘数。
|
||||
- 「共 N 块 · X 字 · 约 Z KB · 已选 n/N · Y 字 · 约 W KB」:字数来自实际可导出块及 `Array.from(text).length`,大小是实际下载内容的字节数(`TextEncoder`),写「约 186 KB」「约 1.4 MB」,不按字数估;包含安全 SVG / Markdown 标记,不是估计文章字数。此为第 2 条的可直接计算文本统计,不是口算领域数字;列表页仍不杜撰章数或盘数。
|
||||
- 删除确认:「确定删除这份报告?」;按钮:「删除 / 取消」;请求中:「删除中…」;失败:「报告没能删除,请稍后重试。」失败保留原行。
|
||||
- 日期中文全写;深度「简版 / 标准深度 / 深度 / 研究级」;主题复用现有中文映射,未知 id 原样回退,不造含义。
|
||||
|
||||
- 英文版提示(2026-09-29):当前看中文、且这份报告有英文版时,导出框顶部写「要拿去问 ChatGPT、Claude 等 AI?建议切到 English 再导出,术语与原典一致,AI 读得更准。」按钮「切到 English」。当前是英文时说明句末加「导出的是英文版。」。英文版上线前的报告写「这份报告生成于英文版上线前,重新生成即可获得中英两版。」;英文版没生成成功的写「这份报告的英文版没有生成成功,只能导出中文版。」
|
||||
- 英文版正文的口径:用 BPHS / PL9 通行的英文术语(Exalted、Own Sign、Mooltrikona、Sub lord……),不音译中文、不中英混写;限制说明用固定英文四句(见 `report-public-projection.ts` 的 `ENGLISH_LIMITATION_COPY`)。应用外壳(侧栏、标题、按钮、到底部)始终是中文。
|
||||
|
||||
## 报告里的限制说明
|
||||
|
||||
普通报告只留下人能读的正文、结论、行动建议和必要限制。不写字段名、状态码、评分、权重或执行账本。说不到日期就写「这次说不到具体哪一天。」依据没补上就写「有些依据还没补上,相关说法不能当成确定预测。」判断没闭合就写「有些判断还没闭合,不能写成确定结论。」不出现 `technique_truth`、`workflow_route` 这类键。
|
||||
|
||||
@@ -189,3 +189,26 @@ test("the export drawer suggests English for AI only when it can switch", async
|
||||
assert.ok(h.container.text.includes("重新生成即可获得中英两版"));
|
||||
} finally { await h.close(); }
|
||||
});
|
||||
|
||||
test("the real engine English golden is accepted whole and matches its Chinese pair", () => {
|
||||
const en = JSON.parse(readFileSync(new URL("./fixtures/report-reader-main-fictional-en.json", import.meta.url), "utf8"));
|
||||
const zh = JSON.parse(readFileSync(new URL("./fixtures/report-reader-main-fictional-zh-pair.json", import.meta.url), "utf8"));
|
||||
assert.equal(en.fixtureProvenance.fictional, true);
|
||||
assert.equal(en.fixtureProvenance.language, "en");
|
||||
const accepted = acceptEnglishEdition(en.markdown, undefined, engine);
|
||||
assert.ok(accepted.english, accepted.englishUnavailable);
|
||||
// Nothing for the projection to cut, so the reader sees the whole edition.
|
||||
assert.equal(projectOrdinaryReportMarkdown(en.markdown, "en"), en.markdown);
|
||||
const zhLines = (zh.markdown as string).split("\n");
|
||||
const enLines = (en.markdown as string).split("\n");
|
||||
assert.equal(enLines.length, zhLines.length, "line-aligned editions");
|
||||
const mismatched = zhLines.flatMap((line, index) => (
|
||||
JSON.stringify(numericTokens(line)) === JSON.stringify(numericTokens(enLines[index])) ? [] : [enLines[index]]
|
||||
));
|
||||
// The one known difference is a label, not a value: the English yoga name
|
||||
// spells out the houses (「婚姻障碍(7宫主在凶宫)」 / "Kalatra Dosha (7th Lord in 6/8/12)").
|
||||
assert.deepEqual(mismatched.map(line => line.split("|")[1]?.trim()), ["Kalatra Dosha (7th Lord in 6/8/12)"]);
|
||||
const h2 = (markdown: string) => buildReportExportBlocks({ markdown }).filter(block => block.group === "body").length;
|
||||
assert.equal(h2(en.markdown), h2(zh.markdown), "same chapters");
|
||||
assert.equal(containsHan(JSON.stringify(buildReportExportBlocks({ markdown: en.markdown, language: "en" }))), false);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user