Inline spark tables (role=spark-table) live in the content registry, but the rsbuild dev proxy forwarded only /__COMPLETIONS.json and /__CONTENT_INDEX.json. The registry fetch fell back to the SPA index.html, so /roll resolved the inline CSV as HTML and csv-parse failed with an invalid opening quote. Load the registry before publishing the loaded completions state so a roll cannot fire while inline content is still unresolvable.
7.8 KiB
7.8 KiB
开发和项目说明
快速开始
# 安装依赖
npm install
# 开发模式(同时启动 CLI 内容服务器 :3000 与 Web 前端 :5173)
npm run dev
# 也可分别启动
npm run dev:cli
npm run dev:web
# 构建(Web)
npm run build
# 预览构建结果
npm run preview
# CLI 开发模式
npm run cli:dev
# CLI 构建
npm run cli:build
# 运行测试
npm test
项目结构
tttk/
├── src/
│ ├── cli/ # CLI 工具源码
│ │ ├── commands/ # 命令实现 (serve, check, compile)
│ │ ├── validate/ # tttk check 校验器(扫描、规则、诊断)
│ │ ├── content-registry.ts # 内容注册表(CLI / 前端共享)
│ │ ├── content-index.ts # 目录文件递归读取
│ │ ├── index.ts # CLI 入口
│ │ └── types.ts # 类型定义
│ ├── components/ # TTRPG 组件
│ │ ├── md-dice.tsx # 骰子组件
│ │ ├── md-table.tsx # 表格组件
│ │ ├── md-deck/ # 卡牌组件
│ │ ├── md-pins.tsx # 标记组件
│ │ ├── md-commander/ # 命令追踪器
│ │ ├── md-yarn-spinner.tsx # 叙事线组件
│ │ ├── Article.tsx # 文章组件
│ │ ├── Sidebar.tsx # 侧边栏
│ │ ├── FileTree.tsx # 文件树
│ │ └── utils/ # 工具函数
│ ├── markdown/ # Markdown 解析器
│ │ ├── index.ts # marked 配置
│ │ └── mermaid.ts # mermaid 支持
│ ├── data-loader/ # 数据加载器
│ ├── plotcutter/ # 剧情切割工具
│ ├── yarn-spinner/ # 叙事线解析
│ ├── App.tsx # 主应用
│ ├── main.tsx # 入口文件
│ ├── styles.css # 样式
│ ├── index.html # HTML 模板
│ └── global.d.ts # 类型声明
├── .agents/
│ └── skills/
│ └── tttk-authoring/ # 随项目维护的 agent skill(见下文)
├── bin/
│ └── cli/ # CLI 二进制文件
├── content/ # 示例内容
├── docs/ # 文档
├── package.json
├── tsconfig.json
├── tsconfig.cli.json
├── rsbuild.config.ts
└── jest.config.js
Agent skills
tttk-authoring skill 由本项目维护,源码位于
.agents/skills/tttk-authoring/。要让 agent 加载它,把全局 skill 目录
链接到仓库内的副本:
# Windows(无需管理员权限的目录联接)
cmd //c mklink /J %USERPROFILE%\.agents\skills\tttk-authoring "%CD%\.agents\skills\tttk-authoring"
# macOS / Linux
ln -s "$PWD/.agents/skills/tttk-authoring" ~/.agents/skills/tttk-authoring
reference.md 由 known.ts 生成,不要手改:
npm run gen:skill-reference
其余文件(SKILL.md、yaml-tag.md、csv.md、layout.md、markdown.md)
为手写内容。
技术栈
| 类别 | 技术 |
|---|---|
| 前端框架 | Solid.js 1.9+ |
| 构建工具 | Rsbuild |
| 样式 | Tailwind CSS 4 + @tailwindcss/typography |
| Markdown | marked + marked-directive + marked-alert + marked-gfm-heading-id |
| 图表 | mermaid |
| 测试 | Jest | | CLI | commander + chokidar |
开发规范
Solid.js 最佳实践
- 优先使用
createEffect和createMemo实现响应式 - 在 JSX 中直接引用
props保持响应式 - 避免滥用
onMount+createSignal
// ✅ 推荐
const doubled = createMemo(() => count() * 2);
createEffect(() => console.log(count()));
// ❌ 避免
onMount(() => {
const [value, setValue] = createSignal(0);
});
组件开发
- 不使用 Shadow DOM:使用
noShadowDOM() - 继承 Tailwind CSS 样式系统
- 使用
customElement注册自定义元素
import { customElement, noShadowDOM } from "solid-element";
customElement("md-dice", { key: "" }, (props, { element }) => {
noShadowDOM();
// 组件逻辑
});
类型检查
开发完成后检查类型错误:
npx tsc --noEmit
代码风格
- 使用 TypeScript 严格模式
- 遵循 ESLint 配置(如有)
- 使用 2 空格缩进
添加新组件
1. 创建组件文件
// src/components/md-new-component.tsx
import { customElement, noShadowDOM } from "solid-element";
import { createSignal } from "solid-js";
export interface NewComponentProps {
prop1?: string;
}
customElement("md-new-component", { prop1: "" }, (props, { element }) => {
noShadowDOM();
const [state, setState] = createSignal("");
return (
<div class="new-component">
{/* 组件内容 */}
</div>
);
});
2. 注册组件
// src/components/index.ts
import './md-new-component';
3. 导出类型(可选)
// src/components/index.ts
export type { NewComponentProps } from './md-new-component';
构建配置
Rsbuild 配置
// rsbuild.config.ts
import { defineConfig } from '@rsbuild/core';
import { pluginBabel } from '@rsbuild/plugin-babel';
import { pluginSolid } from '@rsbuild/plugin-solid';
import tailwindcss from '@tailwindcss/postcss';
export default defineConfig({
plugins: [pluginBabel(), pluginSolid()],
tools: {
postcss: {
postcssOptions: {
plugins: [tailwindcss()],
},
},
},
html: {
template: './src/index.html',
},
});
TypeScript 配置
tsconfig.json- 主项目配置(Web)tsconfig.cli.json- CLI 工具配置(Node.js)
测试
# 运行所有测试
npm test
# 运行特定测试文件
npm test -- src/components/md-dice.test.ts
# 监听模式
npm test -- --watch
调试
开发环境由两个进程组成,必须使用不同端口:
- Web 前端:rsbuild 开发服务器,端口 5173
- CLI 内容服务器:
tttk serve,端口 3000(内容 API + Journal)
rsbuild 会把 /__CONTENT_INDEX.json、/__COMPLETIONS.json、/__CONTENT_REGISTRY.json、/content、/.ttrpg 代理到 CLI 的
3000 端口,因此必须先启动 CLI,否则前端拿不到内容。
Web 开发
npm run dev 会同时启动两个进程;也可以分开运行:
# 同时启动 CLI(3000)与 Web(5173)
npm run dev
# 或分别启动
npm run dev:cli # CLI 内容服务器,占用 3000
npm run dev:web # Web 前端,占用 5173
# 访问 http://localhost:5173
CLI 调试
CLI 通过 ts-node 运行,加上 --inspect 与 --enable-source-maps 后即可在编辑器中附加断点。
如需调试启动/扫描逻辑,用 --inspect-brk 在用户代码执行前暂停:
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/** 后需要重启进程。
# 或构建后运行
npm run cli:build
node dist/cli/index.js serve ./content
发布流程
- 更新版本号
- 运行测试和类型检查
- 构建 Web 和 CLI
- 发布到 npm
npm run build
npm run cli:build
npm test
npx tsc --noEmit
npm publish
常见问题
Windows 换行符
开发环境主要在 Windows 上,注意使用 CRLF 换行符。
依赖安装失败
# 清理缓存
npm cache clean --force
rm -rf node_modules package-lock.json
npm install
构建错误
# 检查类型
npx tsc --noEmit
# 重新构建
npm run build