Compare commits

..
3 Commits
Author SHA1 Message Date
hyper b5591f2a78 chore(cli): ignore .ttrpg session logs 2026-09-12 20:13:53 +08:00
hyper 2ed69d744a chore(dev): Add dev runner and split dev ports
Add dev:web (rsbuild) and dev:cli (tttk serve ./content), with dev
running both via scripts/dev.mjs, which shuts both down when either
exits. Also add tttk:inspect for --inspect debugging.

Both servers previously defaulted to port 3000, so the CLI could not
bind while the web dev server was running and the proxy targeted
itself. Pin the web dev server to 5173; the CLI keeps 3000 to match
the proxy. Document the two-process setup and inspector debugging in
docs/development.md.
2026-09-12 20:11:34 +08:00
hyper 53620e0a92 docs(doc-entries): Show examples as code blocks
Directive examples sat bare in the doc bodies, so rendering them as
markdown executed the directives: live components, missing-file
fetches, and style side effects on the dialog. Wrap each example in
a markdown fence, matching the convention already used by md-table.
2026-09-12 19:58:37 +08:00
14 changed files with 152 additions and 13 deletions
+1
View File
@@ -6,3 +6,4 @@ dist/
.env.local .env.local
*.csv.d.ts *.csv.d.ts
.ttrpg/
+36 -5
View File
@@ -6,9 +6,13 @@
# 安装依赖 # 安装依赖
npm install npm install
# 开发模式(Web) # 开发模式(同时启动 CLI 内容服务器 :3000 与 Web 前端 :5173)
npm run dev npm run dev
# 也可分别启动
npm run dev:cli
npm run dev:web
# 构建(Web) # 构建(Web)
npm run build npm run build
@@ -244,19 +248,46 @@ npm test -- --watch
## 调试 ## 调试
开发环境由两个进程组成,必须使用不同端口:
- Web 前端:rsbuild 开发服务器,端口 **5173**
- CLI 内容服务器:`tttk serve`,端口 **3000**(内容 API + Journal)
rsbuild 会把 `/__CONTENT_INDEX.json`、`/__COMPLETIONS.json`、`/content`、`/.ttrpg` 代理到 CLI 的
3000 端口,因此必须先启动 CLI,否则前端拿不到内容。
### Web 开发 ### Web 开发
`npm run dev` 会同时启动两个进程;也可以分开运行:
```bash ```bash
# 同时启动 CLI(3000)与 Web(5173)
npm run dev npm run dev
# 访问 http://localhost:3000
# 或分别启动
npm run dev:cli # CLI 内容服务器,占用 3000
npm run dev:web # Web 前端,占用 5173
# 访问 http://localhost:5173
``` ```
### CLI 调试 ### CLI 调试
```bash CLI 通过 ts-node 运行,加上 `--inspect` 与 `--enable-source-maps` 后即可在编辑器中附加断点。
# 使用 ts-node 直接运行 如需调试启动/扫描逻辑,用 `--inspect-brk` 在用户代码执行前暂停:
npm run ttrpg serve ./content
```bash
node --inspect-brk --enable-source-maps --loader ts-node/esm ./src/cli/index.ts serve ./content
# 或使用脚本(不带 --inspect-brk)
npm run tttk:inspect -- serve ./content
```
然后在 Zed / VS Code 中附加到 `localhost:9229`,或打开 `chrome://inspect`。
CLI 只监听内容目录(`.md`/`.csv`/`.yarn`/`.svg`)并重建内存中的注册表,**不会**热重载自身源码;
修改 `src/cli/**` 后需要重启进程。
```bash
# 或构建后运行 # 或构建后运行
npm run cli:build npm run cli:build
node dist/cli/index.js serve ./content node dist/cli/index.js serve ./content
+4 -1
View File
@@ -19,7 +19,9 @@
"node": ">=18" "node": ">=18"
}, },
"scripts": { "scripts": {
"dev": "rsbuild dev", "dev": "node scripts/dev.mjs",
"dev:web": "rsbuild dev",
"dev:cli": "node --enable-source-maps --loader ts-node/esm ./src/cli/index.ts serve ./content",
"build": "npm run build:web && npm run build:cli", "build": "npm run build:web && npm run build:cli",
"build:web": "rsbuild build", "build:web": "rsbuild build",
"build:cli": "tsc -p tsconfig.cli.json", "build:cli": "tsc -p tsconfig.cli.json",
@@ -29,6 +31,7 @@
"cli:build": "npm run build:cli", "cli:build": "npm run build:cli",
"ttrpg": "node --loader ts-node/esm ./src/cli/index.ts", "ttrpg": "node --loader ts-node/esm ./src/cli/index.ts",
"tttk": "node --loader ts-node/esm ./src/cli/index.ts", "tttk": "node --loader ts-node/esm ./src/cli/index.ts",
"tttk:inspect": "node --inspect --enable-source-maps --loader ts-node/esm ./src/cli/index.ts",
"gen:reference": "node --loader ts-node/esm ./scripts/generate-reference.ts", "gen:reference": "node --loader ts-node/esm ./scripts/generate-reference.ts",
"gen:skill-reference": "node --loader ts-node/esm ./scripts/generate-reference.ts --out .agents/skills/tttk-authoring/reference.md", "gen:skill-reference": "node --loader ts-node/esm ./scripts/generate-reference.ts --out .agents/skills/tttk-authoring/reference.md",
"test": "jest", "test": "jest",
+3
View File
@@ -48,6 +48,9 @@ export default defineConfig({
}, },
}, },
server: { server: {
// Dev web server runs on its own port; the CLI content server keeps
// port 3000 (see the proxy targets below).
port: 5173,
proxy: { proxy: {
// Proxy content API requests to the CLI dev server (npm run tttk) // Proxy content API requests to the CLI dev server (npm run tttk)
"/__CONTENT_INDEX.json": "http://localhost:3000", "/__CONTENT_INDEX.json": "http://localhost:3000",
+55
View File
@@ -0,0 +1,55 @@
#!/usr/bin/env node
/**
* Dev runner: starts the CLI content server (port 3000) and the rsbuild web
* dev server (port 5173) together, then tears both down when either exits.
*
* npm run dev # both (this script)
* npm run dev:cli # CLI content server only
* npm run dev:web # web dev server only
*/
import { spawn } from "node:child_process";
const isWindows = process.platform === "win32";
const children = new Set();
let shuttingDown = false;
/** Terminate a child and everything it spawned. */
function killTree(child) {
if (child.exitCode !== null || child.signalCode !== null) return;
if (isWindows) {
spawn("taskkill", ["/pid", String(child.pid), "/T", "/F"], {
stdio: "ignore",
});
} else {
try {
// Negative pid targets the process group created via `detached`.
process.kill(-child.pid, "SIGTERM");
} catch {
child.kill("SIGTERM");
}
}
}
function shutdown(code) {
if (shuttingDown) return;
shuttingDown = true;
for (const child of children) killTree(child);
process.exitCode = code ?? 0;
}
for (const script of ["dev:cli", "dev:web"]) {
const child = spawn("npm", ["run", script], {
stdio: "inherit",
shell: true,
detached: !isWindows,
});
children.add(child);
child.on("exit", (code) => shutdown(code ?? 0));
child.on("error", (error) => {
console.error(`[dev] Failed to start ${script}: ${error.message}`);
shutdown(1);
});
}
process.on("SIGINT", () => shutdown(0));
process.on("SIGTERM", () => shutdown(0));
+4 -2
View File
@@ -9,14 +9,16 @@ title: 背景组件
**语法:** `:md-bg[颜色或图片路径]{选项}` **语法:** `:md-bg[颜色或图片路径]{选项}`
**设置背景图:** **设置背景图:**
```markdown
:md-bg[./images/dungeon-bg.jpg]{fit="cover"} :md-bg[./images/dungeon-bg.jpg]{fit="cover"}
```
**设置纯色背景:** **设置纯色背景:**
```markdown
:md-bg[#2a1a3a] :md-bg[#2a1a3a]
:md-bg[rgb(200,50,50)] :md-bg[rgb(200,50,50)]
:md-bg[#ff00dd] :md-bg[#ff00dd]
```
支持图片路径或任意 CSS 颜色值(hex、rgb、颜色名等)。 支持图片路径或任意 CSS 颜色值(hex、rgb、颜色名等)。
+8
View File
@@ -9,16 +9,24 @@ title: 边框组件
**语法:** `:md-border[颜色或图片路径]{方位类 样式类 选项}` **语法:** `:md-border[颜色或图片路径]{方位类 样式类 选项}`
**纯色左边框:** **纯色左边框:**
```markdown
:md-border[#3b82f6]{.l} :md-border[#3b82f6]{.l}
```
**虚线上下边框:** **虚线上下边框:**
```markdown
:md-border[red]{.t .b .dashed width=".2mm"} :md-border[red]{.t .b .dashed width=".2mm"}
```
**图片边框:** **图片边框:**
```markdown
:md-border[./images/frame.png]{.all .round} :md-border[./images/frame.png]{.all .round}
```
**左右图片竖条:** **左右图片竖条:**
```markdown
:md-border[./images/ornament.png]{.l .r .repeat} :md-border[./images/ornament.png]{.l .r .repeat}
```
## 方位类 ## 方位类
+4
View File
@@ -9,10 +9,14 @@ title: 卡牌组件
**语法:** `:md-deck[./cards.csv]{选项}` 或 yaml 代码块 **语法:** `:md-deck[./cards.csv]{选项}` 或 yaml 代码块
**基础卡牌:** **基础卡牌:**
```markdown
:md-deck[./spells.csv]{grid="3x3"} :md-deck[./spells.csv]{grid="3x3"}
```
**多层卡牌:** **多层卡牌:**
```markdown
:md-deck[./cards.csv]{grid="5x8" layers="title:1,1-5,1f8 body:1,5-8,8f3"} :md-deck[./cards.csv]{grid="5x8" layers="title:1,1-5,1f8 body:1,5-8,8f3"}
```
CSV 包含数据字段列,通过图层定义控制各字段的位置、大小和对齐。 CSV 包含数据字段列,通过图层定义控制各字段的位置、大小和对齐。
+12 -3
View File
@@ -8,11 +8,20 @@ title: 骰子组件
**语法:** `:md-dice[公式]{key="标识"}` **语法:** `:md-dice[公式]{key="标识"}`
**攻击检定:** :md-dice[1d20+5]{key="attack"} **攻击检定:**
```markdown
:md-dice[1d20+5]{key="attack"}
```
**伤害掷骰:** :md-dice[2d6+3]{key="damage"} **伤害掷骰:**
```markdown
:md-dice[2d6+3]{key="damage"}
```
**优势检定:** :md-dice[2d20k1+5]{key="advantage"} **优势检定:**
```markdown
:md-dice[2d20k1+5]{key="advantage"}
```
点击 🎲 图标执行投掷,显示结果;点击文字重置为原始公式。 点击 🎲 图标执行投掷,显示结果;点击文字重置为原始公式。
+4
View File
@@ -9,10 +9,14 @@ title: 嵌入组件
**语法:** `:md-embed[路径#章节]{选项}` **语法:** `:md-embed[路径#章节]{选项}`
**嵌入完整文档:** **嵌入完整文档:**
```markdown
:md-embed[./rules.md] :md-embed[./rules.md]
```
**嵌入指定章节:** **嵌入指定章节:**
```markdown
:md-embed[./rules.md#combat] :md-embed[./rules.md#combat]
```
嵌入后内容直接在当前位置显示,包括其中的表格、骰子等组件也会正常渲染。 嵌入后内容直接在当前位置显示,包括其中的表格、骰子等组件也会正常渲染。
+8 -1
View File
@@ -9,18 +9,25 @@ title: 字体组件
**语法:** `:md-font[字体名]{选项}` **语法:** `:md-font[字体名]{选项}`
**Google 字体:** **Google 字体:**
```markdown
:md-font[Noto Sans SC]{source="google"} :md-font[Noto Sans SC]{source="google"}
```
**Emfont 字体:** **Emfont 字体:**
```markdown
:md-font[Source Han Serif]{source="emfont" weight="700"} :md-font[Source Han Serif]{source="emfont" weight="700"}
```
**本地字体(默认):** **本地字体(默认):**
```markdown
:md-font[STSong] :md-font[STSong]
```
**设置文字颜色:** **设置文字颜色:**
```markdown
:md-font[Noto Sans SC]{source="google" color="#1a1a2e"} :md-font[Noto Sans SC]{source="google" color="#1a1a2e"}
:md-font[Noto Sans SC]{source="google" weight="700" color="crimson"} :md-font[Noto Sans SC]{source="google" weight="700" color="crimson"}
```
字体和颜色将应用到整个文档卡片。 字体和颜色将应用到整个文档卡片。
+4
View File
@@ -9,10 +9,14 @@ title: 链接组件
**语法:** `:md-link[路径#章节]` **语法:** `:md-link[路径#章节]`
**展开完整文档:** **展开完整文档:**
```markdown
:md-link[./rules.md] :md-link[./rules.md]
```
**展开特定章节:** **展开特定章节:**
```markdown
:md-link[./rules.md#combat] :md-link[./rules.md#combat]
```
点击链接即可内联展开目标内容,再次点击折叠。 点击链接即可内联展开目标内容,再次点击折叠。
+6
View File
@@ -9,13 +9,19 @@ title: 标记组件
**语法:** `:md-pins[图片路径]{选项}` **语法:** `:md-pins[图片路径]{选项}`
**固定标记(只读):** **固定标记(只读):**
```markdown
:md-pins[./images/battle-map.png]{pins="A:25,50 B:75,30" fixed} :md-pins[./images/battle-map.png]{pins="A:25,50 B:75,30" fixed}
```
**可编辑标记:** **可编辑标记:**
```markdown
:md-pins[./images/blank-map.png] :md-pins[./images/blank-map.png]
```
**数字标签:** **数字标签:**
```markdown
:md-pins[./images/dungeon.png]{labelStart="1"} :md-pins[./images/dungeon.png]{labelStart="1"}
```
非 `fixed` 模式下点击图片添加标记,点击标记删除。 非 `fixed` 模式下点击图片添加标记,点击标记删除。
+2
View File
@@ -9,7 +9,9 @@ title: 叙事线组件
**语法:** `:md-yarn-spinner[./story.yarn]{选项}` **语法:** `:md-yarn-spinner[./story.yarn]{选项}`
**加载叙事文件:** **加载叙事文件:**
```markdown
:md-yarn-spinner[./story.yarn] :md-yarn-spinner[./story.yarn]
```
Yarn Spinner 是用于游戏对话系统的格式,支持选项、条件分支和变量。 Yarn Spinner 是用于游戏对话系统的格式,支持选项、条件分支和变量。