concept-atlas-dense-explain 1.0.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.
Files changed (30) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +49 -0
  3. package/bin/cli.mjs +75 -6
  4. package/package.json +14 -2
  5. package/{skill → packaged-skill}/SKILL.md +22 -9
  6. package/{template/guides → packaged-skill/references}/atlas-guide.mdx +30 -2
  7. package/{template/guides → packaged-skill/references}/scroll-guide.mdx +43 -0
  8. package/template/index.html +15 -4
  9. package/template/package.json +5 -22
  10. package/template/references/assets/sample-diagram.svg +47 -0
  11. package/template/references/atlas-guide.mdx +197 -0
  12. package/template/references/scroll-guide.mdx +181 -0
  13. package/template/scroll.html +19 -4
  14. package/template/src/app/App.jsx +47 -14
  15. package/template/src/app/use-appearance.js +25 -10
  16. package/template/src/components/MDXComponents.jsx +337 -0
  17. package/template/src/components/SkinPicker.jsx +5 -1
  18. package/template/src/model/validate-content.js +44 -0
  19. package/template/src/styles/core.css +404 -447
  20. package/template/src/styles/packs/elastic.css +142 -27
  21. package/template/src/styles/packs/manuscript.css +109 -30
  22. package/template/src/styles/packs/shadcn.css +133 -26
  23. package/template/src/views/NodeExplorer.jsx +13 -116
  24. package/template/vite.config.js +1 -1
  25. package/template/content/compile-runtime.mdx +0 -158
  26. package/template/content/scroll-reading-demo.mdx +0 -31
  27. package/template/scripts/build.mjs +0 -117
  28. package/template/scripts/clean-temp.mjs +0 -27
  29. package/template/scripts/validate-content.mjs +0 -44
  30. /package/{template/guides → packaged-skill/references}/assets/sample-diagram.svg +0 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Concept Atlas contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,49 @@
1
+ # concept-atlas-dense-explain
2
+
3
+ Compile semantic MDX into a standalone, interactive Concept Atlas HTML page. The
4
+ same workflow ships as an AI-agent skill, so an agent can generate, validate and
5
+ render a "dense explainer" without hand-writing any layout.
6
+
7
+ Two page shells share one component library:
8
+
9
+ - `atlas` — concept graph with node navigation (`ExplainPage` → `ConceptGraph` → `ConceptNode`)
10
+ - `scroll` — continuous document with an auto-built table of contents
11
+
12
+ ## CLI
13
+
14
+ ```bash
15
+ # Learn the components from a real, compilable reference
16
+ npx concept-atlas-dense-explain guide --mode atlas -o atlas-guide.mdx
17
+ npx concept-atlas-dense-explain guide --mode scroll -o scroll-guide.mdx
18
+
19
+ # Scaffold a document skeleton
20
+ npx concept-atlas-dense-explain create topic.mdx --mode atlas
21
+
22
+ # Validate structure before building
23
+ npx concept-atlas-dense-explain validate topic.mdx --mode atlas
24
+
25
+ # Compile to a standalone HTML file beside the MDX
26
+ npx concept-atlas-dense-explain topic.mdx --mode atlas
27
+ ```
28
+
29
+ `guide` and `create` refuse to overwrite an existing file unless `--force` is
30
+ passed. Use `-o` to choose the output path (a directory when passing several
31
+ inputs). Validation errors abort the build; `--no-validate` forces a knowingly
32
+ broken build.
33
+
34
+ Mermaid loads from a CDN at runtime by default (fast builds, needs network);
35
+ `--inline-mermaid` bakes it into the HTML for a fully offline single file, and
36
+ `--mermaid-cdn` overrides the CDN URL. Appearance defaults can be baked with
37
+ `--skin`, `--default-mode` and `--style`; readers can still switch in the UI.
38
+
39
+ Run `npx concept-atlas-dense-explain help` for the full flag list.
40
+
41
+ ## Links
42
+
43
+ - Repository: https://github.com/wurenrumian/concept-atlas
44
+ - Framework guide: [`docs/FRAMEWORK.md`](https://github.com/wurenrumian/concept-atlas/blob/main/docs/FRAMEWORK.md)
45
+ - Usage recipes: [`docs/USAGE.md`](https://github.com/wurenrumian/concept-atlas/blob/main/docs/USAGE.md)
46
+
47
+ ## License
48
+
49
+ MIT
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
- const args = process.argv.slice(2);
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 = ['help', 'create', 'new', 'render', 'validate', 'guide'].includes(args[0]) ? args.shift() : 'render';
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
 
@@ -114,14 +160,14 @@ if (command === 'guide') {
114
160
  console.error(`Refusing to overwrite ${output}; pass --force to replace it.`);
115
161
  process.exit(1);
116
162
  }
117
- const source = path.join(templateRoot, 'guides', `${mode}-guide.mdx`);
163
+ const source = path.join(templateRoot, 'references', `${mode}-guide.mdx`);
118
164
  if (!(await exists(source))) {
119
165
  console.error(`Guide for mode "${mode}" is missing from the package.`);
120
166
  process.exit(1);
121
167
  }
122
168
  await mkdir(path.dirname(output), { recursive: true });
123
169
  await copyFile(source, output);
124
- const assetsSource = path.join(templateRoot, 'guides', 'assets');
170
+ const assetsSource = path.join(templateRoot, 'references', 'assets');
125
171
  const assetsTarget = path.join(path.dirname(output), 'assets');
126
172
  if (await exists(assetsSource) && path.resolve(assetsSource) !== path.resolve(assetsTarget) && (args.includes('--force') || !(await exists(assetsTarget)))) {
127
173
  await cp(assetsSource, assetsTarget, { recursive: true, force: true });
@@ -353,8 +399,31 @@ async function buildOne(job) {
353
399
  rollupOptions: { input: path.join(templateRoot, templateEntry) },
354
400
  },
355
401
  });
356
- await rm(output, { force: true });
357
- await rename(path.join(scratch, templateEntry), output);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "concept-atlas-dense-explain",
3
- "version": "1.0.0",
3
+ "version": "1.2.0",
4
4
  "description": "Portable dense-explanation skill and MDX concept atlas template",
5
5
  "type": "module",
6
6
  "bin": {
@@ -12,7 +12,7 @@
12
12
  "files": [
13
13
  "bin",
14
14
  "template",
15
- "skill"
15
+ "packaged-skill"
16
16
  ],
17
17
  "dependencies": {
18
18
  "@mdx-js/rollup": "^3.0.1",
@@ -34,6 +34,18 @@
34
34
  "dense-explain",
35
35
  "concept-map"
36
36
  ],
37
+ "repository": {
38
+ "type": "git",
39
+ "url": "git+https://github.com/wurenrumian/concept-atlas.git",
40
+ "directory": "packages/concept-atlas-dense-explain"
41
+ },
42
+ "homepage": "https://github.com/wurenrumian/concept-atlas#readme",
43
+ "bugs": {
44
+ "url": "https://github.com/wurenrumian/concept-atlas/issues"
45
+ },
46
+ "engines": {
47
+ "node": ">=18"
48
+ },
37
49
  "publishConfig": {
38
50
  "access": "public"
39
51
  },
@@ -1,21 +1,29 @@
1
1
  ---
2
2
  name: concept-atlas-dense-explain
3
- description: Turn a topic or an existing document into an interactive Concept Atlas explainer. Generate AI-editable MDX with the concept-atlas-dense-explain npm CLI, validate its structure, and compile standalone HTML with concept nodes, relation graphs, math (KaTeX), charts, figures, citations, and reading aids. Use when a technical explanation, concept map, layered knowledge page, dense explainer, or interactive teaching page is requested.
3
+ description: Turn a topic or an existing document into an interactive Concept Atlas explainer. Generate AI-editable MDX with the concept-atlas-dense-explain CLI, validate its structure, and compile standalone HTML with concept nodes, relation graphs, math (KaTeX), charts, figures, citations, and reading aids. Use when a technical explanation, concept map, layered knowledge page, dense explainer, or interactive teaching page is requested.
4
4
  ---
5
5
 
6
6
  # Concept Atlas Dense Explain
7
7
 
8
- Drive everything through the `concept-atlas-dense-explain` npm CLI. This skill is intentionally lightweight: do not copy implementation files from the skill directory or recreate the React/Vite app. If a command name is unclear, run `npx concept-atlas-dense-explain help`. If the user only wants the prompt/methodology and not files, still choose a shell and emit valid MDX; the CLI is needed only to compile.
8
+ Drive everything through the `concept-atlas-dense-explain` CLI, always via `npx`. Everything you need is in this file and the bundled `references/`; do not read the framework repository or installed package files, do not copy implementation files from the skill directory, and do not recreate the React/Vite app. If a command name is unclear, run `npx concept-atlas-dense-explain help`. If the user only wants the prompt/methodology and not files, still choose a shell and emit valid MDX; the CLI is needed only to validate and compile.
9
+
10
+ ## Running the CLI
11
+
12
+ Always invoke the CLI through `npx`, from any directory:
13
+
14
+ ```bash
15
+ npx concept-atlas-dense-explain <args>
16
+ ```
17
+
18
+ This is the only runner — there is nothing to look for. Do not search for a local binary, a repository checkout, or an installed copy, and do not run the framework's own `npm` scripts. The first call downloads the package from the npm registry; say so once, then continue.
9
19
 
10
20
  ## Workflow
11
21
 
12
22
  1. Choose one page shell: `atlas` (concept graph with node navigation) or `scroll` (continuous document). Recommend `atlas` when the reader drills into concepts or follows relations, `scroll` for linear argument, tutorials, and reports. The library is shared. If an `.mdx` already exists, detect its shell and work with it.
13
- 2. **Learn the components from the canonical guide before authoring.** Generate it for the chosen shell and read it:
14
- ```bash
15
- npx concept-atlas-dense-explain guide --mode atlas -o ./concept-atlas-atlas-guide.mdx
16
- npx concept-atlas-dense-explain guide --mode scroll -o ./concept-atlas-scroll-guide.mdx
17
- ```
18
- It is real, compilable MDX showing that shell's components and their exact props; search it for a component name instead of guessing. Delete it when done.
23
+ 2. **Learn the components from the bundled reference before authoring.** The only reference you need is the guide that ships with this skill:
24
+ - `references/atlas-guide.mdx` (atlas) or `references/scroll-guide.mdx` (scroll)
25
+ It is real, compilable MDX showing that shell's components and their exact props; search it for a component name instead of guessing, and do not look for component documentation anywhere else. Do not run `guide` for this.
26
+ Run `npx concept-atlas-dense-explain guide --mode <mode> -o <file>` only when you specifically need a project-local copy to compile beside the page, and delete that copy when done.
19
27
  3. Start from a skeleton when useful: `npx concept-atlas-dense-explain create <file>.mdx --mode atlas|scroll`. `create` and `guide` refuse to overwrite an existing file unless `--force` is passed.
20
28
  4. Write the semantic MDX into the user's `.mdx` file (see Authoring rules).
21
29
  5. Validate before rendering:
@@ -40,7 +48,9 @@ Drive everything through the `concept-atlas-dense-explain` npm CLI. This skill i
40
48
 
41
49
  - Node semantics: `Overview`, `Definition`, `Mechanism`, `Implementation`, `CodeBlock`, `Boundary`, `Example`, `Counterexample`, `Prerequisite`, `Input`, `Output`, `Glossary`
42
50
  - Argument and evidence: `Evidence`, `Invariant`, `FailureMode`, `Tradeoff`, `LearningObjectives`, `KeyQuestion`
51
+ - Learning loop and provenance: `WorkedExample` + `Step`, `Quiz`, `KeyTakeaways`, `Source`, `Confidence`, `Term`
43
52
  - Information models: `Flow`, `Timeline`, `Compare`, `DecisionMatrix`, `FrameworkModel`, `MatrixModel`, `FormulaModel`, `PyramidModel`, `FunnelModel`
53
+ - Data and behaviour: `DataTable`, `Metric`, `StateMachine`, `DecisionTree`, `FeedbackLoop`, `CodeDiff`
44
54
  - Reading and layout: `Insight`, `Callout`, `Details`, `NoteGrid`, `Tabs`, `Columns`, `Stack`, `Grid`, `Split`, `ScrollGrid`, `ScrollPair`, `ScrollToc`
45
55
  - Graphics and extensions: `Mermaid`, `RelationMap`, `RelationPath`, `Math`, `MathBlock`, `Chart`, `Figure` (alias `Image`), `Cite`, `References`
46
56
 
@@ -54,6 +64,9 @@ Drive everything through the `concept-atlas-dense-explain` npm CLI. This skill i
54
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`).
55
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.
56
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.
57
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.
58
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.
59
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.
@@ -70,4 +83,4 @@ Drive everything through the `concept-atlas-dense-explain` npm CLI. This skill i
70
83
  - Every node has `title` + `summary`; every `Relation` has a `label`.
71
84
  - The HTML file exists at the reported path.
72
85
 
73
- If the CLI or npm registry is unavailable, report the blocker instead of copying the implementation into the skill.
86
+ If `npx` cannot reach the registry, report the blocker. Never copy implementation files into the skill directory.
@@ -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:'supports',to:'Claim',note:'由证据支撑判断'}]} />
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 边界反例。" />
@@ -9,8 +9,13 @@
9
9
  (function () {
10
10
  var doc = document.documentElement;
11
11
  var stored = null;
12
- try { stored = JSON.parse(localStorage.getItem('concept_atlas_appearance') || 'null'); } catch (e) {}
13
- var legacy = localStorage.getItem('concept_atlas_theme');
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) {}
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,14 +25,20 @@
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
- doc.setAttribute('data-skin', stored && stored.skin ? stored.skin : skin);
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', stored && stored.style ? stored.style : style);
41
+ doc.setAttribute('data-style', style);
31
42
  })();
32
43
  </script>
33
44
  <!-- Google Fonts for Academic / Editorial Knowledge style -->
@@ -1,22 +1,5 @@
1
- {
2
- "name": "my-concept-atlas",
3
- "private": true,
4
- "version": "0.1.0",
5
- "type": "module",
6
- "scripts": { "dev": "vite", "validate": "node scripts/validate-content.mjs", "build": "npm run validate && node scripts/build.mjs", "clean:temp": "node scripts/clean-temp.mjs" },
7
- "dependencies": {
8
- "@mdx-js/rollup": "^3.0.1",
9
- "clsx": "^2.1.1",
10
- "d3": "^7.9.0",
11
- "katex": "^0.16.47",
12
- "lucide-react": "^0.475.0",
13
- "mermaid": "^11.17.2",
14
- "react": "^18.3.1",
15
- "react-dom": "^18.3.1"
16
- },
17
- "devDependencies": {
18
- "@vitejs/plugin-react": "^4.3.4",
19
- "vite": "^5.4.14",
20
- "vite-plugin-singlefile": "^2.1.0"
21
- }
22
- }
1
+ {
2
+ "name": "my-concept-atlas",
3
+ "private": true,
4
+ "type": "module"
5
+ }
@@ -0,0 +1,47 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 720 240" role="img" aria-label="Concept zoom levels">
3
+ <defs>
4
+ <linearGradient id="band" x1="0" y1="0" x2="1" y2="0">
5
+ <stop offset="0" stop-color="#6366f1" stop-opacity="0.9"/>
6
+ <stop offset="1" stop-color="#06b6d4" stop-opacity="0.75"/>
7
+ </linearGradient>
8
+ </defs>
9
+ <rect x="0" y="0" width="720" height="240" rx="14" fill="#0f172a"/>
10
+ <g fill="none" stroke="#334155" stroke-width="1">
11
+ <line x1="40" y1="176" x2="680" y2="176"/>
12
+ </g>
13
+ <g font-family="Segoe UI, Helvetica, Arial, sans-serif" fill="#e2e8f0" font-size="13">
14
+ <g>
15
+ <rect x="48" y="60" width="152" height="86" rx="8" fill="url(#band)"/>
16
+ <text x="124" y="96" text-anchor="middle" font-size="15" font-weight="700">L0</text>
17
+ <text x="124" y="118" text-anchor="middle" font-size="12">全局主题</text>
18
+ </g>
19
+ <g>
20
+ <rect x="236" y="76" width="152" height="70" rx="8" fill="#1e293b" stroke="#475569"/>
21
+ <text x="312" y="106" text-anchor="middle" font-size="15" font-weight="700">L1</text>
22
+ <text x="312" y="126" text-anchor="middle" font-size="12">主要分支</text>
23
+ </g>
24
+ <g>
25
+ <rect x="424" y="92" width="152" height="54" rx="8" fill="#1e293b" stroke="#475569"/>
26
+ <text x="500" y="115" text-anchor="middle" font-size="15" font-weight="700">L2</text>
27
+ <text x="500" y="134" text-anchor="middle" font-size="12">局部机制</text>
28
+ </g>
29
+ <g>
30
+ <rect x="612" y="96" width="0" height="0"/>
31
+ </g>
32
+ </g>
33
+ <g fill="#94a3b8" font-family="Segoe UI, Helvetica, Arial, sans-serif" font-size="11">
34
+ <text x="48" y="200">L3 实现细节与 L4 边界反例继续向右下钻</text>
35
+ </g>
36
+ <g fill="none" stroke="#64748b" stroke-width="1.5" marker-end="url(#arrow)">
37
+ </g>
38
+ <defs>
39
+ <marker id="arrow" markerWidth="8" markerHeight="8" refX="6" refY="3" orient="auto">
40
+ <path d="M0,0 L6,3 L0,6 Z" fill="#64748b"/>
41
+ </marker>
42
+ </defs>
43
+ <g fill="none" stroke="#64748b" stroke-width="1.5">
44
+ <line x1="200" y1="103" x2="232" y2="111" marker-end="url(#arrow)"/>
45
+ <line x1="388" y1="111" x2="420" y2="119" marker-end="url(#arrow)"/>
46
+ </g>
47
+ </svg>