concept-atlas-dense-explain 1.1.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/cli.mjs +73 -4
- package/package.json +1 -1
- package/packaged-skill/SKILL.md +5 -0
- package/packaged-skill/references/atlas-guide.mdx +30 -2
- package/packaged-skill/references/scroll-guide.mdx +43 -0
- package/template/index.html +53 -42
- package/template/references/atlas-guide.mdx +30 -2
- package/template/references/scroll-guide.mdx +43 -0
- package/template/scroll.html +19 -4
- package/template/src/app/App.jsx +47 -14
- package/template/src/app/use-appearance.js +25 -10
- package/template/src/components/MDXComponents.jsx +337 -0
- package/template/src/components/SkinPicker.jsx +5 -1
- package/template/src/model/validate-content.js +44 -0
- package/template/src/styles/core.css +404 -447
- package/template/src/styles/packs/elastic.css +142 -27
- package/template/src/styles/packs/manuscript.css +109 -30
- package/template/src/styles/packs/shadcn.css +133 -26
- package/template/src/views/NodeExplorer.jsx +13 -116
- package/template/vite.config.js +1 -1
package/bin/cli.mjs
CHANGED
|
@@ -9,7 +9,7 @@ import { SKINS, normalizeSkin, COMPONENT_STYLES, normalizeStyle } from '../templ
|
|
|
9
9
|
|
|
10
10
|
const packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
11
11
|
const templateRoot = path.join(packageRoot, 'template');
|
|
12
|
-
|
|
12
|
+
let args = process.argv.slice(2);
|
|
13
13
|
let buildCounter = 0;
|
|
14
14
|
|
|
15
15
|
function usage() {
|
|
@@ -20,6 +20,7 @@ function usage() {
|
|
|
20
20
|
console.log(' npx concept-atlas-dense-explain create <output.mdx> [--mode atlas|scroll] [--force]');
|
|
21
21
|
console.log(' npx concept-atlas-dense-explain guide [--mode atlas|scroll] [-o output.mdx] [--force]');
|
|
22
22
|
console.log('');
|
|
23
|
+
console.log(' --help shows this text; --version prints the package version.');
|
|
23
24
|
console.log(' Multiple inputs build in parallel (default 2 at a time, cap 4); -o is then a directory.');
|
|
24
25
|
console.log(' --link-assets keeps figures as relative links instead of inlining them as base64.');
|
|
25
26
|
console.log(' Mermaid diagrams load from a CDN at runtime by default (fast builds, needs network); --inline-mermaid bakes Mermaid into the HTML for a fully offline single file; --mermaid-cdn overrides the CDN URL.');
|
|
@@ -45,6 +46,8 @@ function fail(message) {
|
|
|
45
46
|
}
|
|
46
47
|
|
|
47
48
|
const VALUE_FLAGS = new Set(['--mode', '-o', '--output', '--concurrency', '--skin', '--default-mode', '--style', '--mermaid-cdn']);
|
|
49
|
+
const BOOLEAN_FLAGS = new Set(['--force', '--json', '--strict', '--no-validate', '--link-assets', '--inline-mermaid', '--help', '-h', '--version', '-v']);
|
|
50
|
+
const COMMAND_NAMES = ['help', 'create', 'new', 'render', 'validate', 'guide'];
|
|
48
51
|
|
|
49
52
|
/** Splits argv into flags, flag values and positional arguments. */
|
|
50
53
|
function parseFlags(argv) {
|
|
@@ -65,6 +68,26 @@ function parseFlags(argv) {
|
|
|
65
68
|
return { flags, values, positional };
|
|
66
69
|
}
|
|
67
70
|
|
|
71
|
+
/**
|
|
72
|
+
* Finds the subcommand even when flags precede it (`--force create x.mdx`),
|
|
73
|
+
* skipping flag values so a value like `create` isn't mistaken for a command.
|
|
74
|
+
* The first non-flag token that isn't a command means `render` with inputs.
|
|
75
|
+
*/
|
|
76
|
+
function extractCommand(argv) {
|
|
77
|
+
const rest = [...argv];
|
|
78
|
+
for (let i = 0; i < rest.length; i += 1) {
|
|
79
|
+
const arg = rest[i];
|
|
80
|
+
if (VALUE_FLAGS.has(arg)) { i += 1; continue; }
|
|
81
|
+
if (arg.startsWith('-') && arg.length > 1) continue;
|
|
82
|
+
if (COMMAND_NAMES.includes(arg)) {
|
|
83
|
+
rest.splice(i, 1);
|
|
84
|
+
return { command: arg, rest };
|
|
85
|
+
}
|
|
86
|
+
return { command: 'render', rest };
|
|
87
|
+
}
|
|
88
|
+
return { command: 'render', rest };
|
|
89
|
+
}
|
|
90
|
+
|
|
68
91
|
/**
|
|
69
92
|
* A single input may name an output file; a batch needs a directory, because the
|
|
70
93
|
* per-target file names come from the inputs.
|
|
@@ -102,7 +125,30 @@ function printDiagnostics(source, options, { json, label = null, quiet = false }
|
|
|
102
125
|
return result;
|
|
103
126
|
}
|
|
104
127
|
|
|
105
|
-
const command
|
|
128
|
+
const { command, rest } = extractCommand(args);
|
|
129
|
+
args = rest;
|
|
130
|
+
|
|
131
|
+
if (args.includes('--help') || args.includes('-h')) { usage(); process.exit(0); }
|
|
132
|
+
if (args.includes('--version') || args.includes('-v')) {
|
|
133
|
+
const pkg = JSON.parse(await readFile(new URL('../package.json', import.meta.url), 'utf8'));
|
|
134
|
+
console.log(pkg.version);
|
|
135
|
+
process.exit(0);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Typos like `--mdoe atlas` used to be accepted silently; fail fast instead.
|
|
139
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
140
|
+
const arg = args[i];
|
|
141
|
+
if (VALUE_FLAGS.has(arg)) {
|
|
142
|
+
if (i + 1 >= args.length || (args[i + 1].startsWith('-') && args[i + 1].length > 1)) {
|
|
143
|
+
fail(`Missing value for ${arg}`);
|
|
144
|
+
}
|
|
145
|
+
i += 1;
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
if (arg.startsWith('-') && arg.length > 1 && !BOOLEAN_FLAGS.has(arg)) {
|
|
149
|
+
fail(`Unknown flag: ${arg}`);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
106
152
|
|
|
107
153
|
if (command === 'help') { usage(); process.exit(0); }
|
|
108
154
|
|
|
@@ -353,8 +399,31 @@ async function buildOne(job) {
|
|
|
353
399
|
rollupOptions: { input: path.join(templateRoot, templateEntry) },
|
|
354
400
|
},
|
|
355
401
|
});
|
|
356
|
-
|
|
357
|
-
|
|
402
|
+
// Swap the new build in without a window where no output exists: on
|
|
403
|
+
// POSIX rename replaces atomically; the fallback path (Windows can refuse
|
|
404
|
+
// to overwrite a locked file) moves the old output aside first and
|
|
405
|
+
// restores it if the swap fails, instead of rm-ing it up front.
|
|
406
|
+
const built = path.join(scratch, templateEntry);
|
|
407
|
+
try {
|
|
408
|
+
await rename(built, output);
|
|
409
|
+
} catch (error) {
|
|
410
|
+
if (error.code !== 'EEXIST' && error.code !== 'EPERM' && error.code !== 'EACCES') throw error;
|
|
411
|
+
const backup = `${output}.bak-${process.pid}-${Date.now()}`;
|
|
412
|
+
let hasBackup = false;
|
|
413
|
+
try {
|
|
414
|
+
await rename(output, backup);
|
|
415
|
+
hasBackup = true;
|
|
416
|
+
} catch (backupError) {
|
|
417
|
+
if (backupError.code !== 'ENOENT') throw backupError; // old output kept, swap aborted
|
|
418
|
+
}
|
|
419
|
+
try {
|
|
420
|
+
await rename(built, output);
|
|
421
|
+
} catch (swapError) {
|
|
422
|
+
if (hasBackup) await rename(backup, output);
|
|
423
|
+
throw swapError;
|
|
424
|
+
}
|
|
425
|
+
if (hasBackup) await rm(backup, { force: true });
|
|
426
|
+
}
|
|
358
427
|
console.log(`Built ${mode} HTML: ${output}${title ? ` [tab: ${title}]` : ''}${describeFeatures(features)}${link ? ' [figures linked]' : ''}`);
|
|
359
428
|
return { ok: true, input, output };
|
|
360
429
|
} catch (error) {
|
package/package.json
CHANGED
package/packaged-skill/SKILL.md
CHANGED
|
@@ -48,7 +48,9 @@ This is the only runner — there is nothing to look for. Do not search for a lo
|
|
|
48
48
|
|
|
49
49
|
- Node semantics: `Overview`, `Definition`, `Mechanism`, `Implementation`, `CodeBlock`, `Boundary`, `Example`, `Counterexample`, `Prerequisite`, `Input`, `Output`, `Glossary`
|
|
50
50
|
- Argument and evidence: `Evidence`, `Invariant`, `FailureMode`, `Tradeoff`, `LearningObjectives`, `KeyQuestion`
|
|
51
|
+
- Learning loop and provenance: `WorkedExample` + `Step`, `Quiz`, `KeyTakeaways`, `Source`, `Confidence`, `Term`
|
|
51
52
|
- Information models: `Flow`, `Timeline`, `Compare`, `DecisionMatrix`, `FrameworkModel`, `MatrixModel`, `FormulaModel`, `PyramidModel`, `FunnelModel`
|
|
53
|
+
- Data and behaviour: `DataTable`, `Metric`, `StateMachine`, `DecisionTree`, `FeedbackLoop`, `CodeDiff`
|
|
52
54
|
- Reading and layout: `Insight`, `Callout`, `Details`, `NoteGrid`, `Tabs`, `Columns`, `Stack`, `Grid`, `Split`, `ScrollGrid`, `ScrollPair`, `ScrollToc`
|
|
53
55
|
- Graphics and extensions: `Mermaid`, `RelationMap`, `RelationPath`, `Math`, `MathBlock`, `Chart`, `Figure` (alias `Image`), `Cite`, `References`
|
|
54
56
|
|
|
@@ -62,6 +64,9 @@ This is the only runner — there is nothing to look for. Do not search for a lo
|
|
|
62
64
|
- **Math**: MDX parses `{ ... }` in children as expressions, so pass LaTeX with braces or backslashes through `formula`: `<Math formula="r_{\text{ann}} = (1 + r)^{12} - 1" />`, `<MathBlock formula="I(x) = -\log_2 p(x)" variables={[{symbol, description}]} />`. Brace-free children such as `<Math>\log_2 N</Math>` are fine. The validator warns (`MATH_CHILDREN_BRACES`).
|
|
63
65
|
- **CodeBlock**: a general code block usable anywhere; `Implementation` is the node-bound variant. Pass the code as a string via `code` (or as children) wrapped in a template literal so MDX does not read it as expressions: `<CodeBlock language="bash" title="..." lineNumbers>{`npm run validate`}</CodeBlock>`. `language` adds the badge, `title`/`caption` add labels, `lineNumbers` and `wrap` are booleans.
|
|
64
66
|
- **Chart**: `type` is `bar` | `line` | `pie`; use `data` for bar/pie and `labels` + `series={[{name, values}]}` for line. Charts follow theme colors.
|
|
67
|
+
- **Learning loop**: `WorkedExample` holds `Step` children; give each step a `reason` (the justification) so it teaches reasoning, not just the result. `Quiz question answer` reveals the answer on click with children as the explanation. `KeyTakeaways items={[...]}` closes a section.
|
|
68
|
+
- **Provenance**: `Source kind="spec|rfc|implementation|experiment|experience" label href` marks one claim's origin inline; `Confidence level="high|medium|low" basis` wraps a claim with how strongly it is established; `Term name` (or children as the definition) gives an inline hover definition.
|
|
69
|
+
- **Data and behaviour**: `DataTable headers={[...]} rows={[[...]]}` is the neutral table (no comparative stance). `Metric label value unit delta trend note` is one headline number — compose several with `Grid`/`ScrollGrid`. `StateMachine states={[{id,label,terminal}]} transitions={[{from,to,event,guard}]}` expresses cycles and guards. `DecisionTree branches={[{condition,outcome,tone,branches}]}` nests branches. `FeedbackLoop type="reinforcing|balancing" nodes={[{label,description}]}` closes a loop. `CodeDiff before after language` shows a change.
|
|
65
70
|
- **Figure**: a relative `src` (`./assets/diagram.png`) is inlined as base64 at build time so the HTML stays standalone; `http(s)` URLs stay links. Always set `alt`; add `label` and `caption` for a numbered caption. A missing relative file warns (`ASSET_MISSING`) and shows a placeholder. Readers can click a figure to open it full-screen (zoom, drag, `Esc`) — mention it for diagram-heavy pages.
|
|
66
71
|
- **Figure size & cost**: inlining images is what makes a screenshot-heavy page large; a page with no heavy renderers otherwise lands near 250KB. When the user cares, compile with `--link-assets` to keep images as relative links (measured 1.51MB → 270KB); the output must then sit beside the MDX's `assets/`, and the CLI warns if `-o` points elsewhere — tell the user that trade-off instead of choosing silently.
|
|
67
72
|
- **Cite/References**: `<Cite id="..." />` renders `[n]` from the matching item's position in `<References items={...} />`. In `scroll`, `References` can sit anywhere. In `atlas`, keep the cites and the `References` block in the same node, because node content only renders when that node is open.
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
<LearningObjectives items={['认识全部组件家族', '理解组件之间的职责边界', '选择合适组件编写自己的 MDX']} />
|
|
9
9
|
<KeyQuestion>我需要表达的是一个事实、一种关系,还是一种组织信息的结构?</KeyQuestion>
|
|
10
10
|
<Callout type="success" title="展厅导航">先看“知识结构”,再看“内容语义”和“经典模型”;最后打开“可视化与布局”和“扩展能力”,就能覆盖完整能力。</Callout>
|
|
11
|
-
<Children><ConceptRef id="structure-family" /><ConceptRef id="content-family" /><ConceptRef id="verification-family" /><ConceptRef id="concept-models" /><ConceptRef id="presentation-family" /><ConceptRef id="visual-family" /><ConceptRef id="extension-family" /></Children>
|
|
11
|
+
<Children><ConceptRef id="structure-family" /><ConceptRef id="content-family" /><ConceptRef id="verification-family" /><ConceptRef id="concept-models" /><ConceptRef id="presentation-family" /><ConceptRef id="visual-family" /><ConceptRef id="extension-family" /><ConceptRef id="learning-family" /><ConceptRef id="data-family" /></Children>
|
|
12
12
|
</ConceptNode>
|
|
13
13
|
|
|
14
14
|
<ConceptNode id="structure-family" title="知识结构组件:搭出可下钻的概念树" level="L1" parent="showroom" summary="页面、图谱、节点、子节点和跨节点关系共同构成导航骨架。">
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
</ConceptNode>
|
|
24
24
|
<ConceptNode id="relation-anatomy" title="Relation:把跨分支意义显式画出来" level="L2" parent="structure-family" summary="Relation 不改变树层级,而是表达前置、因果、产出、依赖和对比。">
|
|
25
25
|
<Definition>关系边使用白名单类型:prerequisite、causes、produces、uses、implements、contrasts、depends-on、exception-of、precedes。</Definition>
|
|
26
|
-
<RelationMap title="关系类型示例" items={[{from:'Definition',type:'implements',to:'ConceptNode',note:'内容具体化节点'},{from:'Evidence',type:'
|
|
26
|
+
<RelationMap title="关系类型示例" items={[{from:'Definition',type:'implements',to:'ConceptNode',note:'内容具体化节点'},{from:'Evidence',type:'produces',to:'Claim',note:'由证据支撑判断'}]} />
|
|
27
27
|
<Boundary>不要用 Relation 表达 parent-child;父子边由 parent 和 Children 自动生成。</Boundary>
|
|
28
28
|
</ConceptNode>
|
|
29
29
|
|
|
@@ -154,6 +154,31 @@ npm run build`}</CodeBlock>
|
|
|
154
154
|
<References title="本节点引用" items={[{id:'shannon1948',authors:'Shannon, C. E.',year:'1948',title:'A Mathematical Theory of Communication',source:'Bell System Technical Journal'},{id:'tufte1983',authors:'Tufte, E. R.',year:'1983',title:'The Visual Display of Quantitative Information',source:'Graphics Press'}]} />
|
|
155
155
|
</ConceptNode>
|
|
156
156
|
|
|
157
|
+
<ConceptNode id="learning-family" title="学习闭环组件:从推演到自测再到收尾" level="L1" parent="showroom" summary="WorkedExample、Quiz、KeyTakeaways 与 Confidence 把讲解补成可推演、可自检、可追溯、可收束的闭环。">
|
|
158
|
+
<Definition>LearningObjectives 和 KeyQuestion 负责开场;这一组负责中段逐步推演、即时自测、来源与置信度标注,以及结尾的压缩要点。</Definition>
|
|
159
|
+
<WorkedExample title="一次动态库加载失败的完整追踪" problem="程序编译成功却在启动时退出,需要逐步定位而不是直接猜测。">
|
|
160
|
+
<Step number="1" title="确认格式与架构" reason="格式或架构不匹配会让 loader 直接拒绝文件">file app && readelf -h app</Step>
|
|
161
|
+
<Step number="2" title="检查直接依赖" reason="排除共享库缺失这一最常见原因">ldd app</Step>
|
|
162
|
+
<Step number="3" title="核对搜索路径" reason="依赖存在但不在 loader 的搜索路径上同样会失败">echo $LD_LIBRARY_PATH</Step>
|
|
163
|
+
</WorkedExample>
|
|
164
|
+
<Quiz question="哪个阶段负责解析跨文件符号?" answer="链接阶段" tone="success">
|
|
165
|
+
链接器把目标文件中的未定义引用与其他目标文件或库中的定义匹配起来,装载阶段只做重定位。
|
|
166
|
+
</Quiz>
|
|
167
|
+
<KeyTakeaways items={['每一步都要给出理由,而不只是结果', '自测用于主动回忆,而不是装饰', '结尾要点应压缩,而不是复述正文']} />
|
|
168
|
+
<Definition>ELF 的装载语义由规范定义<Source kind="spec" label="System V ABI" href="https://refspecs.linuxfoundation.org/elf/gabi4+/contents.html" />,而不是某个实现的约定。</Definition>
|
|
169
|
+
<Confidence level="high" basis="规范定义 + 实测复现">装载阶段的不变量由 ABI 明确规定,可由 loader 行为直接验证。</Confidence>
|
|
170
|
+
</ConceptNode>
|
|
171
|
+
|
|
172
|
+
<ConceptNode id="data-family" title="数据与行为模型:表格、指标、状态、分支与回路" level="L1" parent="showroom" summary="DataTable、Metric、StateMachine、DecisionTree、FeedbackLoop 和 CodeDiff 分别表达中立数据、头条数字、状态迁移、分支决策、反馈回路与代码改动。">
|
|
173
|
+
<Definition>当线性 Flow 和二维矩阵不足以表达信息时,用这些模型补上状态、分支、回路、数据形态和代码差异。缺页<Term name="缺页异常">访问未驻留内存页时由 MMU 触发的可恢复异常</Term>属于典型的状态迁移问题。</Definition>
|
|
174
|
+
<DataTable title="三种异常对比" caption="同一现象可能来自不同层次,先分类再定位。" headers={['异常', '触发条件', '典型信号']} rows={[['缺页', '访问未驻留页', '可恢复'], ['段错误', '非法地址访问', 'SIGSEGV'], ['除零', '整数除以零', 'SIGFPE']]} />
|
|
175
|
+
<Metric label="P99 延迟" value="128" unit="ms" delta="-18%" trend="down" note="相对上一版本,来自压测环境。" />
|
|
176
|
+
<StateMachine title="进程状态" initial="new" states={[{id:'new',label:'新建'},{id:'ready',label:'就绪'},{id:'running',label:'运行'},{id:'done',label:'终止',terminal:true}]} transitions={[{from:'new',to:'ready',event:'admit'},{from:'ready',to:'running',event:'dispatch'},{from:'running',to:'ready',event:'preempt'},{from:'running',to:'done',event:'exit'}]} />
|
|
177
|
+
<DecisionTree title="先看哪一层" question="页面没有渲染,先排查哪一层?" branches={[{condition:'有构建错误',outcome:'先修构建',tone:'danger'},{condition:'构建通过',outcome:'检查运行时',branches:[{condition:'组件抛错',outcome:'看错误边界',tone:'warn'},{condition:'数据为空',outcome:'检查数据来源',tone:'info'}]}]} />
|
|
178
|
+
<FeedbackLoop title="缓存命中回路" type="reinforcing" nodes={[{label:'命中率上升',description:'更多请求由缓存服务'},{label:'后端压力下降',description:'响应更快'},{label:'体验提升',description:'更多请求进入'}]} />
|
|
179
|
+
<CodeDiff language="javascript" title="避免空值崩溃" before={`const name = user.profile.name;`} after={`const name = user?.profile?.name ?? '匿名';`} caption="可选链与空值合并把运行时错误前移为可预期的默认值。" />
|
|
180
|
+
</ConceptNode>
|
|
181
|
+
|
|
157
182
|
<Relation from="structure-family" to="content-family" type="precedes" label="先搭骨架" />
|
|
158
183
|
<Relation from="content-family" to="verification-family" type="uses" label="补充证据" />
|
|
159
184
|
<Relation from="concept-models" to="presentation-family" type="implements" label="结构落地" />
|
|
@@ -165,5 +190,8 @@ npm run build`}</CodeBlock>
|
|
|
165
190
|
<Relation from="pyramid-funnel-demo" to="framework-demo" type="contrasts" label="分层 vs 循环" />
|
|
166
191
|
<Relation from="math-demo" to="formula-demo" type="contrasts" label="LaTeX vs 纯文本公式" />
|
|
167
192
|
<Relation from="chart-demo" to="matrix-demo" type="contrasts" label="连续数据 vs 二维定位" />
|
|
193
|
+
<Relation from="verification-family" to="learning-family" type="precedes" label="证据之后自测" />
|
|
194
|
+
<Relation from="learning-family" to="data-family" type="uses" label="自测需要数据模型" />
|
|
195
|
+
<Relation from="concept-models" to="data-family" type="contrasts" label="静态模型 vs 行为模型" />
|
|
168
196
|
</ConceptGraph>
|
|
169
197
|
</ExplainPage>
|
|
@@ -58,6 +58,49 @@ npm run dev`}</CodeBlock>
|
|
|
58
58
|
</ScrollGrid>
|
|
59
59
|
</ScrollSection>
|
|
60
60
|
|
|
61
|
+
<ScrollSection title="把讲解补成学习闭环">
|
|
62
|
+
<ScrollProse>讲解不应只给结论。WorkedExample 记录每一步的理由,Quiz 触发主动回忆,KeyTakeaways 在结尾压缩真正要带走的部分。</ScrollProse>
|
|
63
|
+
<WorkedExample title="一次动态库加载失败的完整追踪" problem="程序编译成功却在启动时退出,需要逐步定位而不是直接猜测。">
|
|
64
|
+
<Step number="1" title="确认格式与架构" reason="格式或架构不匹配会让 loader 直接拒绝文件">file app && readelf -h app</Step>
|
|
65
|
+
<Step number="2" title="检查直接依赖" reason="排除共享库缺失这一最常见原因">ldd app</Step>
|
|
66
|
+
<Step number="3" title="核对搜索路径" reason="依赖存在但不在 loader 搜索路径上同样会失败">echo $LD_LIBRARY_PATH</Step>
|
|
67
|
+
</WorkedExample>
|
|
68
|
+
<Quiz question="哪个阶段负责解析跨文件符号?" answer="链接阶段" tone="success">
|
|
69
|
+
链接器把目标文件中的未定义引用与其他目标文件或库中的定义匹配起来,装载阶段只做重定位。
|
|
70
|
+
</Quiz>
|
|
71
|
+
<KeyTakeaways items={['每一步都要给出理由,而不只是结果', '自测用于主动回忆,而不是装饰', '结尾要点应压缩,而不是复述正文']} />
|
|
72
|
+
</ScrollSection>
|
|
73
|
+
|
|
74
|
+
<ScrollSection title="标注来源、置信度与术语">
|
|
75
|
+
<ScrollProse>同一句话的权威性并不相同。用 Source 标出来源类型,用 Confidence 标出确信程度,用 Term 在行内解释术语,读者才能判断该信多少。</ScrollProse>
|
|
76
|
+
<Confidence level="high" basis="规范定义 + 实测复现">
|
|
77
|
+
ELF 的装载语义由规范定义<Source kind="spec" label="System V ABI" href="https://refspecs.linuxfoundation.org/elf/gabi4+/contents.html" />,而非某个实现的约定。
|
|
78
|
+
</Confidence>
|
|
79
|
+
<Confidence level="medium" basis="单次压测,样本有限">
|
|
80
|
+
缺页<Term name="缺页异常">访问未驻留内存页时由 MMU 触发的可恢复异常</Term>是延迟波动的主要来源。
|
|
81
|
+
</Confidence>
|
|
82
|
+
</ScrollSection>
|
|
83
|
+
|
|
84
|
+
<ScrollSection title="中立数据与行为模型">
|
|
85
|
+
<ScrollProse>DataTable 承载没有比较立场的数据,Metric 给出头条数字,StateMachine、DecisionTree 和 FeedbackLoop 表达状态、分支与回路这三种 Flow 覆盖不了的结构。</ScrollProse>
|
|
86
|
+
<DataTable title="三种异常对比" caption="同一现象可能来自不同层次,先分类再定位。" headers={['异常', '触发条件', '典型信号']} rows={[['缺页', '访问未驻留页', '可恢复'], ['段错误', '非法地址访问', 'SIGSEGV'], ['除零', '整数除以零', 'SIGFPE']]} />
|
|
87
|
+
<ScrollGrid columns="3">
|
|
88
|
+
<Metric label="P99 延迟" value="128" unit="ms" delta="-18%" trend="down" note="相对上一版本。" />
|
|
89
|
+
<Metric label="可用性" value="99.95" unit="%" delta="+0.02%" trend="up" note="近 30 天。" />
|
|
90
|
+
<Metric label="错误率" value="0.7" unit="%" delta="+0.3%" trend="up" note="需要关注。" />
|
|
91
|
+
</ScrollGrid>
|
|
92
|
+
<StateMachine title="进程状态" initial="new" states={[{id:'new',label:'新建'},{id:'ready',label:'就绪'},{id:'running',label:'运行'},{id:'done',label:'终止',terminal:true}]} transitions={[{from:'new',to:'ready',event:'admit'},{from:'ready',to:'running',event:'dispatch'},{from:'running',to:'ready',event:'preempt'},{from:'running',to:'done',event:'exit'}]} />
|
|
93
|
+
<ScrollGrid columns="2">
|
|
94
|
+
<DecisionTree title="先看哪一层" question="页面没有渲染,先排查哪一层?" branches={[{condition:'有构建错误',outcome:'先修构建',tone:'danger'},{condition:'构建通过',outcome:'检查运行时',branches:[{condition:'组件抛错',outcome:'看错误边界',tone:'warn'},{condition:'数据为空',outcome:'检查数据来源',tone:'info'}]}]} />
|
|
95
|
+
<FeedbackLoop title="缓存命中回路" type="reinforcing" nodes={[{label:'命中率上升',description:'更多请求由缓存服务'},{label:'后端压力下降',description:'响应更快'},{label:'体验提升',description:'更多请求进入'}]} />
|
|
96
|
+
</ScrollGrid>
|
|
97
|
+
</ScrollSection>
|
|
98
|
+
|
|
99
|
+
<ScrollSection title="展示代码改动">
|
|
100
|
+
<ScrollProse>CodeDiff 把改动前后并排呈现,适合讲解一次修复或重构;它与单块的 CodeBlock 互补。</ScrollProse>
|
|
101
|
+
<CodeDiff language="javascript" title="避免空值崩溃" before={`const name = user.profile.name;`} after={`const name = user?.profile?.name ?? '匿名';`} caption="可选链与空值合并把运行时错误前移为可预期的默认值。" />
|
|
102
|
+
</ScrollSection>
|
|
103
|
+
|
|
61
104
|
<ScrollSection title="补充配图与出处">
|
|
62
105
|
<ScrollProse>配图和引用让讲解从断言变成可追溯的论述<Cite id="tufte1983" />。相对路径的图片会在构建时内联,输出仍是可离线打开的单文件。</ScrollProse>
|
|
63
106
|
<Figure src="./assets/sample-diagram.svg" alt="概念缩放示意" label="图 1" caption="概念缩放:同一主题可以从 L0 全局逐层下钻到 L4 边界反例。" />
|
package/template/index.html
CHANGED
|
@@ -1,42 +1,53 @@
|
|
|
1
|
-
<!doctype html>
|
|
2
|
-
<html lang="zh-CN">
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="UTF-8" />
|
|
5
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
-
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'%3E%3Ctext y='.9em' font-size='90'%3E📃%3C/text%3E%3C/svg%3E" />
|
|
7
|
-
<title>Concept Atlas · 概念缩放式知识图谱</title>
|
|
8
|
-
<script>
|
|
9
|
-
(function () {
|
|
10
|
-
var doc = document.documentElement;
|
|
11
|
-
var stored = null;
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
//
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
if (
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="zh-CN">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'%3E%3Ctext y='.9em' font-size='90'%3E📃%3C/text%3E%3C/svg%3E" />
|
|
7
|
+
<title>Concept Atlas · 概念缩放式知识图谱</title>
|
|
8
|
+
<script>
|
|
9
|
+
(function () {
|
|
10
|
+
var doc = document.documentElement;
|
|
11
|
+
var stored = null;
|
|
12
|
+
var legacy = null;
|
|
13
|
+
// Storage access itself can throw (private mode); never let the
|
|
14
|
+
// anti-flash script crash before first paint.
|
|
15
|
+
try {
|
|
16
|
+
stored = JSON.parse(localStorage.getItem('concept_atlas_appearance') || 'null');
|
|
17
|
+
legacy = localStorage.getItem('concept_atlas_theme');
|
|
18
|
+
} catch (e) {}
|
|
19
|
+
// Placeholders are replaced at build time when --skin/--default-mode
|
|
20
|
+
// (or CONCEPT_ATLAS_* env vars) are configured; otherwise the runtime
|
|
21
|
+
// fallbacks below keep the carrier defaults.
|
|
22
|
+
var skin = '__ATLAS_DEFAULT_SKIN__';
|
|
23
|
+
if (/^__.+__$/.test(skin)) skin = 'aurora';
|
|
24
|
+
var mode = '__ATLAS_DEFAULT_MODE__';
|
|
25
|
+
if (/^__.+__$/.test(mode)) mode = 'light';
|
|
26
|
+
var style = '__ATLAS_DEFAULT_STYLE__';
|
|
27
|
+
if (/^__.+__$/.test(style)) style = 'manuscript';
|
|
28
|
+
// Stored values are whitelisted so a corrupt payload can't paint a
|
|
29
|
+
// frame with an unknown skin/style before React corrects it.
|
|
30
|
+
var skins = ['aurora', 'ember', 'verdant', 'sakura', 'noir'];
|
|
31
|
+
var styles = ['manuscript', 'classic', 'shadcn', 'elastic'];
|
|
32
|
+
if (stored && (stored.mode === 'light' || stored.mode === 'dark')) {
|
|
33
|
+
mode = stored.mode;
|
|
34
|
+
} else if (mode !== 'light' && mode !== 'dark') {
|
|
35
|
+
mode = legacy === 'light' || legacy === 'dark' ? legacy : 'light';
|
|
36
|
+
}
|
|
37
|
+
if (stored && skins.indexOf(stored.skin) >= 0) skin = stored.skin;
|
|
38
|
+
if (stored && styles.indexOf(stored.style) >= 0) style = stored.style;
|
|
39
|
+
doc.setAttribute('data-skin', skin);
|
|
40
|
+
doc.setAttribute('data-theme', mode);
|
|
41
|
+
doc.setAttribute('data-style', style);
|
|
42
|
+
})();
|
|
43
|
+
</script>
|
|
44
|
+
<!-- Google Fonts for Academic / Editorial Knowledge style -->
|
|
45
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
46
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
47
|
+
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;700&family=Lora:ital,wght@0,500;0,600;0,700;1,400&family=Plus+Jakarta+Sans:wght@400;500;600;700;800&family=Noto+Serif+SC:wght@500;600;700;900&display=swap" rel="stylesheet">
|
|
48
|
+
</head>
|
|
49
|
+
<body>
|
|
50
|
+
<div id="root"></div>
|
|
51
|
+
<script type="module" src="/src/main.jsx"></script>
|
|
52
|
+
</body>
|
|
53
|
+
</html>
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
<LearningObjectives items={['认识全部组件家族', '理解组件之间的职责边界', '选择合适组件编写自己的 MDX']} />
|
|
9
9
|
<KeyQuestion>我需要表达的是一个事实、一种关系,还是一种组织信息的结构?</KeyQuestion>
|
|
10
10
|
<Callout type="success" title="展厅导航">先看“知识结构”,再看“内容语义”和“经典模型”;最后打开“可视化与布局”和“扩展能力”,就能覆盖完整能力。</Callout>
|
|
11
|
-
<Children><ConceptRef id="structure-family" /><ConceptRef id="content-family" /><ConceptRef id="verification-family" /><ConceptRef id="concept-models" /><ConceptRef id="presentation-family" /><ConceptRef id="visual-family" /><ConceptRef id="extension-family" /></Children>
|
|
11
|
+
<Children><ConceptRef id="structure-family" /><ConceptRef id="content-family" /><ConceptRef id="verification-family" /><ConceptRef id="concept-models" /><ConceptRef id="presentation-family" /><ConceptRef id="visual-family" /><ConceptRef id="extension-family" /><ConceptRef id="learning-family" /><ConceptRef id="data-family" /></Children>
|
|
12
12
|
</ConceptNode>
|
|
13
13
|
|
|
14
14
|
<ConceptNode id="structure-family" title="知识结构组件:搭出可下钻的概念树" level="L1" parent="showroom" summary="页面、图谱、节点、子节点和跨节点关系共同构成导航骨架。">
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
</ConceptNode>
|
|
24
24
|
<ConceptNode id="relation-anatomy" title="Relation:把跨分支意义显式画出来" level="L2" parent="structure-family" summary="Relation 不改变树层级,而是表达前置、因果、产出、依赖和对比。">
|
|
25
25
|
<Definition>关系边使用白名单类型:prerequisite、causes、produces、uses、implements、contrasts、depends-on、exception-of、precedes。</Definition>
|
|
26
|
-
<RelationMap title="关系类型示例" items={[{from:'Definition',type:'implements',to:'ConceptNode',note:'内容具体化节点'},{from:'Evidence',type:'
|
|
26
|
+
<RelationMap title="关系类型示例" items={[{from:'Definition',type:'implements',to:'ConceptNode',note:'内容具体化节点'},{from:'Evidence',type:'produces',to:'Claim',note:'由证据支撑判断'}]} />
|
|
27
27
|
<Boundary>不要用 Relation 表达 parent-child;父子边由 parent 和 Children 自动生成。</Boundary>
|
|
28
28
|
</ConceptNode>
|
|
29
29
|
|
|
@@ -154,6 +154,31 @@ npm run build`}</CodeBlock>
|
|
|
154
154
|
<References title="本节点引用" items={[{id:'shannon1948',authors:'Shannon, C. E.',year:'1948',title:'A Mathematical Theory of Communication',source:'Bell System Technical Journal'},{id:'tufte1983',authors:'Tufte, E. R.',year:'1983',title:'The Visual Display of Quantitative Information',source:'Graphics Press'}]} />
|
|
155
155
|
</ConceptNode>
|
|
156
156
|
|
|
157
|
+
<ConceptNode id="learning-family" title="学习闭环组件:从推演到自测再到收尾" level="L1" parent="showroom" summary="WorkedExample、Quiz、KeyTakeaways 与 Confidence 把讲解补成可推演、可自检、可追溯、可收束的闭环。">
|
|
158
|
+
<Definition>LearningObjectives 和 KeyQuestion 负责开场;这一组负责中段逐步推演、即时自测、来源与置信度标注,以及结尾的压缩要点。</Definition>
|
|
159
|
+
<WorkedExample title="一次动态库加载失败的完整追踪" problem="程序编译成功却在启动时退出,需要逐步定位而不是直接猜测。">
|
|
160
|
+
<Step number="1" title="确认格式与架构" reason="格式或架构不匹配会让 loader 直接拒绝文件">file app && readelf -h app</Step>
|
|
161
|
+
<Step number="2" title="检查直接依赖" reason="排除共享库缺失这一最常见原因">ldd app</Step>
|
|
162
|
+
<Step number="3" title="核对搜索路径" reason="依赖存在但不在 loader 的搜索路径上同样会失败">echo $LD_LIBRARY_PATH</Step>
|
|
163
|
+
</WorkedExample>
|
|
164
|
+
<Quiz question="哪个阶段负责解析跨文件符号?" answer="链接阶段" tone="success">
|
|
165
|
+
链接器把目标文件中的未定义引用与其他目标文件或库中的定义匹配起来,装载阶段只做重定位。
|
|
166
|
+
</Quiz>
|
|
167
|
+
<KeyTakeaways items={['每一步都要给出理由,而不只是结果', '自测用于主动回忆,而不是装饰', '结尾要点应压缩,而不是复述正文']} />
|
|
168
|
+
<Definition>ELF 的装载语义由规范定义<Source kind="spec" label="System V ABI" href="https://refspecs.linuxfoundation.org/elf/gabi4+/contents.html" />,而不是某个实现的约定。</Definition>
|
|
169
|
+
<Confidence level="high" basis="规范定义 + 实测复现">装载阶段的不变量由 ABI 明确规定,可由 loader 行为直接验证。</Confidence>
|
|
170
|
+
</ConceptNode>
|
|
171
|
+
|
|
172
|
+
<ConceptNode id="data-family" title="数据与行为模型:表格、指标、状态、分支与回路" level="L1" parent="showroom" summary="DataTable、Metric、StateMachine、DecisionTree、FeedbackLoop 和 CodeDiff 分别表达中立数据、头条数字、状态迁移、分支决策、反馈回路与代码改动。">
|
|
173
|
+
<Definition>当线性 Flow 和二维矩阵不足以表达信息时,用这些模型补上状态、分支、回路、数据形态和代码差异。缺页<Term name="缺页异常">访问未驻留内存页时由 MMU 触发的可恢复异常</Term>属于典型的状态迁移问题。</Definition>
|
|
174
|
+
<DataTable title="三种异常对比" caption="同一现象可能来自不同层次,先分类再定位。" headers={['异常', '触发条件', '典型信号']} rows={[['缺页', '访问未驻留页', '可恢复'], ['段错误', '非法地址访问', 'SIGSEGV'], ['除零', '整数除以零', 'SIGFPE']]} />
|
|
175
|
+
<Metric label="P99 延迟" value="128" unit="ms" delta="-18%" trend="down" note="相对上一版本,来自压测环境。" />
|
|
176
|
+
<StateMachine title="进程状态" initial="new" states={[{id:'new',label:'新建'},{id:'ready',label:'就绪'},{id:'running',label:'运行'},{id:'done',label:'终止',terminal:true}]} transitions={[{from:'new',to:'ready',event:'admit'},{from:'ready',to:'running',event:'dispatch'},{from:'running',to:'ready',event:'preempt'},{from:'running',to:'done',event:'exit'}]} />
|
|
177
|
+
<DecisionTree title="先看哪一层" question="页面没有渲染,先排查哪一层?" branches={[{condition:'有构建错误',outcome:'先修构建',tone:'danger'},{condition:'构建通过',outcome:'检查运行时',branches:[{condition:'组件抛错',outcome:'看错误边界',tone:'warn'},{condition:'数据为空',outcome:'检查数据来源',tone:'info'}]}]} />
|
|
178
|
+
<FeedbackLoop title="缓存命中回路" type="reinforcing" nodes={[{label:'命中率上升',description:'更多请求由缓存服务'},{label:'后端压力下降',description:'响应更快'},{label:'体验提升',description:'更多请求进入'}]} />
|
|
179
|
+
<CodeDiff language="javascript" title="避免空值崩溃" before={`const name = user.profile.name;`} after={`const name = user?.profile?.name ?? '匿名';`} caption="可选链与空值合并把运行时错误前移为可预期的默认值。" />
|
|
180
|
+
</ConceptNode>
|
|
181
|
+
|
|
157
182
|
<Relation from="structure-family" to="content-family" type="precedes" label="先搭骨架" />
|
|
158
183
|
<Relation from="content-family" to="verification-family" type="uses" label="补充证据" />
|
|
159
184
|
<Relation from="concept-models" to="presentation-family" type="implements" label="结构落地" />
|
|
@@ -165,5 +190,8 @@ npm run build`}</CodeBlock>
|
|
|
165
190
|
<Relation from="pyramid-funnel-demo" to="framework-demo" type="contrasts" label="分层 vs 循环" />
|
|
166
191
|
<Relation from="math-demo" to="formula-demo" type="contrasts" label="LaTeX vs 纯文本公式" />
|
|
167
192
|
<Relation from="chart-demo" to="matrix-demo" type="contrasts" label="连续数据 vs 二维定位" />
|
|
193
|
+
<Relation from="verification-family" to="learning-family" type="precedes" label="证据之后自测" />
|
|
194
|
+
<Relation from="learning-family" to="data-family" type="uses" label="自测需要数据模型" />
|
|
195
|
+
<Relation from="concept-models" to="data-family" type="contrasts" label="静态模型 vs 行为模型" />
|
|
168
196
|
</ConceptGraph>
|
|
169
197
|
</ExplainPage>
|
|
@@ -58,6 +58,49 @@ npm run dev`}</CodeBlock>
|
|
|
58
58
|
</ScrollGrid>
|
|
59
59
|
</ScrollSection>
|
|
60
60
|
|
|
61
|
+
<ScrollSection title="把讲解补成学习闭环">
|
|
62
|
+
<ScrollProse>讲解不应只给结论。WorkedExample 记录每一步的理由,Quiz 触发主动回忆,KeyTakeaways 在结尾压缩真正要带走的部分。</ScrollProse>
|
|
63
|
+
<WorkedExample title="一次动态库加载失败的完整追踪" problem="程序编译成功却在启动时退出,需要逐步定位而不是直接猜测。">
|
|
64
|
+
<Step number="1" title="确认格式与架构" reason="格式或架构不匹配会让 loader 直接拒绝文件">file app && readelf -h app</Step>
|
|
65
|
+
<Step number="2" title="检查直接依赖" reason="排除共享库缺失这一最常见原因">ldd app</Step>
|
|
66
|
+
<Step number="3" title="核对搜索路径" reason="依赖存在但不在 loader 搜索路径上同样会失败">echo $LD_LIBRARY_PATH</Step>
|
|
67
|
+
</WorkedExample>
|
|
68
|
+
<Quiz question="哪个阶段负责解析跨文件符号?" answer="链接阶段" tone="success">
|
|
69
|
+
链接器把目标文件中的未定义引用与其他目标文件或库中的定义匹配起来,装载阶段只做重定位。
|
|
70
|
+
</Quiz>
|
|
71
|
+
<KeyTakeaways items={['每一步都要给出理由,而不只是结果', '自测用于主动回忆,而不是装饰', '结尾要点应压缩,而不是复述正文']} />
|
|
72
|
+
</ScrollSection>
|
|
73
|
+
|
|
74
|
+
<ScrollSection title="标注来源、置信度与术语">
|
|
75
|
+
<ScrollProse>同一句话的权威性并不相同。用 Source 标出来源类型,用 Confidence 标出确信程度,用 Term 在行内解释术语,读者才能判断该信多少。</ScrollProse>
|
|
76
|
+
<Confidence level="high" basis="规范定义 + 实测复现">
|
|
77
|
+
ELF 的装载语义由规范定义<Source kind="spec" label="System V ABI" href="https://refspecs.linuxfoundation.org/elf/gabi4+/contents.html" />,而非某个实现的约定。
|
|
78
|
+
</Confidence>
|
|
79
|
+
<Confidence level="medium" basis="单次压测,样本有限">
|
|
80
|
+
缺页<Term name="缺页异常">访问未驻留内存页时由 MMU 触发的可恢复异常</Term>是延迟波动的主要来源。
|
|
81
|
+
</Confidence>
|
|
82
|
+
</ScrollSection>
|
|
83
|
+
|
|
84
|
+
<ScrollSection title="中立数据与行为模型">
|
|
85
|
+
<ScrollProse>DataTable 承载没有比较立场的数据,Metric 给出头条数字,StateMachine、DecisionTree 和 FeedbackLoop 表达状态、分支与回路这三种 Flow 覆盖不了的结构。</ScrollProse>
|
|
86
|
+
<DataTable title="三种异常对比" caption="同一现象可能来自不同层次,先分类再定位。" headers={['异常', '触发条件', '典型信号']} rows={[['缺页', '访问未驻留页', '可恢复'], ['段错误', '非法地址访问', 'SIGSEGV'], ['除零', '整数除以零', 'SIGFPE']]} />
|
|
87
|
+
<ScrollGrid columns="3">
|
|
88
|
+
<Metric label="P99 延迟" value="128" unit="ms" delta="-18%" trend="down" note="相对上一版本。" />
|
|
89
|
+
<Metric label="可用性" value="99.95" unit="%" delta="+0.02%" trend="up" note="近 30 天。" />
|
|
90
|
+
<Metric label="错误率" value="0.7" unit="%" delta="+0.3%" trend="up" note="需要关注。" />
|
|
91
|
+
</ScrollGrid>
|
|
92
|
+
<StateMachine title="进程状态" initial="new" states={[{id:'new',label:'新建'},{id:'ready',label:'就绪'},{id:'running',label:'运行'},{id:'done',label:'终止',terminal:true}]} transitions={[{from:'new',to:'ready',event:'admit'},{from:'ready',to:'running',event:'dispatch'},{from:'running',to:'ready',event:'preempt'},{from:'running',to:'done',event:'exit'}]} />
|
|
93
|
+
<ScrollGrid columns="2">
|
|
94
|
+
<DecisionTree title="先看哪一层" question="页面没有渲染,先排查哪一层?" branches={[{condition:'有构建错误',outcome:'先修构建',tone:'danger'},{condition:'构建通过',outcome:'检查运行时',branches:[{condition:'组件抛错',outcome:'看错误边界',tone:'warn'},{condition:'数据为空',outcome:'检查数据来源',tone:'info'}]}]} />
|
|
95
|
+
<FeedbackLoop title="缓存命中回路" type="reinforcing" nodes={[{label:'命中率上升',description:'更多请求由缓存服务'},{label:'后端压力下降',description:'响应更快'},{label:'体验提升',description:'更多请求进入'}]} />
|
|
96
|
+
</ScrollGrid>
|
|
97
|
+
</ScrollSection>
|
|
98
|
+
|
|
99
|
+
<ScrollSection title="展示代码改动">
|
|
100
|
+
<ScrollProse>CodeDiff 把改动前后并排呈现,适合讲解一次修复或重构;它与单块的 CodeBlock 互补。</ScrollProse>
|
|
101
|
+
<CodeDiff language="javascript" title="避免空值崩溃" before={`const name = user.profile.name;`} after={`const name = user?.profile?.name ?? '匿名';`} caption="可选链与空值合并把运行时错误前移为可预期的默认值。" />
|
|
102
|
+
</ScrollSection>
|
|
103
|
+
|
|
61
104
|
<ScrollSection title="补充配图与出处">
|
|
62
105
|
<ScrollProse>配图和引用让讲解从断言变成可追溯的论述<Cite id="tufte1983" />。相对路径的图片会在构建时内联,输出仍是可离线打开的单文件。</ScrollProse>
|
|
63
106
|
<Figure src="./assets/sample-diagram.svg" alt="概念缩放示意" label="图 1" caption="概念缩放:同一主题可以从 L0 全局逐层下钻到 L4 边界反例。" />
|
package/template/scroll.html
CHANGED
|
@@ -9,8 +9,13 @@
|
|
|
9
9
|
(function () {
|
|
10
10
|
var doc = document.documentElement;
|
|
11
11
|
var stored = null;
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
var legacy = null;
|
|
13
|
+
// Storage access itself can throw (private mode); never let the
|
|
14
|
+
// anti-flash script crash before first paint.
|
|
15
|
+
try {
|
|
16
|
+
stored = JSON.parse(localStorage.getItem('concept_atlas_appearance') || 'null');
|
|
17
|
+
legacy = localStorage.getItem('concept_atlas_scroll_theme');
|
|
18
|
+
} catch (e) {}
|
|
14
19
|
// Placeholders are replaced at build time when --skin/--default-mode
|
|
15
20
|
// (or CONCEPT_ATLAS_* env vars) are configured; otherwise the runtime
|
|
16
21
|
// fallbacks below keep the carrier defaults.
|
|
@@ -20,16 +25,26 @@
|
|
|
20
25
|
if (/^__.+__$/.test(mode)) mode = 'light';
|
|
21
26
|
var style = '__ATLAS_DEFAULT_STYLE__';
|
|
22
27
|
if (/^__.+__$/.test(style)) style = 'manuscript';
|
|
28
|
+
// Stored values are whitelisted so a corrupt payload can't paint a
|
|
29
|
+
// frame with an unknown skin/style before React corrects it.
|
|
30
|
+
var skins = ['aurora', 'ember', 'verdant', 'sakura', 'noir'];
|
|
31
|
+
var styles = ['manuscript', 'classic', 'shadcn', 'elastic'];
|
|
23
32
|
if (stored && (stored.mode === 'light' || stored.mode === 'dark')) {
|
|
24
33
|
mode = stored.mode;
|
|
25
34
|
} else if (mode !== 'light' && mode !== 'dark') {
|
|
26
35
|
mode = legacy === 'light' || legacy === 'dark' ? legacy : 'light';
|
|
27
36
|
}
|
|
28
|
-
|
|
37
|
+
if (stored && skins.indexOf(stored.skin) >= 0) skin = stored.skin;
|
|
38
|
+
if (stored && styles.indexOf(stored.style) >= 0) style = stored.style;
|
|
39
|
+
doc.setAttribute('data-skin', skin);
|
|
29
40
|
doc.setAttribute('data-theme', mode);
|
|
30
|
-
doc.setAttribute('data-style',
|
|
41
|
+
doc.setAttribute('data-style', style);
|
|
31
42
|
})();
|
|
32
43
|
</script>
|
|
44
|
+
<!-- Google Fonts for the scroll carrier's type stack (same set as index.html) -->
|
|
45
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
46
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
47
|
+
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;700&family=Lora:ital,wght@0,500;0,600;0,700;1,400&family=Plus+Jakarta+Sans:wght@400;500;600;700;800&family=Noto+Serif+SC:wght@500;600;700;900&display=swap" rel="stylesheet">
|
|
33
48
|
</head>
|
|
34
49
|
<body>
|
|
35
50
|
<div id="root"></div>
|