feat: add protocol explorer with catalogs and live playground

This commit is contained in:
2026-10-19 18:21:30 +09:00
parent b82c1d0a70
commit 75a36f2298
38 changed files with 1836 additions and 83 deletions
+28 -2
View File
@@ -3,7 +3,7 @@ import { readdir, readFile } from 'node:fs/promises';
import path from 'node:path';
import test from 'node:test';
import { contentsRoot } from '../plugins/document-links.mjs';
import { documentTree, chapterTree, bookTree, childrenOf, visibleNodes, treePrefix, serializeTree } from '../src/lib/landing-tree.ts';
import { documentTree, chapterTree, bookTree, publicTree, childrenOf, visibleNodes, treePrefix, serializeTree } from '../src/lib/landing-tree.ts';
async function sourceFiles(directory, prefix = '') {
const entries = await readdir(directory, { withFileTypes: true });
@@ -83,7 +83,7 @@ test('public book container wraps the numbered chapters without changing documen
const published = documents.filter((document) => !document.id.startsWith('draft/'));
const display = bookTree(published);
assert.deepEqual(childrenOf(display, '/').map((node) => node.path), ['/book']);
assert.equal(display[1].name, 'book/');
assert.equal(display[1].name, 'book');
assert.equal(display[1].depth, 2);
assert.equal(display.length, published.length + 2);
assert.ok(!display.some((node) => node.path.startsWith('/draft')));
@@ -98,6 +98,32 @@ test('public book container wraps the numbered chapters without changing documen
}
});
test('public root catalogs and playground are leaves alongside the unchanged book subtree', () => {
const published = documents.filter((document) => !document.id.startsWith('draft/'));
for (const base of ['/', '/reference/']) {
const display = publicTree(published, base);
assert.deepEqual(display.slice(0, -3), bookTree(published));
assert.deepEqual(childrenOf(display, '/').map((node) => node.path), ['/book', '/sources', '/packages', '/playground']);
assert.deepEqual(visibleNodes(display, new Set(['/book'])).map((node) => node.path), ['/', '/book', '/sources', '/packages', '/playground']);
assert.deepEqual(visibleNodes(display, new Set(['/'])), [display[0]]);
for (const name of ['sources', 'packages', 'playground']) {
const leaf = display.find((node) => node.path === `/${name}`);
assert.equal(leaf.name, name);
assert.equal(leaf.depth, 2);
assert.equal(leaf.href, `${base}${name}/`);
assert.ok(leaf.title);
assert.deepEqual(childrenOf(display, leaf.path), []);
assert.deepEqual(visibleNodes(display, new Set([leaf.path])), display);
}
assert.equal(treePrefix(display.find((node) => node.path === '/book'), display), '├─ ');
assert.equal(treePrefix(display.find((node) => node.path === '/1.1-interaction-abstraction.md'), display), '│ │ ├─ ');
assert.equal(treePrefix(display.find((node) => node.path === '/packages'), display), '├─ ');
assert.equal(display.at(-1).path, '/playground');
assert.equal(treePrefix(display.at(-1), display), '└─ ');
assert.ok(!display.some((node) => node.path.includes('/draft')));
}
});
test('existing titles and hrefs pass through without embedding document bodies', () => {
for (const document of documents) {
const node = nodes.find((node) => node.path === '/' + document.id + '.md');