concept-atlas-dense-explain 0.6.0 → 0.6.1
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 +7 -4
- package/package.json +1 -1
- package/skill/SKILL.md +6 -5
- package/template/index.html +1 -0
- package/template/scripts/build.mjs +19 -1
- package/template/scroll.html +1 -0
- package/template/src/model/validate-content.js +25 -0
- package/template/vite.config.js +26 -0
package/bin/cli.mjs
CHANGED
|
@@ -4,7 +4,7 @@ import { existsSync } from 'node:fs';
|
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import { build } from 'vite';
|
|
6
6
|
import { fileURLToPath } from 'node:url';
|
|
7
|
-
import { validateMdxSource, countBySeverity, detectFeatures } from '../template/src/model/validate-content.js';
|
|
7
|
+
import { validateMdxSource, countBySeverity, detectFeatures, extractPageTitle } from '../template/src/model/validate-content.js';
|
|
8
8
|
import { SKINS, normalizeSkin, COMPONENT_STYLES, normalizeStyle } from '../template/src/model/skins.js';
|
|
9
9
|
|
|
10
10
|
const packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
@@ -144,6 +144,7 @@ if (command === 'create' || command === 'new') {
|
|
|
144
144
|
}
|
|
145
145
|
await mkdir(path.dirname(output), { recursive: true });
|
|
146
146
|
const template = mode === 'atlas' ? `\
|
|
147
|
+
{/* shell 的 title 会成为浏览器标签页标题;页面图标固定为 📃。请把“主题名称”改成真实标题。 */}
|
|
147
148
|
<ExplainPage id="topic-id" title="主题名称" summary="用一句话说明这个主题解决什么问题。">
|
|
148
149
|
<ConceptGraph root="root-node">
|
|
149
150
|
<ConceptNode id="root-node" title="核心概念" level="L0" summary="给读者建立整体认知。">
|
|
@@ -168,6 +169,7 @@ if (command === 'create' || command === 'new') {
|
|
|
168
169
|
</ConceptGraph>
|
|
169
170
|
</ExplainPage>
|
|
170
171
|
` : `\
|
|
172
|
+
{/* shell 的 title 会成为浏览器标签页标题;页面图标固定为 📃。请把“主题名称”改成真实标题。 */}
|
|
171
173
|
<ScrollDocument>
|
|
172
174
|
<ScrollHeader title="主题名称">用一两句话说明主题、背景和读者应该带走的判断。</ScrollHeader>
|
|
173
175
|
|
|
@@ -300,7 +302,7 @@ const jobs = inputs.map((input, index) => {
|
|
|
300
302
|
if (linkAssets && path.resolve(path.dirname(outputs[index])) !== path.resolve(path.dirname(input))) {
|
|
301
303
|
console.error(`警告:--link-assets 下 ${outputs[index]} 不在 ${path.dirname(input)} 内,相对图片路径会失效。`);
|
|
302
304
|
}
|
|
303
|
-
return { input, output: outputs[index], mode, features: detectFeatures(sources[index]), linkAssets };
|
|
305
|
+
return { input, output: outputs[index], mode, title: extractPageTitle(sources[index]), features: detectFeatures(sources[index]), linkAssets };
|
|
304
306
|
});
|
|
305
307
|
|
|
306
308
|
const limit = clampConcurrency(parsed.values.get('--concurrency'), jobs.length);
|
|
@@ -316,7 +318,7 @@ if (failures.length) {
|
|
|
316
318
|
}
|
|
317
319
|
|
|
318
320
|
async function buildOne(job) {
|
|
319
|
-
const { input, output, mode, features, linkAssets: link } = job;
|
|
321
|
+
const { input, output, mode, title, features, linkAssets: link } = job;
|
|
320
322
|
const templateEntry = mode === 'atlas' ? 'index.html' : 'scroll.html';
|
|
321
323
|
// Each build gets its own scratch outDir: the template always writes
|
|
322
324
|
// `index.html`/`scroll.html`, so concurrent builds sharing a directory would
|
|
@@ -326,6 +328,7 @@ async function buildOne(job) {
|
|
|
326
328
|
await mkdir(scratch, { recursive: true });
|
|
327
329
|
const define = { __ATLAS_FEATURES__: JSON.stringify(features) };
|
|
328
330
|
if (link) define.__ATLAS_INLINE_ASSETS__ = 'false';
|
|
331
|
+
if (title) define.__ATLAS_PAGE_TITLE__ = JSON.stringify(title);
|
|
329
332
|
if (skinFlag) define.__ATLAS_DEFAULT_SKIN__ = JSON.stringify(skinFlag);
|
|
330
333
|
if (defaultModeFlag) define.__ATLAS_DEFAULT_MODE__ = JSON.stringify(defaultModeFlag);
|
|
331
334
|
if (styleFlag) define.__ATLAS_DEFAULT_STYLE__ = JSON.stringify(styleFlag);
|
|
@@ -343,7 +346,7 @@ async function buildOne(job) {
|
|
|
343
346
|
});
|
|
344
347
|
await rm(output, { force: true });
|
|
345
348
|
await rename(path.join(scratch, templateEntry), output);
|
|
346
|
-
console.log(`Built ${mode} HTML: ${output}${describeFeatures(features)}${link ? ' [figures linked]' : ''}`);
|
|
349
|
+
console.log(`Built ${mode} HTML: ${output}${title ? ` [tab: ${title}]` : ''}${describeFeatures(features)}${link ? ' [figures linked]' : ''}`);
|
|
347
350
|
return { ok: true, input, output };
|
|
348
351
|
} catch (error) {
|
|
349
352
|
return { ok: false, input, output, error };
|
package/package.json
CHANGED
package/skill/SKILL.md
CHANGED
|
@@ -17,7 +17,7 @@ If the user only wants the prompt/methodology and not files, still choose a shel
|
|
|
17
17
|
npx concept-atlas-dense-explain guide --mode atlas -o ./concept-atlas-atlas-guide.mdx
|
|
18
18
|
npx concept-atlas-dense-explain guide --mode scroll -o ./concept-atlas-scroll-guide.mdx
|
|
19
19
|
```
|
|
20
|
-
It is a real, compilable MDX file
|
|
20
|
+
It is a real, compilable MDX file that demonstrates the components for that shell and their exact props. Search it for a component name to copy the correct prop shape instead of guessing. Delete it when done.
|
|
21
21
|
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.
|
|
22
22
|
4. Write the semantic MDX into the user's `.mdx` file (see Authoring rules).
|
|
23
23
|
5. Validate before rendering:
|
|
@@ -27,7 +27,7 @@ If the user only wants the prompt/methodology and not files, still choose a shel
|
|
|
27
27
|
```
|
|
28
28
|
Every diagnostic is `CODE line:column message`. Fix all `error`s and re-run; warnings are quality signals you should also address when cheap.
|
|
29
29
|
6. Compile: `npx concept-atlas-dense-explain <file>.mdx --mode atlas|scroll [-o out.html] [--skin <id>] [--default-mode dark|light|system] [--style <id>]`. Output is a standalone HTML beside the MDX unless `-o` is given. Validation errors abort the build; use `--no-validate` only to force a knowingly broken build.
|
|
30
|
-
7. **Appearance (optional)**: pages ship with a reader-facing appearance menu — palette (`aurora` cool blue, `ember` warm gold), a dark/light toggle, and a component style pack (`manuscript` editorial marginalia, `classic` boxed cards). The shipped default is aurora × manuscript × light; choices persist in localStorage across both carriers. You can bake different compile-time defaults: `--skin ember --default-mode dark --style classic` (or env `CONCEPT_ATLAS_SKIN` / `CONCEPT_ATLAS_DEFAULT_MODE` / `CONCEPT_ATLAS_STYLE` on the repo build). Bake a default only when the user asks for one. Content MDX never sets appearance — it is carrier/tooling territory, not content.
|
|
30
|
+
7. **Appearance (optional)**: pages ship with a reader-facing appearance menu — palette (`aurora` cool blue, `ember` warm gold), a dark/light toggle, and a component style pack (`manuscript` editorial marginalia, `classic` boxed cards). The shipped default is aurora × manuscript × light; choices persist in localStorage across both carriers. You can bake different compile-time defaults: `--skin ember --default-mode dark --style classic` (or env `CONCEPT_ATLAS_SKIN` / `CONCEPT_ATLAS_DEFAULT_MODE` / `CONCEPT_ATLAS_STYLE` on the repo build). `--default-mode` only honors `dark`/`light`; `system` is accepted by the CLI but resolves to the carrier default (`light`). Bake a default only when the user asks for one. Content MDX never sets appearance — it is carrier/tooling territory, not content.
|
|
31
31
|
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. The batch validates everything first and builds in parallel. Builds only bundle the heavy renderers the content uses: a page with no `<Math>`/`<Mermaid>` skips KaTeX (its ~1.4MB inlined fonts) and Mermaid, shrinking a typical scroll article from ~5MB to ~250KB. Do not add dummy `<Math>`/`<Mermaid>` nodes to "enable" them — write the components only when the content needs them. Add `--link-assets` when the page carries many screenshots and size matters.
|
|
32
32
|
9. Report the shell, output path, validation result (errors/warnings), and limitations. Do not claim interactions you did not verify.
|
|
33
33
|
|
|
@@ -35,6 +35,7 @@ If the user only wants the prompt/methodology and not files, still choose a shel
|
|
|
35
35
|
|
|
36
36
|
- `atlas`: `ExplainPage` → `ConceptGraph` → `ConceptNode`, plus `Children`/`ConceptRef` and cross-branch `Relation`s. Exactly one `L0` root, several `L1` branches, depth to `L3`/`L4`. Shared components live inside nodes.
|
|
37
37
|
- `scroll`: `ScrollDocument` → `ScrollHeader` + `ScrollSection` (+ `ScrollProse`, `ScrollGrid`). Shared components live inside sections. The shell auto-builds a table of contents and reading progress from section titles — do not hand-build navigation.
|
|
38
|
+
- The browser tab comes from the shell, not the build flags: `ExplainPage title="..."` (atlas) or `ScrollHeader title="..."` (scroll) becomes the `<title>`, so give it a real, specific document name — never leave a placeholder like "主题名称". The favicon is a fixed 📃 document emoji on every generated page.
|
|
38
39
|
- Never make one MDX file both shells. When switching shells, convert only the outer structure.
|
|
39
40
|
|
|
40
41
|
## Component families
|
|
@@ -42,8 +43,8 @@ If the user only wants the prompt/methodology and not files, still choose a shel
|
|
|
42
43
|
- Node semantics: `Overview`, `Definition`, `Mechanism`, `Implementation`, `Boundary`, `Example`, `Counterexample`, `Prerequisite`, `Input`, `Output`, `Glossary`
|
|
43
44
|
- Argument and evidence: `Evidence`, `Invariant`, `FailureMode`, `Tradeoff`, `LearningObjectives`, `KeyQuestion`
|
|
44
45
|
- Information models: `Flow`, `Timeline`, `Compare`, `DecisionMatrix`, `FrameworkModel`, `MatrixModel`, `FormulaModel`, `PyramidModel`, `FunnelModel`
|
|
45
|
-
- Reading and layout: `Insight`, `Callout`, `Details`, `NoteGrid`, `Tabs`, `Columns`, `Stack`, `Grid`, `Split`, `ScrollGrid`
|
|
46
|
-
- Graphics and extensions: `Mermaid`, `RelationMap`, `RelationPath`, `Math`, `MathBlock`, `Chart`, `Figure
|
|
46
|
+
- Reading and layout: `Insight`, `Callout`, `Details`, `NoteGrid`, `Tabs`, `Columns`, `Stack`, `Grid`, `Split`, `ScrollGrid`, `ScrollPair`, `ScrollToc`
|
|
47
|
+
- Graphics and extensions: `Mermaid`, `RelationMap`, `RelationPath`, `Math`, `MathBlock`, `Chart`, `Figure` (alias `Image`), `Cite`, `References`
|
|
47
48
|
|
|
48
49
|
## Authoring rules
|
|
49
50
|
|
|
@@ -61,7 +62,7 @@ If the user only wants the prompt/methodology and not files, still choose a shel
|
|
|
61
62
|
|
|
62
63
|
## Validation diagnostics
|
|
63
64
|
|
|
64
|
-
`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_UNRESOLVED`, `RELATION_FROM_UNRESOLVED`, `RELATION_TO_UNRESOLVED`. Warnings worth fixing: `NODE_MISSING_SUMMARY`, `NODE_NO_CORE_CONTENT`, `UNKNOWN_LEVEL`, `UNKNOWN_RELATION_TYPE`, `RELATION_MISSING_LABEL`, `PROP_EXPECTS_ARRAY`, `MATH_CHILDREN_BRACES`, `GRAPH_MISSING_ROOT`, `ASSET_MISSING
|
|
65
|
+
`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_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` and `MULTIPLE_ROOT_LEVEL` are warnings that `--strict` promotes to errors.
|
|
65
66
|
|
|
66
67
|
## Before you report
|
|
67
68
|
|
package/template/index.html
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
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" />
|
|
6
7
|
<title>Concept Atlas · 概念缩放式知识图谱</title>
|
|
7
8
|
<script>
|
|
8
9
|
(function () {
|
|
@@ -3,6 +3,7 @@ import path from 'path';
|
|
|
3
3
|
import fs from 'fs';
|
|
4
4
|
import { fileURLToPath } from 'url';
|
|
5
5
|
import { normalizeSkin, normalizeStyle } from '../src/model/skins.js';
|
|
6
|
+
import { extractPageTitle } from '../src/model/validate-content.js';
|
|
6
7
|
|
|
7
8
|
const __filename = fileURLToPath(import.meta.url);
|
|
8
9
|
const __dirname = path.dirname(__filename);
|
|
@@ -24,6 +25,20 @@ function appearanceDefines() {
|
|
|
24
25
|
return define;
|
|
25
26
|
}
|
|
26
27
|
|
|
28
|
+
/**
|
|
29
|
+
* The tab title comes from the mounted demo document, not the carrier shell:
|
|
30
|
+
* the repository mounts content/components-demo.mdx in index.html while the
|
|
31
|
+
* npm template mounts content/compile-runtime.mdx, so both are probed and the
|
|
32
|
+
* first existing file supplies its <ExplainPage>/<ScrollHeader> title.
|
|
33
|
+
*/
|
|
34
|
+
function pageTitleFor(entry) {
|
|
35
|
+
const demos = entry === 'scroll.html'
|
|
36
|
+
? ['content/scroll-reading-demo.mdx']
|
|
37
|
+
: ['content/components-demo.mdx', 'content/compile-runtime.mdx'];
|
|
38
|
+
const demo = demos.map(name => path.resolve(rootDir, name)).find(file => fs.existsSync(file));
|
|
39
|
+
return demo ? extractPageTitle(fs.readFileSync(demo, 'utf8')) : null;
|
|
40
|
+
}
|
|
41
|
+
|
|
27
42
|
async function runBuild() {
|
|
28
43
|
console.log('🚀 开始构建 Concept Atlas 知识讲解页面...');
|
|
29
44
|
|
|
@@ -44,9 +59,12 @@ async function runBuild() {
|
|
|
44
59
|
// vite-plugin-singlefile supports one HTML input per build. Build each
|
|
45
60
|
// requested carrier separately so every output remains a standalone file.
|
|
46
61
|
for (const [index, entry] of carriers.entries()) {
|
|
62
|
+
const title = pageTitleFor(entry);
|
|
63
|
+
const entryDefine = title ? { ...define, __ATLAS_PAGE_TITLE__: JSON.stringify(title) } : define;
|
|
64
|
+
if (title) console.log(`🔖 ${entry} 标签页标题:${title}`);
|
|
47
65
|
await build({
|
|
48
66
|
root: rootDir,
|
|
49
|
-
define,
|
|
67
|
+
define: entryDefine,
|
|
50
68
|
build: {
|
|
51
69
|
outDir: 'dist',
|
|
52
70
|
emptyOutDir: index === 0,
|
package/template/scroll.html
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
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" />
|
|
6
7
|
<title>Concept Atlas · 连续阅读示例</title>
|
|
7
8
|
<script>
|
|
8
9
|
(function () {
|
|
@@ -598,3 +598,28 @@ export function detectFeatures(source) {
|
|
|
598
598
|
mermaid: used.has('Mermaid'),
|
|
599
599
|
};
|
|
600
600
|
}
|
|
601
|
+
|
|
602
|
+
/** JSX string literals decode these five entities; a single pass avoids
|
|
603
|
+
* double-decoding sequences like `&lt;`. */
|
|
604
|
+
const ENTITY_MAP = { amp: '&', lt: '<', gt: '>', quot: '"', '#39': "'" };
|
|
605
|
+
const decodeEntities = text => text.replace(/&(amp|lt|gt|quot|#39);/g, (_, entity) => ENTITY_MAP[entity]);
|
|
606
|
+
|
|
607
|
+
/**
|
|
608
|
+
* Reads the browser-tab title from the MDX source without rendering: the atlas
|
|
609
|
+
* shell declares it on <ExplainPage title="...">, the scroll shell on
|
|
610
|
+
* <ScrollHeader title="...">. Only quoted string props count — a `{...}`
|
|
611
|
+
* expression title cannot be known at build time. Returns null when no static
|
|
612
|
+
* title exists, so the build keeps the carrier's default <title>.
|
|
613
|
+
*/
|
|
614
|
+
export function extractPageTitle(source) {
|
|
615
|
+
const tags = tokenize(maskIgnored(source));
|
|
616
|
+
for (const shell of ['ExplainPage', 'ScrollHeader']) {
|
|
617
|
+
const tag = tags.find(item => item.name === shell && item.kind !== 'close');
|
|
618
|
+
if (!tag) continue;
|
|
619
|
+
const attr = attrsToMap(tag.attrs).title;
|
|
620
|
+
if (attr && attr.hasValue && attr.quoted && attr.value.trim()) {
|
|
621
|
+
return decodeEntities(attr.value).trim();
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
return null;
|
|
625
|
+
}
|
package/template/vite.config.js
CHANGED
|
@@ -161,6 +161,31 @@ function resolveToken(raw, validate) {
|
|
|
161
161
|
return validate(value);
|
|
162
162
|
}
|
|
163
163
|
|
|
164
|
+
/**
|
|
165
|
+
* Bakes the per-document <title> into the carrier HTML.
|
|
166
|
+
*
|
|
167
|
+
* The CLI (and the repository's demo build) extract the title from the MDX
|
|
168
|
+
* source — <ExplainPage title="..."> for the atlas carrier, <ScrollHeader
|
|
169
|
+
* title="..."> for scroll — and forward it as the `__ATLAS_PAGE_TITLE__`
|
|
170
|
+
* define. When present, the carrier's default <title> is replaced so the
|
|
171
|
+
* browser tab names the actual document instead of the demo placeholder.
|
|
172
|
+
* Without the define the default title survives unchanged.
|
|
173
|
+
*/
|
|
174
|
+
function pageTitle() {
|
|
175
|
+
let title = null;
|
|
176
|
+
return {
|
|
177
|
+
name: 'concept-atlas-page-title',
|
|
178
|
+
configResolved(config) {
|
|
179
|
+
title = resolveToken(config.define && config.define.__ATLAS_PAGE_TITLE__, value => value.trim() || null);
|
|
180
|
+
},
|
|
181
|
+
transformIndexHtml(html) {
|
|
182
|
+
if (!title) return html;
|
|
183
|
+
const escaped = title.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
184
|
+
return html.replace(/<title>[\s\S]*?<\/title>/, `<title>${escaped}</title>`);
|
|
185
|
+
},
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
|
|
164
189
|
export default defineConfig({
|
|
165
190
|
plugins: [
|
|
166
191
|
inlineMdxAssets(),
|
|
@@ -173,6 +198,7 @@ export default defineConfig({
|
|
|
173
198
|
react(),
|
|
174
199
|
optionalFeatures(),
|
|
175
200
|
appearanceDefaults(),
|
|
201
|
+
pageTitle(),
|
|
176
202
|
viteSingleFile(),
|
|
177
203
|
],
|
|
178
204
|
build: {
|