concept-atlas-dense-explain 1.1.0 → 2.0.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/README.md +5 -2
- package/bin/cli.mjs +99 -13
- package/package.json +1 -1
- package/packaged-skill/SKILL.md +12 -5
- package/packaged-skill/references/atlas-guide.mdx +35 -6
- package/packaged-skill/references/scroll-guide.mdx +45 -2
- package/template/index.html +53 -42
- package/template/references/atlas-guide.mdx +35 -6
- package/template/references/scroll-guide.mdx +45 -2
- package/template/scroll.html +19 -4
- package/template/src/app/App.jsx +47 -17
- package/template/src/app/use-appearance.js +25 -10
- package/template/src/components/FigureScope.jsx +17 -0
- package/template/src/components/MDXComponents.jsx +379 -5
- package/template/src/components/SkinPicker.jsx +5 -1
- package/template/src/model/figures.js +61 -0
- package/template/src/model/validate-content.js +76 -2
- package/template/src/scroll-main.jsx +4 -1
- package/template/src/styles/core.css +445 -520
- package/template/src/styles/packs/elastic.css +142 -31
- package/template/src/styles/packs/manuscript.css +109 -30
- package/template/src/styles/packs/shadcn.css +133 -30
- package/template/src/views/NodeExplorer.jsx +20 -141
- package/template/vite.config.js +91 -17
package/README.md
CHANGED
|
@@ -35,14 +35,17 @@ Mermaid loads from a CDN at runtime by default (fast builds, needs network);
|
|
|
35
35
|
`--inline-mermaid` bakes it into the HTML for a fully offline single file, and
|
|
36
36
|
`--mermaid-cdn` overrides the CDN URL. Appearance defaults can be baked with
|
|
37
37
|
`--skin`, `--default-mode` and `--style`; readers can still switch in the UI.
|
|
38
|
+
Local figures link by default (small HTML, ship `assets/` beside the output);
|
|
39
|
+
`--inline-assets` bakes them in as base64 for a self-contained file, and a
|
|
40
|
+
per-figure `inline={true|false}` overrides that choice.
|
|
38
41
|
|
|
39
42
|
Run `npx concept-atlas-dense-explain help` for the full flag list.
|
|
40
43
|
|
|
41
44
|
## Links
|
|
42
45
|
|
|
43
46
|
- Repository: https://github.com/wurenrumian/concept-atlas
|
|
44
|
-
- Framework guide: [`docs/FRAMEWORK.md`](https://github.com/wurenrumian/concept-atlas/blob/
|
|
45
|
-
- Usage recipes: [`docs/USAGE.md`](https://github.com/wurenrumian/concept-atlas/blob/
|
|
47
|
+
- Framework guide: [`docs/FRAMEWORK.md`](https://github.com/wurenrumian/concept-atlas/blob/master/docs/FRAMEWORK.md)
|
|
48
|
+
- Usage recipes: [`docs/USAGE.md`](https://github.com/wurenrumian/concept-atlas/blob/master/docs/USAGE.md)
|
|
46
49
|
|
|
47
50
|
## License
|
|
48
51
|
|
package/bin/cli.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { access, constants, copyFile, cp, mkdir, readFile, rm, rename, writeFile } from 'node:fs/promises';
|
|
3
|
-
import { existsSync } from 'node:fs';
|
|
3
|
+
import { existsSync, statSync } from 'node:fs';
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import { build } from 'vite';
|
|
6
6
|
import { fileURLToPath } from 'node:url';
|
|
@@ -9,19 +9,20 @@ 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() {
|
|
16
16
|
console.log('Usage:');
|
|
17
|
-
console.log(' npx concept-atlas-dense-explain <input.mdx>... [--mode atlas|scroll] [--skin <id>] [--default-mode dark|light|system] [--style <id>] [-o output.html|dir] [--force] [--concurrency N] [--
|
|
17
|
+
console.log(' npx concept-atlas-dense-explain <input.mdx>... [--mode atlas|scroll] [--skin <id>] [--default-mode dark|light|system] [--style <id>] [-o output.html|dir] [--force] [--concurrency N] [--inline-assets] [--inline-mermaid] [--mermaid-cdn <url>] [--json] [--no-validate]');
|
|
18
18
|
console.log(' npx concept-atlas-dense-explain render <input.mdx>... [-o output.html|dir]');
|
|
19
19
|
console.log(' npx concept-atlas-dense-explain validate <input.mdx> [--mode atlas|scroll] [--strict] [--json]');
|
|
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
|
-
console.log('
|
|
25
|
+
console.log(' Figures are kept as relative links by default (small HTML; ship the assets/ dir beside it). --inline-assets bakes every local image into the HTML as base64 instead; a per-tag inline={true|false} on <Figure> overrides that for one image. --link-assets is kept as an explicit alias for the default.');
|
|
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.');
|
|
26
27
|
console.log(` --skin bakes a default palette (${SKINS.map(skin => skin.id).join(', ')}); --default-mode bakes a default dark/light mode; --style bakes a default component style (${COMPONENT_STYLES.map(style => style.id).join(', ')}). Readers can still switch in the UI.`);
|
|
27
28
|
}
|
|
@@ -30,6 +31,11 @@ async function exists(filePath) {
|
|
|
30
31
|
try { await access(filePath, constants.F_OK); return true; } catch { return false; }
|
|
31
32
|
}
|
|
32
33
|
|
|
34
|
+
/** Byte size of an asset, or null when it does not exist. */
|
|
35
|
+
function assetByteSize(filePath) {
|
|
36
|
+
try { return statSync(filePath).size; } catch { return null; }
|
|
37
|
+
}
|
|
38
|
+
|
|
33
39
|
function flagValue(flags, names) {
|
|
34
40
|
for (const name of names) {
|
|
35
41
|
const index = flags.indexOf(name);
|
|
@@ -45,6 +51,8 @@ function fail(message) {
|
|
|
45
51
|
}
|
|
46
52
|
|
|
47
53
|
const VALUE_FLAGS = new Set(['--mode', '-o', '--output', '--concurrency', '--skin', '--default-mode', '--style', '--mermaid-cdn']);
|
|
54
|
+
const BOOLEAN_FLAGS = new Set(['--force', '--json', '--strict', '--no-validate', '--link-assets', '--inline-assets', '--inline-mermaid', '--help', '-h', '--version', '-v']);
|
|
55
|
+
const COMMAND_NAMES = ['help', 'create', 'new', 'render', 'validate', 'guide'];
|
|
48
56
|
|
|
49
57
|
/** Splits argv into flags, flag values and positional arguments. */
|
|
50
58
|
function parseFlags(argv) {
|
|
@@ -65,6 +73,26 @@ function parseFlags(argv) {
|
|
|
65
73
|
return { flags, values, positional };
|
|
66
74
|
}
|
|
67
75
|
|
|
76
|
+
/**
|
|
77
|
+
* Finds the subcommand even when flags precede it (`--force create x.mdx`),
|
|
78
|
+
* skipping flag values so a value like `create` isn't mistaken for a command.
|
|
79
|
+
* The first non-flag token that isn't a command means `render` with inputs.
|
|
80
|
+
*/
|
|
81
|
+
function extractCommand(argv) {
|
|
82
|
+
const rest = [...argv];
|
|
83
|
+
for (let i = 0; i < rest.length; i += 1) {
|
|
84
|
+
const arg = rest[i];
|
|
85
|
+
if (VALUE_FLAGS.has(arg)) { i += 1; continue; }
|
|
86
|
+
if (arg.startsWith('-') && arg.length > 1) continue;
|
|
87
|
+
if (COMMAND_NAMES.includes(arg)) {
|
|
88
|
+
rest.splice(i, 1);
|
|
89
|
+
return { command: arg, rest };
|
|
90
|
+
}
|
|
91
|
+
return { command: 'render', rest };
|
|
92
|
+
}
|
|
93
|
+
return { command: 'render', rest };
|
|
94
|
+
}
|
|
95
|
+
|
|
68
96
|
/**
|
|
69
97
|
* A single input may name an output file; a batch needs a directory, because the
|
|
70
98
|
* per-target file names come from the inputs.
|
|
@@ -102,7 +130,30 @@ function printDiagnostics(source, options, { json, label = null, quiet = false }
|
|
|
102
130
|
return result;
|
|
103
131
|
}
|
|
104
132
|
|
|
105
|
-
const command
|
|
133
|
+
const { command, rest } = extractCommand(args);
|
|
134
|
+
args = rest;
|
|
135
|
+
|
|
136
|
+
if (args.includes('--help') || args.includes('-h')) { usage(); process.exit(0); }
|
|
137
|
+
if (args.includes('--version') || args.includes('-v')) {
|
|
138
|
+
const pkg = JSON.parse(await readFile(new URL('../package.json', import.meta.url), 'utf8'));
|
|
139
|
+
console.log(pkg.version);
|
|
140
|
+
process.exit(0);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Typos like `--mdoe atlas` used to be accepted silently; fail fast instead.
|
|
144
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
145
|
+
const arg = args[i];
|
|
146
|
+
if (VALUE_FLAGS.has(arg)) {
|
|
147
|
+
if (i + 1 >= args.length || (args[i + 1].startsWith('-') && args[i + 1].length > 1)) {
|
|
148
|
+
fail(`Missing value for ${arg}`);
|
|
149
|
+
}
|
|
150
|
+
i += 1;
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
if (arg.startsWith('-') && arg.length > 1 && !BOOLEAN_FLAGS.has(arg)) {
|
|
154
|
+
fail(`Unknown flag: ${arg}`);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
106
157
|
|
|
107
158
|
if (command === 'help') { usage(); process.exit(0); }
|
|
108
159
|
|
|
@@ -206,6 +257,10 @@ const strict = parsed.flags.has('--strict');
|
|
|
206
257
|
const skipValidate = parsed.flags.has('--no-validate');
|
|
207
258
|
const force = parsed.flags.has('--force');
|
|
208
259
|
const linkAssets = parsed.flags.has('--link-assets');
|
|
260
|
+
const inlineAssets = parsed.flags.has('--inline-assets');
|
|
261
|
+
if (linkAssets && inlineAssets) {
|
|
262
|
+
fail('--link-assets and --inline-assets are mutually exclusive (linking is the default).');
|
|
263
|
+
}
|
|
209
264
|
const inlineMermaid = parsed.flags.has('--inline-mermaid');
|
|
210
265
|
const mermaidCdn = parsed.values.get('--mermaid-cdn') || null;
|
|
211
266
|
const modeFlag = parsed.values.get('--mode') || null;
|
|
@@ -239,7 +294,9 @@ if (command === 'validate') {
|
|
|
239
294
|
filePath: target,
|
|
240
295
|
mode: modeFlag,
|
|
241
296
|
strict,
|
|
297
|
+
inlineAssets,
|
|
242
298
|
assetExists: spec => existsSync(path.resolve(path.dirname(target), spec)),
|
|
299
|
+
assetSize: spec => assetByteSize(path.resolve(path.dirname(target), spec)),
|
|
243
300
|
}, { json });
|
|
244
301
|
process.exit(countBySeverity(result.diagnostics).error ? 1 : 0);
|
|
245
302
|
}
|
|
@@ -280,7 +337,9 @@ const validations = sources.map((source, index) => printDiagnostics(source, {
|
|
|
280
337
|
filePath: inputs[index],
|
|
281
338
|
mode: modeFlag,
|
|
282
339
|
strict,
|
|
340
|
+
inlineAssets,
|
|
283
341
|
assetExists: spec => existsSync(path.resolve(path.dirname(inputs[index]), spec)),
|
|
342
|
+
assetSize: spec => assetByteSize(path.resolve(path.dirname(inputs[index]), spec)),
|
|
284
343
|
}, json
|
|
285
344
|
? { json: false, quiet: true }
|
|
286
345
|
: { json: false, label: multi ? inputs[index] : null }));
|
|
@@ -304,10 +363,13 @@ const jobs = inputs.map((input, index) => {
|
|
|
304
363
|
console.error(`Could not detect the MDX carrier for ${input}; choose --mode atlas or --mode scroll.`);
|
|
305
364
|
process.exit(1);
|
|
306
365
|
}
|
|
307
|
-
|
|
308
|
-
|
|
366
|
+
// Figures link by default. If the HTML is written outside the MDX's directory
|
|
367
|
+
// its relative image paths no longer resolve, so warn unless --inline-assets
|
|
368
|
+
// bakes them in.
|
|
369
|
+
if (!inlineAssets && path.resolve(path.dirname(outputs[index])) !== path.resolve(path.dirname(input))) {
|
|
370
|
+
console.error(`警告:默认外链图片,但 ${outputs[index]} 不在 ${path.dirname(input)} 内,相对图片路径会失效;请用 --inline-assets,或把 assets/ 一并放到输出目录。`);
|
|
309
371
|
}
|
|
310
|
-
return { input, output: outputs[index], mode, title: extractPageTitle(sources[index]), features: detectFeatures(sources[index]),
|
|
372
|
+
return { input, output: outputs[index], mode, title: extractPageTitle(sources[index]), features: detectFeatures(sources[index]), inlineAssets };
|
|
311
373
|
});
|
|
312
374
|
|
|
313
375
|
const limit = clampConcurrency(parsed.values.get('--concurrency'), jobs.length);
|
|
@@ -323,7 +385,7 @@ if (failures.length) {
|
|
|
323
385
|
}
|
|
324
386
|
|
|
325
387
|
async function buildOne(job) {
|
|
326
|
-
const { input, output, mode, title, features,
|
|
388
|
+
const { input, output, mode, title, features, inlineAssets: inline } = job;
|
|
327
389
|
const templateEntry = mode === 'atlas' ? 'index.html' : 'scroll.html';
|
|
328
390
|
// Each build gets its own scratch outDir: the template always writes
|
|
329
391
|
// `index.html`/`scroll.html`, so concurrent builds sharing a directory would
|
|
@@ -332,7 +394,8 @@ async function buildOne(job) {
|
|
|
332
394
|
await mkdir(path.dirname(output), { recursive: true });
|
|
333
395
|
await mkdir(scratch, { recursive: true });
|
|
334
396
|
const define = { __ATLAS_FEATURES__: JSON.stringify(features) };
|
|
335
|
-
|
|
397
|
+
// 'false' is the default (link); only --inline-assets sets 'true'.
|
|
398
|
+
define.__ATLAS_INLINE_ASSETS__ = inline ? 'true' : 'false';
|
|
336
399
|
if (title) define.__ATLAS_PAGE_TITLE__ = JSON.stringify(title);
|
|
337
400
|
if (skinFlag) define.__ATLAS_DEFAULT_SKIN__ = JSON.stringify(skinFlag);
|
|
338
401
|
if (defaultModeFlag) define.__ATLAS_DEFAULT_MODE__ = JSON.stringify(defaultModeFlag);
|
|
@@ -353,9 +416,32 @@ async function buildOne(job) {
|
|
|
353
416
|
rollupOptions: { input: path.join(templateRoot, templateEntry) },
|
|
354
417
|
},
|
|
355
418
|
});
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
419
|
+
// Swap the new build in without a window where no output exists: on
|
|
420
|
+
// POSIX rename replaces atomically; the fallback path (Windows can refuse
|
|
421
|
+
// to overwrite a locked file) moves the old output aside first and
|
|
422
|
+
// restores it if the swap fails, instead of rm-ing it up front.
|
|
423
|
+
const built = path.join(scratch, templateEntry);
|
|
424
|
+
try {
|
|
425
|
+
await rename(built, output);
|
|
426
|
+
} catch (error) {
|
|
427
|
+
if (error.code !== 'EEXIST' && error.code !== 'EPERM' && error.code !== 'EACCES') throw error;
|
|
428
|
+
const backup = `${output}.bak-${process.pid}-${Date.now()}`;
|
|
429
|
+
let hasBackup = false;
|
|
430
|
+
try {
|
|
431
|
+
await rename(output, backup);
|
|
432
|
+
hasBackup = true;
|
|
433
|
+
} catch (backupError) {
|
|
434
|
+
if (backupError.code !== 'ENOENT') throw backupError; // old output kept, swap aborted
|
|
435
|
+
}
|
|
436
|
+
try {
|
|
437
|
+
await rename(built, output);
|
|
438
|
+
} catch (swapError) {
|
|
439
|
+
if (hasBackup) await rename(backup, output);
|
|
440
|
+
throw swapError;
|
|
441
|
+
}
|
|
442
|
+
if (hasBackup) await rm(backup, { force: true });
|
|
443
|
+
}
|
|
444
|
+
console.log(`Built ${mode} HTML: ${output}${title ? ` [tab: ${title}]` : ''}${describeFeatures(features)}${inline ? ' [figures inlined]' : ' [figures linked]'}`);
|
|
359
445
|
return { ok: true, input, output };
|
|
360
446
|
} catch (error) {
|
|
361
447
|
return { ok: false, input, output, error };
|
package/package.json
CHANGED
package/packaged-skill/SKILL.md
CHANGED
|
@@ -32,7 +32,7 @@ This is the only runner — there is nothing to look for. Do not search for a lo
|
|
|
32
32
|
npx concept-atlas-dense-explain validate <file>.mdx --json
|
|
33
33
|
```
|
|
34
34
|
Diagnostics are `CODE line:column message`. Fix all `error`s and re-run; address warnings when cheap.
|
|
35
|
-
6. Compile: `npx concept-atlas-dense-explain <file>.mdx --mode atlas|scroll [-o out.html] [--skin <id>] [--default-mode dark|light|system] [--style <id>] [--
|
|
35
|
+
6. Compile: `npx concept-atlas-dense-explain <file>.mdx --mode atlas|scroll [-o out.html] [--skin <id>] [--default-mode dark|light|system] [--style <id>] [--mermaid-cdn <url>]`. Output is a standalone HTML beside the MDX unless `-o` is given. Validation errors abort the build (`--no-validate` forces a knowingly broken build). **Mermaid stays on its runtime CDN by default — do not pass `--inline-mermaid` on your own.** Only add `--inline-mermaid` when the user explicitly asks for a fully offline single file, since it bloats the HTML with the whole Mermaid bundle.
|
|
36
36
|
7. **Appearance (optional)**: pages ship a reader-facing appearance menu — palette (`aurora` indigo, `ember` gold, `verdant` forest, `sakura` pink-plum, `noir` ink), a dark/light toggle, and a component style pack (`manuscript` editorial marginalia, `classic` boxed cards, `shadcn` hairline-bordered minimal UI, `elastic` bordered observability panels). The shipped default is aurora × manuscript × light; choices persist in localStorage across both carriers. Bake different compile-time defaults with `--skin ember --default-mode dark --style classic` (or `CONCEPT_ATLAS_SKIN` / `CONCEPT_ATLAS_DEFAULT_MODE` / `CONCEPT_ATLAS_STYLE` on the repo build); `--default-mode` honors `dark`/`light` and resolves `system` to the carrier default (`light`). Bake a default only when the user asks for one — content MDX never sets appearance.
|
|
37
37
|
8. For several documents, pass them all in one call: `npx concept-atlas-dense-explain a.mdx b.mdx c.mdx -o dist --force [--concurrency 3]` (`-o` is then a directory; everything validates first, then builds in parallel). Builds bundle only the heavy renderers the content uses: no `<Math>` skips KaTeX's ~1.4MB inlined fonts, and Mermaid stays on a CDN. Never add dummy `<Math>`/`<Mermaid>` nodes to "enable" them.
|
|
38
38
|
9. Report the shell, output path, validation result (errors/warnings), and limitations. Do not claim interactions you did not verify.
|
|
@@ -48,9 +48,11 @@ 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
|
-
- Graphics and extensions: `Mermaid`, `RelationMap`, `RelationPath`, `Math`, `MathBlock`, `Chart`, `Figure` (alias `Image`), `Cite`, `References`
|
|
55
|
+
- Graphics and extensions: `Mermaid`, `RelationMap`, `RelationPath`, `Math`, `MathBlock`, `Chart`, `Figure` (alias `Image`), `FigureRef`, `Cite`, `References`
|
|
54
56
|
|
|
55
57
|
## Authoring rules
|
|
56
58
|
|
|
@@ -62,20 +64,25 @@ 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.
|
|
65
|
-
- **
|
|
66
|
-
- **
|
|
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.
|
|
70
|
+
- **Figure**: a relative `src` (`./assets/diagram.png`) stays a relative link by default; compile with `--inline-assets` to bake every local image in as base64, or set `inline={true}` / `inline={false}` on one figure to override (precedence: per-figure prop > global flag > link). `http(s)` URLs stay links and warn (`ASSET_REMOTE`). Always set `alt`; 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.
|
|
71
|
+
- **Figure numbering**: give a figure an `id` and reference it with `<FigureRef id="..." />` to render "图 N". Numbering follows document order (per node in `atlas`, whole document in `scroll`) and reflows automatically, so never hand-write "图 1"; an explicit `label` still wins. A ref with no matching `id` warns (`FIGURE_REF_UNRESOLVED`).
|
|
72
|
+
- **Figure size & cost**: linking keeps the HTML small but requires the output to sit beside the MDX's `assets/` (the CLI warns when `-o` points elsewhere). Inlining is what makes a screenshot-heavy page large (a page with no heavy renderers otherwise lands near 250KB), and an inlined asset over 512KB warns (`ASSET_LARGE`). Decide the trade-off with the user instead of choosing silently.
|
|
67
73
|
- **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.
|
|
68
74
|
- Continuous reading is configured on the shell, not with manual CSS: `spacing="compact|comfortable|airy"` for rhythm, `fontSize="compact|normal|large|xlarge"` (or numeric `scale`/`lineHeight`) for text size.
|
|
69
75
|
|
|
70
76
|
## Validation diagnostics
|
|
71
77
|
|
|
72
|
-
`validate` and the build print `CODE line:column message`. Fix these `error`s before building: `UNKNOWN_COMPONENT`, `CARRIER_MISSING`, `CARRIER_CONFLICT`, `CARRIER_MODE_MISMATCH`, `NODE_MISSING_ID`, `DUPLICATE_NODE_ID`, `NODE_MISSING_TITLE`, `MISSING_PARENT`, `GRAPH_ROOT_UNRESOLVED`, `REF_MISSING_ID`, `REF_UNRESOLVED`, `RELATION_FROM_UNRESOLVED`, `RELATION_TO_UNRESOLVED`. Warnings worth fixing: `NODE_MISSING_SUMMARY`, `NODE_NO_CORE_CONTENT`, `UNKNOWN_LEVEL`, `UNKNOWN_KIND`, `MECHANISM_KIND_UNVERIFIED`, `FAILURE_KIND_UNSTRUCTURED`, `FAILURE_MODE_EMPTY`, `UNKNOWN_RELATION_TYPE`, `RELATION_MISSING_LABEL`, `RELATION_SELF`, `PROP_EXPECTS_ARRAY`, `MATH_CHILDREN_BRACES`, `PROSE_EXPRESSION`, `FRONTMATTER_UNSUPPORTED`, `GRAPH_MISSING_ROOT`, `FIGURE_MISSING_SRC`, `ASSET_MISSING`, `REF_SELF`; `NO_ROOT_LEVEL`, `MULTIPLE_ROOT_LEVEL`, `MECHANISM_KIND_UNVERIFIED` and `FAILURE_KIND_UNSTRUCTURED` are warnings that `--strict` promotes to errors.
|
|
78
|
+
`validate` and the build print `CODE line:column message`. Fix these `error`s before building: `UNKNOWN_COMPONENT`, `CARRIER_MISSING`, `CARRIER_CONFLICT`, `CARRIER_MODE_MISMATCH`, `NODE_MISSING_ID`, `DUPLICATE_NODE_ID`, `NODE_MISSING_TITLE`, `MISSING_PARENT`, `GRAPH_ROOT_UNRESOLVED`, `REF_MISSING_ID`, `REF_UNRESOLVED`, `RELATION_FROM_UNRESOLVED`, `RELATION_TO_UNRESOLVED`. Warnings worth fixing: `NODE_MISSING_SUMMARY`, `NODE_NO_CORE_CONTENT`, `UNKNOWN_LEVEL`, `UNKNOWN_KIND`, `MECHANISM_KIND_UNVERIFIED`, `FAILURE_KIND_UNSTRUCTURED`, `FAILURE_MODE_EMPTY`, `UNKNOWN_RELATION_TYPE`, `RELATION_MISSING_LABEL`, `RELATION_SELF`, `PROP_EXPECTS_ARRAY`, `MATH_CHILDREN_BRACES`, `PROSE_EXPRESSION`, `FRONTMATTER_UNSUPPORTED`, `GRAPH_MISSING_ROOT`, `FIGURE_MISSING_SRC`, `ASSET_MISSING`, `ASSET_REMOTE`, `ASSET_LARGE`, `FIGURE_REF_UNRESOLVED`, `FIGURE_REF_MISSING_ID`, `FIGURE_DUPLICATE_ID`, `REF_SELF`; `NO_ROOT_LEVEL`, `MULTIPLE_ROOT_LEVEL`, `MECHANISM_KIND_UNVERIFIED` and `FAILURE_KIND_UNSTRUCTURED` are warnings that `--strict` promotes to errors.
|
|
73
79
|
|
|
74
80
|
## Before you report
|
|
75
81
|
|
|
76
82
|
- All `error` diagnostics resolved (or `--no-validate` explicitly justified).
|
|
77
83
|
- All `ConceptRef`, `Relation` endpoints, and `ConceptGraph root` point at existing node ids.
|
|
78
84
|
- Every node has `title` + `summary`; every `Relation` has a `label`.
|
|
85
|
+
- You left Mermaid on its CDN default and did not pass `--inline-mermaid` unless the user explicitly asked for a fully offline single file.
|
|
79
86
|
- The HTML file exists at the reported path.
|
|
80
87
|
|
|
81
88
|
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:'
|
|
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
|
|
|
@@ -132,10 +132,11 @@
|
|
|
132
132
|
<Chart title="留存趋势" type="line" labels={['第1周','第2周','第3周','第4周']} series={[{name:'留存率',values:[100,72,58,49]}]} />
|
|
133
133
|
</ConceptNode>
|
|
134
134
|
|
|
135
|
-
<ConceptNode id="figure-demo" title="Figure:图片与题注一起出现" level="L2" parent="extension-family" summary="Figure
|
|
136
|
-
<Definition
|
|
137
|
-
<Figure src="./assets/sample-diagram.svg" alt="概念缩放示意"
|
|
138
|
-
<
|
|
135
|
+
<ConceptNode id="figure-demo" title="Figure:图片与题注一起出现" level="L2" parent="extension-family" summary="Figure 把图片、题注编号和说明绑定;默认外链,按需内联。">
|
|
136
|
+
<Definition>相对路径的图片默认保持外链,构建时用 --inline-assets 可全部内联,单张图也可用 `inline={true}` 或 `inline={false}` 覆盖;远程 URL 保持不变。给图一个 id,正文就能用 FigureRef 自动引用“图 N”。</Definition>
|
|
137
|
+
<Figure id="zoom-scale" src="./assets/sample-diagram.svg" alt="概念缩放示意" caption="概念缩放:同一主题可以从 L0 全局逐层下钻到 L4 边界反例。" />
|
|
138
|
+
<Example title="自动图号">正文写“见 <FigureRef id="zoom-scale" />”,编号会随插图自动重排,不必手写“图 1”。</Example>
|
|
139
|
+
<Boundary>内联大图会显著增大 HTML;截图类内容建议控制尺寸,或对单张图设 `inline={false}`。</Boundary>
|
|
139
140
|
</ConceptNode>
|
|
140
141
|
|
|
141
142
|
<ConceptNode id="code-demo" title="CodeBlock:把命令、配置或伪代码放进正文" level="L2" parent="extension-family" summary="与绑定节点的 Implementation 不同,CodeBlock 可以出现在任何位置,并支持标题、题注、换行和行号。">
|
|
@@ -154,6 +155,31 @@ npm run build`}</CodeBlock>
|
|
|
154
155
|
<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
156
|
</ConceptNode>
|
|
156
157
|
|
|
158
|
+
<ConceptNode id="learning-family" title="学习闭环组件:从推演到自测再到收尾" level="L1" parent="showroom" summary="WorkedExample、Quiz、KeyTakeaways 与 Confidence 把讲解补成可推演、可自检、可追溯、可收束的闭环。">
|
|
159
|
+
<Definition>LearningObjectives 和 KeyQuestion 负责开场;这一组负责中段逐步推演、即时自测、来源与置信度标注,以及结尾的压缩要点。</Definition>
|
|
160
|
+
<WorkedExample title="一次动态库加载失败的完整追踪" problem="程序编译成功却在启动时退出,需要逐步定位而不是直接猜测。">
|
|
161
|
+
<Step number="1" title="确认格式与架构" reason="格式或架构不匹配会让 loader 直接拒绝文件">file app && readelf -h app</Step>
|
|
162
|
+
<Step number="2" title="检查直接依赖" reason="排除共享库缺失这一最常见原因">ldd app</Step>
|
|
163
|
+
<Step number="3" title="核对搜索路径" reason="依赖存在但不在 loader 的搜索路径上同样会失败">echo $LD_LIBRARY_PATH</Step>
|
|
164
|
+
</WorkedExample>
|
|
165
|
+
<Quiz question="哪个阶段负责解析跨文件符号?" answer="链接阶段" tone="success">
|
|
166
|
+
链接器把目标文件中的未定义引用与其他目标文件或库中的定义匹配起来,装载阶段只做重定位。
|
|
167
|
+
</Quiz>
|
|
168
|
+
<KeyTakeaways items={['每一步都要给出理由,而不只是结果', '自测用于主动回忆,而不是装饰', '结尾要点应压缩,而不是复述正文']} />
|
|
169
|
+
<Definition>ELF 的装载语义由规范定义<Source kind="spec" label="System V ABI" href="https://refspecs.linuxfoundation.org/elf/gabi4+/contents.html" />,而不是某个实现的约定。</Definition>
|
|
170
|
+
<Confidence level="high" basis="规范定义 + 实测复现">装载阶段的不变量由 ABI 明确规定,可由 loader 行为直接验证。</Confidence>
|
|
171
|
+
</ConceptNode>
|
|
172
|
+
|
|
173
|
+
<ConceptNode id="data-family" title="数据与行为模型:表格、指标、状态、分支与回路" level="L1" parent="showroom" summary="DataTable、Metric、StateMachine、DecisionTree、FeedbackLoop 和 CodeDiff 分别表达中立数据、头条数字、状态迁移、分支决策、反馈回路与代码改动。">
|
|
174
|
+
<Definition>当线性 Flow 和二维矩阵不足以表达信息时,用这些模型补上状态、分支、回路、数据形态和代码差异。缺页<Term name="缺页异常">访问未驻留内存页时由 MMU 触发的可恢复异常</Term>属于典型的状态迁移问题。</Definition>
|
|
175
|
+
<DataTable title="三种异常对比" caption="同一现象可能来自不同层次,先分类再定位。" headers={['异常', '触发条件', '典型信号']} rows={[['缺页', '访问未驻留页', '可恢复'], ['段错误', '非法地址访问', 'SIGSEGV'], ['除零', '整数除以零', 'SIGFPE']]} />
|
|
176
|
+
<Metric label="P99 延迟" value="128" unit="ms" delta="-18%" trend="down" note="相对上一版本,来自压测环境。" />
|
|
177
|
+
<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'}]} />
|
|
178
|
+
<DecisionTree title="先看哪一层" question="页面没有渲染,先排查哪一层?" branches={[{condition:'有构建错误',outcome:'先修构建',tone:'danger'},{condition:'构建通过',outcome:'检查运行时',branches:[{condition:'组件抛错',outcome:'看错误边界',tone:'warn'},{condition:'数据为空',outcome:'检查数据来源',tone:'info'}]}]} />
|
|
179
|
+
<FeedbackLoop title="缓存命中回路" type="reinforcing" nodes={[{label:'命中率上升',description:'更多请求由缓存服务'},{label:'后端压力下降',description:'响应更快'},{label:'体验提升',description:'更多请求进入'}]} />
|
|
180
|
+
<CodeDiff language="javascript" title="避免空值崩溃" before={`const name = user.profile.name;`} after={`const name = user?.profile?.name ?? '匿名';`} caption="可选链与空值合并把运行时错误前移为可预期的默认值。" />
|
|
181
|
+
</ConceptNode>
|
|
182
|
+
|
|
157
183
|
<Relation from="structure-family" to="content-family" type="precedes" label="先搭骨架" />
|
|
158
184
|
<Relation from="content-family" to="verification-family" type="uses" label="补充证据" />
|
|
159
185
|
<Relation from="concept-models" to="presentation-family" type="implements" label="结构落地" />
|
|
@@ -165,5 +191,8 @@ npm run build`}</CodeBlock>
|
|
|
165
191
|
<Relation from="pyramid-funnel-demo" to="framework-demo" type="contrasts" label="分层 vs 循环" />
|
|
166
192
|
<Relation from="math-demo" to="formula-demo" type="contrasts" label="LaTeX vs 纯文本公式" />
|
|
167
193
|
<Relation from="chart-demo" to="matrix-demo" type="contrasts" label="连续数据 vs 二维定位" />
|
|
194
|
+
<Relation from="verification-family" to="learning-family" type="precedes" label="证据之后自测" />
|
|
195
|
+
<Relation from="learning-family" to="data-family" type="uses" label="自测需要数据模型" />
|
|
196
|
+
<Relation from="concept-models" to="data-family" type="contrasts" label="静态模型 vs 行为模型" />
|
|
168
197
|
</ConceptGraph>
|
|
169
198
|
</ExplainPage>
|
|
@@ -58,9 +58,52 @@ 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
|
-
<ScrollProse>配图和引用让讲解从断言变成可追溯的论述<Cite id="tufte1983"
|
|
63
|
-
<Figure src="./assets/sample-diagram.svg" alt="概念缩放示意"
|
|
105
|
+
<ScrollProse>配图和引用让讲解从断言变成可追溯的论述<Cite id="tufte1983" />。图片默认保持外链(`--inline-assets` 可全部内联),给图一个 id 后正文可用 <FigureRef id="zoom-scale" /> 自动引用编号。</ScrollProse>
|
|
106
|
+
<Figure id="zoom-scale" src="./assets/sample-diagram.svg" alt="概念缩放示意" caption="概念缩放:同一主题可以从 L0 全局逐层下钻到 L4 边界反例。" />
|
|
64
107
|
<ScrollGrid columns="2">
|
|
65
108
|
<DecisionMatrix title="需要确认" headers={['变量','检查']} rows={[['新体验','是否改善核心任务'],['旧体验','能否满足需求'],['替换成本','迁移是否可接受']]} />
|
|
66
109
|
<Compare items={[{label:'连续阅读',rows:['信息密度','适用场景'],values:['高','按章节论证']},{label:'概念图谱',rows:['信息密度','适用场景'],values:['中','概念下钻']}]} />
|
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>
|