Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 73 additions & 0 deletions next/src/lib/templates/__tests__/prompt-guardrails.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { describe, expect, it } from "vitest";
import { SHARED_DESIGN_DIRECTIVES, assemblePrompt } from "../shared";
import { loadSkill } from "../loader";

/**
* Regression guards for nexu-io/html-anything#88.
*
* Two output bugs were observed when running `docs-page` with `haiku`:
*
* 1. Mermaid blocks emitted as `<div class="mermaid"><pre>…<br/>…</pre></div>`.
* The browser parses the inner `<br/>` as a real <br> element which gets
* collapsed to a newline in textContent, then mermaid sees an invalid
* multi-line message and renders "Syntax error in text".
*
* 2. Custom sidebar link classes (e.g. `.nav-link`) got padding / radius
* but no `display: block`. Anchors default to inline, so `space-y-*`
* is a no-op and the entire nav collapses onto one line.
*
* We can't catch the bugs at runtime without a live agent call (model output
* is non-deterministic and live invocations are flaky in CI). Instead we
* assert that the prompt assembled for every request contains the guardrail
* text that steers the model away from these patterns — a pure-function
* substring check.
*/

const MERMAID_MARKERS = [
// Steers toward the robust textContent-injection pattern.
"pre.textContent = source",
// Forbids the broken pattern observed in the issue repro.
"不要嵌套",
// Tells the model how to escape <br/> if it does use the direct-div pattern.
"&lt;br/&gt;",
];

const SIDEBAR_MARKERS = [
"display: block",
// The "why" — references space-y on inline anchors so future model retraining
// has the failure mode spelled out, not just the rule.
"space-y-",
];

describe("SHARED_DESIGN_DIRECTIVES (issue #88 guardrails)", () => {
it("contains the Mermaid textContent-injection guidance", () => {
for (const marker of MERMAID_MARKERS) {
expect(SHARED_DESIGN_DIRECTIVES).toContain(marker);
}
});
});

describe("docs-page SKILL.md (issue #88 guardrails)", () => {
it("contains the sidebar `display: block` rule", () => {
const skill = loadSkill("docs-page");
expect(skill).not.toBeNull();
for (const marker of SIDEBAR_MARKERS) {
expect(skill!.body).toContain(marker);
}
});
});

describe("assemblePrompt (issue #88 guardrails reach the model)", () => {
it("includes both Mermaid and sidebar guardrails in the assembled prompt", () => {
const skill = loadSkill("docs-page");
expect(skill).not.toBeNull();
const prompt = assemblePrompt({
body: skill!.body,
content: "minimal user input",
format: "markdown",
});
for (const marker of [...MERMAID_MARKERS, ...SIDEBAR_MARKERS]) {
expect(prompt).toContain(marker);
}
});
});
7 changes: 7 additions & 0 deletions next/src/lib/templates/shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,13 @@ export const SHARED_DESIGN_DIRECTIVES = `
- 如果用户数据是结构化数据 (CSV/JSON), 请提取关键洞察并以图表/表格呈现。
- 中文与英文混排时, 中英文之间留半角空格 (盘古之白)。

【Mermaid 图表 — 渲染兼容性】
- **推荐**: 将每张 mermaid 图源放进 JS 字符串常量, 渲染时用 \`pre.textContent = source\` 写入 \`<pre class="mermaid">\` (或 \`<div class="mermaid">\`) 占位元素, 然后调用 \`mermaid.run({ nodes: [...] })\` 或 \`mermaid.contentLoaded()\`。这是**最稳健**的方式 — \`textContent\` 跳过浏览器对内嵌 HTML 标签的解析, 保证 \`<br/>\` 等字面字符串原样到达 mermaid。
- **如果直接把图源写进 \`<div class="mermaid">…</div>\`**, 则:
- **不要嵌套** \`<pre>\` / \`<code>\`。mermaid 直接读 div 的 \`textContent\`, 嵌套包装层只会破坏空格/缩进。
- 图源里出现的 \`<br/>\` 标签必须 **HTML-entity 编码** 为 \`&lt;br/&gt;\`, 否则浏览器解析时会把它当真 \`<br>\` 元素吃掉, mermaid 拿到换行符 → 报 \`Syntax error in text\`。
- mermaid 配置: \`startOnLoad: true\` 即可自动渲染; 对超宽时序图/类图考虑 \`sequence: { useMaxWidth: true }\` + 在容器上加 \`overflow-x: auto\`。

`;

/**
Expand Down
8 changes: 8 additions & 0 deletions next/src/lib/templates/skills/docs-page/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,11 @@ tags: ["docs", "api", "tutorial", "guide"]
【设计细节】
- 代码块: 圆角 + dark + 语言标签 + 复制按钮
- callout: info / warn / danger 三色

【sidebar / TOC 链接 — 硬性 CSS 要求】
- 任何用于侧边栏 / 目录的链接类 (如 \`.nav-link\` / \`.sidebar-link\` / \`.toc-link\`, 自命名亦同)
必须显式带 \`display: block\` (或 Tailwind \`block\` 工具类)。
- \`<a>\` 默认是 inline, \`space-y-*\` 等纵向间距工具对 inline 元素**无效** — 不加 \`display: block\`
会让导航项塌成一行连续文字, 而不是纵向列表。
- 优先做法: 给链接直接挂 Tailwind \`block\` (或 \`flex items-center\`), 不要只在自定义 class 里
写 padding/border-radius 却忘了 display。