| name | doc-html-preview |
| description | Sync `docs/*.md` to TWO standalone HTML previews in `docs/_html/` for user (David) confirmation โ (1) engineering version for code review, (2) boss (decision) version with AI-generated summary cards, decisions, and risks in plain language. MD stays as source of truth. Trigger after any docs/*.md write or update in a project. |
| version | 2.3.0 |
| platforms | ["macos","linux"] |
| metadata | {"hermes":{"tags":["docs","markdown","html","pandoc","preview","developer-profile","boss-summary","decision"]}} |
Doc HTML Preview
Two HTMLs per MD: engineering version (1:1) + boss version (decision-focused).
Why this exists
SOUL.md ็ด
็ท 10 says: "ๆฒๆๆไปถ็ไปฃ็ขผไธ่ฝ merge" โ every project must have PROJECT-OVERVIEW.md / PRD.md / DESIGN.md / ADR / API.md / TEST-COVERAGE.md / TECH-DEBT.md committed to git.
David needs to read and confirm these documents during Think and Plan phases. Two audiences:
- Developer / code review โ wants the full MD rendered faithfully
- David (the user / boss) โ wants a decision-oriented summary with:
- One sentence "what is this?"
- 3 emoji cards (ๅนพ่ / ๅนพ้ข / ้ฟ้ช)
- Yellow box of "ๆๆฟไบ้
" (3 yes/no questions with pros/cons)
- Red box of risks in plain language
- Links to engineering version for full details
This skill produces both HTMLs side-by-side, generated from the same MD. The boss version's structured content (cards/decisions/risks) comes from docs/_meta/<doc>.json โ generated by the developer profile (or kanban CEO worker) reading the MD and producing a structured summary. Override possible via MD section.
Conventions (frozen โ do not change without David's approval)
| Aspect | Rule |
|---|
| Source of truth | ~/www/<project>/docs/*.md |
| Engineering HTML | ~/www/<project>/docs/_html/<doc-name>.html |
| Boss HTML | ~/www/<project>/docs/_html/<doc-name>-boss.html |
| Boss summary JSON (input) | ~/www/<project>/docs/_meta/<doc-name>.json |
| Git status | HTML and meta JSON are NOT committed. docs/_html/ AND docs/_meta/ MUST be in .gitignore |
| Git status โ KNOWN EXCEPTION | crm-system commits docs/index.html.bundled.html (David's request โ for stable URL sharing via Discord). Naming must use .bundled.html suffix to avoid colliding with Vite/React's index.html. See bundled-html-recipe.md ยง8. When applying this exception, also gitignore docs/_html/ and docs/_meta/ (those are the per-doc outputs; the bundled exception is independent). |
| MD override mechanism | If MD has ## ๐ ่้็ๆ่ฆ section, that whole section is used as the boss body content (overriding JSON) |
| CSS | GitHub-like light/dark theme (engineering) + custom decision UI (boss). Both inline, no external assets |
| Tools | pandoc (already installed) + python3 (already installed) |
| Server | None. HTML opens via file:// |
| LLM calls | Never inside build.sh. Boss summary JSON is generated by the agent loop / kanban CEO worker separately. |
When to trigger
Every time the developer profile writes or updates a docs/*.md file in any ~/www/<project>/ directory:
- Write the MD file.
- Generate
docs/_meta/<doc>.json FIRST (read the MD โ call LLM โ write JSON to docs/_meta/<doc>.json). See "Generating the boss summary JSON" below.
- Run the build script to produce both engineering + boss HTMLs.
- ๐ Verify the build (mandatory) โ see "๐ Boss HTML verification (mandatory)" below. The build script ALWAYS exits 0 and produces 20-24 KB *-boss.html files, even when they render as a "ๅพ
็ๆ" placeholder. File size, build exit code, and a naive
grep 'boss-placeholder' file.html are ALL unreliable โ see the verification section for the recipe that actually works.
- Do NOT send a Discord notification (David opens HTML manually via
file://).
- Do NOT commit the HTML or meta JSON to git.
๐ Boss HTML verification (mandatory) โ 2026-06-06 lesson
The trap: crm-system Day 9. I ran build.sh --project crm-system and shipped 12 boss HTMLs. Exit code 0. File count correct (12 boss + 12 engineering). File sizes looked healthy (~20-24 KB each). David opened one and immediately said: "boss.html ้ฝๅฅฝๅฅฝ๏ผไฝๆๅ
ฉไธๅ้ฝๆฒๆ่ณๆไบ". 10 of the 12 were rendering the literal placeholder <div class="boss-placeholder">๐ ่้็ๆ่ฆๅพ
็ๆ</div> โ but my naive grep had reported them as fine.
Why naive verification fails:
| Check | Why it lies |
|---|
ls docs/_html/*-boss.html | wc -l | Build ALWAYS produces the file, even without a JSON input |
wc -c file.html | Placeholder HTML is ~20 KB, content HTML is ~24 KB โ 4 KB difference invisible in a casual glance |
grep -l 'boss-placeholder' file.html | The CSS section in <style> legitimately defines .boss-placeholder for the empty-state styling, so this matches EVERY boss file, including the good ones. False positive on good files. |
| Build exit code | 0. The build script is silent about missing JSON. |
The verification that actually works โ scan only the BODY region (between </head> and <script>), not the CSS, not the inlined JS:
awk '/<\/head>/,/<script>/' docs/_html/PRD-boss.html | grep -q 'class="boss-placeholder"' \
&& echo "โ PLACEHOLDER" \
|| echo "โ
HAS CONTENT"
Bulk version โ there is a one-shot script at scripts/verify_boss_html.sh:
bash ~/.hermes/profiles/developer/skills/doc-html-preview/scripts/verify_boss_html.sh crm-system
The deeper lesson โ when to generate the JSON:
The original step ordering (build first, JSON later) was wrong. Always generate the JSON before running the build. The build script's silent-placeholder behavior makes post-hoc detection painful and error-prone. Reverse the steps:
| Old (wrong) | New (correct) |
|---|
1. Write MD 2. Build 3. Generate JSON 4. Re-build | 1. Write MD 2. Generate JSON 3. Build (one shot) 4. Verify with verify_boss_html.sh |
If you have a project where some docs already have a JSON and some don't (the crm-system case), generate the missing ones FIRST, then rebuild, then verify. Do not rely on re-build to fix it.
๐งญ Which variant to use โ per-doc vs bundled (2026-06-06 lesson)
Two build paths, not interchangeable. Pick by intent:
| David's phrasing | Variant | Why |
|---|
| ใๆ send ๅ doc ็ reviewerใใ็ๅไบ็ๅขไธไปฝใ | per-doc (build.sh) | One MD โ one HTML. Reviewer opens one file, sees one doc, no clutter. |
| ใ็่้็ใใfor the bossใใ็ๅฎขๆถใใๆ้่ฆๆ้
็ฎๅ
HTML docใ | bundled (build_bundled_html.cjs) | Single shareable artifact, sidebar nav across all docs, search, theme. Email-able. |
| ใHTML doc ๆฏๆไบๆฐ่ฆๆฑใใไฝ check ไธ check ่จญๅฎใ (vague) | bundled (likely) | Almost always means "boss wants to see the project, give me the one file to share" |
| ใๆๆณ็ไธ PRD ๅ
designใ | per-doc, boss variant | Targeted read of one doc with decision UI |
| ใ็ๅฎขๆถ/ๆฐไบบ็ 1 ๅ user manualใ | per-doc, pandoc single-file fallback | One MD, one self-contained HTML, no sidebar nav needed |
Default for projects โค 20 docs: use the bundled variant โ it's the most shareable, requires the least decision-making, and David's projects typically have ~10โ15 docs.
The two variants are NOT the same artifact โ never:
- Build per-doc HTMLs and rename the index to
bundled.html (different renderer, different nav)
- Build bundled and call it
<doc>-boss.html (different audience, different chrome)
๐ AUDIENCE GATE โ read this BEFORE writing any docs (2026-06-06 lesson)
If David's request contains ANY of these phrases, you are writing for the boss / decision-maker / non-engineer, NOT for the dev team:
- ใๆๆณ่ฆ PRD / ็ตฆ่้็ / ็่้็ / for the boss / for ๅฎขๆถ / ่้็ใ
- ใDesign / ่จญ่จ็จฟ / mockup / ่ฆ่ฆบใ
- ใ็ๅฎขๆถ็ / ๅฎขๆถ่ฒ ่ฒฌไบบ / ๆ่ณ่
/ board memberใ
- ใๅๅฅฝๅคชๅค technical / ๅๅฅฝ schema / ๅๅฅฝ code sample / ่ฌไบบ่ฉฑใ
In that case, the doc set you produce must be:
| Write this | NOT this |
|---|
docs/PRD.md (one sentence summary, persona, pillars, KPIs, mockups, FAQ) | docs/api.md with t.Object({...}) schemas |
docs/DESIGN.md (design tokens, screen mockups, interaction details) | docs/database.md with field-level tables |
| Plain business language, minimal code blocks | 1:1 engineering reference |
| Visual ASCII mockups are OK | Code samples in 3+ languages are NOT |
DO NOT build the bundled HTML on top of the engineering reference set and call it done. If the engineering docs already exist and the user is now asking for boss-facing docs, you are being asked for a second, separate set โ write PRD.md and DESIGN.md as new files alongside (NOT replacing) the engineering ones. The bundled HTML should then list BOTH groups.
Failure mode to avoid (crm-system 2026-06-06): I delivered 11 engineering reference docs (architecture / database / api / etc.) before David said "stop, ๆๆฏๆณ็ตฆ่้็็ PRD ๅDesign". David had to correct the audience mid-task. The fix is to ask "for whom?" up front when the doc request is ambiguous โ skill_view(name="doc-html-preview") will surface this gate, but only if you load it before starting.
Audience-mismatch red flag (learned 2026-06-06)
If David says "stop", "ๆๆณ่ฆ X version", "for the boss", "engineer ๅ่ญๅขๅฒ", or otherwise corrects the tone/audience of docs you've already produced:
- STOP building engineering-version HTMLs. You almost certainly picked the wrong audience. Don't apologize and re-render โ re-derive the doc set.
- Check what David actually wants: usually two separate MDs:
docs/PRD.md and docs/DESIGN.md โ executive / business-language (persona, pillars, KPIs, mockups, FAQ)
docs/architecture.md / database.md / api.md / etc. โ developer reference (schemas, code samples)
- These have different vocabularies: PRD says "polymorphic line items" in one sentence + visual mockup, API doc says
t.Object({ productId: t.Optional(t.String()) }). The PRD/Design must not contain code blocks; the reference docs may.
- Build the bundled HTML AFTER the doc split is right โ re-deriving the docs is the right move, not a workaround. Don't fight the audience.
When David says "check your settings" / vague new-requirement phrases (2026-06-06 lesson)
If David says anything like "ไฝ check ไธ check ไฝ ๅ
่จญๅฎ" / "HTML doc ๆฏๆไบๆฐ่ฆๆฑ" / "for the boss" / "ๅฎขๆถ็ๆฌ" / "็่ตทๅๅ professional" without specifying the requirement, do NOT ask him to enumerate โ that pushes the cognitive load back. Run the 9-point boss-audit checklist yourself and fix what you find. The checklist lives in
references/bundled-html-boss-audit-checklist.md โ default landing = boss doc, <title> in ็นไธญ, skip link, print button, footer hint, print CSS, aria-label, tabindex. Verified crm-system 2026-06-06.
The build script verifies structure. Your job is to verify the artifact serves its audience. The boss cares about first impression + navigation + printability, not schema correctness.
When David asks "give me html version" or "show me project docs"
Load this skill and run the bundled variant (scripts/build_bundled_html.cjs) โ it gives the most shareable artifact in one command. The bundled variant's output goes in docs/ (default name: <project>-docs.html); see references/bundled-html-recipe.md for the gotchas. Always read that reference file before running โ every pitfall there (.cjs extension, </script> escape, regex footgun, leading H1 strip) bit at least one session.
When David asks for a Word file (.docx) โ use pandoc, not bundled HTML (2026-06-09 pm-system)
If the request is "็ๆไธไปฝ word file" / "็ตฆๆ docx" / "for printing" / "for the boss to forward", do NOT hand the boss the bundled HTML โ many corporate recipients still default to Word and won't open .html. Generate a .docx from the same MD source:
cd ~/www/<project>/docs
pandoc USER-MANUAL.md -o <Project>-็จๆทไฝฟ็จๆๅ.docx \
--toc --toc-depth=2 \
--metadata title="<Project> ็จๆทไฝฟ็จๆๅ"
Should the .docx be committed to git? Default yes for one-off deliverables like USER-MANUAL. Do NOT gitignore โ David wants the artifact shareable. ~3 MB per doc is acceptable for project repos with < 5 manuals.
Naming: use the project's display name in the file (e.g. PM-System-็จๆทไฝฟ็จๆๅ.docx not user-manual.docx) so the recipient immediately knows what they're opening.
Audience-aware writing for USER-MANUAL and external docs (2026-06-09 lesson):
- David ๅ
conversation ็จ Cantonese ๅฃ่ช,ไฝๆญฃๅผๆไปถ (USER-MANUAL / API doc / onboarding guide) ่ฆ็จไธญๆๆธ้ข่ช (ๆฎ้่ฉฑ)ใ
- ๅปฃๆฑๅฃ่ช็นๅพต:
ๅ
/ ๅฐ / ๅฒ / ๅ / ๅฉ / ๅ / ๅ / ๅ / ใ / ๅฆ / ๅ / ็ไธ / ่ทไฝ / ๅนพๅค / ้ๅ / ้ป่งฃ / ้ปๆจฃ
- ๆธ้ข่ชๅฐๆ:
็ / ่ฏฅ / ไบ / ไบ / ไปไน / ไธ / ๆ / ๆฅ / ๏ผๅช๏ผ/ ๏ผๅช๏ผ/ ๏ผๅช๏ผ/ ๆฅ็ / ็ถๅ / ๅคๅฐ / ๅชไธช / ไธบไปไน / ๅฆไฝ
- ่ง่ฒๆ่ฟฐ่ฆๅ
จๅๅ:ใAdminใโใ็ฎก็ๅใใใPMใโใ้กน็ฎ็ป็ใใใDeveloperใโใๅผๅไบบๅใ
- ๅ่ฉ:ใๆ/ๆ/ๅ/ๆด/็จใโใๅฐไผ/ๅ
ทๆ/ๆง่ก/ๅๅปบ/ไฝฟ็จใ
- Verification:
grep -nE "ๅ
|ๅฐ|ๅฒ|ๅ|ๅฉ|ๅ|ๅ|ๅ|ใ|ๅฆ|ๅ" docs/USER-MANUAL.md ๅฟ
้ 0 ่กๅ
็ฎๆธ้ข่ชๅๅฎๆใ
Playwright screenshot recipe (2026-06-09 pm-system, see references/playwright-screenshot-recipe.md)
When the doc includes 20+ page screenshots (USER-MANUAL, ONBOARDING, etc.), use Playwright headless Chromium to capture them. Key patterns:
- Reuse existing project playwright:
cd e2e && node ../script.js โ pm-system / crm-system both ship e2e/node_modules/playwright already (no need to add to frontend deps).
- First run downloads chromium binary (~120 MB) into
~/.cache/ms-playwright/chromium-NNNN/. The npx playwright install chromium step in fresh projects takes ~30s; subsequent runs are instant.
- Verify routes before screenshotting:
grep -rE "Route path=\"" frontend/src/App.tsx and grep -E "<name>Page" frontend/src/pages/*.tsx โ pm-system has AgentMonitorPage.tsx source but no Route registered โ /agent-monitor 404. Always navigate to the actually-routed page.
- Click the right sub-tab:
ProjectDetailPage uses local useState tabs, not URL params. Pass a fragment #tasks then await page.locator('button').filter({ hasText: /^ไปปๅ\s*\(/ }).click().
- 5 KB screenshots = blank page (404, redirect to login, or modal that didn't open). After the script finishes,
ls -la screenshots/ | awk '$5<10000' lists them โ re-shoot manually.
- Hardcode IDs as constants at top of script (
PROJECT_ID = 'bfba6607-...'), reference them in routes, and re-derive from API after each fresh seed (the req.get(...) ๅ
่ฃน็ list endpoint may have changed since last run).
See references/playwright-screenshot-recipe.md for the full 20-page script + seed-data recipe.
๐ Bundled HTML: node --check self-test FAILS to catch all bugs (2026-06-08 lesson)
The bundled script (build_bundled_html.cjs) has a node --check self-test that extracts the inlined <script>...</script> block and runs Node syntax check on it. This catches a lot, but it is NOT a substitute for a visually inspectable source.
Symptom I hit on pm-system 2026-06-08 while building USER-MANUAL.md:
node build_bundled_html.cjs pm-system โ exit 1, error points at line 188 of the inlined JS
- The error was "Unexpected token
}" but the source line 188 had 6 { and 6 } โ visually balanced per-line
- Real cause:
renderSearchResults was missing ONE closing } for the inner for (var j=0; ...) loop. The function's final } was closing the inner for-loop, leaving the outer for-in block open. The brace count per-line was coincidentally balanced; only the SEMANTIC structure was wrong.
- Spent ~20 minutes trying to patch the inline regex
[^)\\s]+ (which Node 22's regex literal parser DOES reject for unescaped )) before realising the actual bug was the missing }.
Three concrete bugs in the bundled script's inline JS that the node --check self-test fails to make easy to diagnose:
- Brace count per-line is misleading in a one-liner. When the entire function body is on one line, the brace count balances by coincidence, and you can't tell from the error message which brace closes which block.
- Unescaped
) in regex character class [^)\\s]+. Node 22's regex literal parser rejects this at syntax-check time. new RegExp(string, 'g') accepts it; the literal form does not. Fix: use [^)\s]+ (omit ) since (?! ) style negative lookahead is rare in image/link URLs).
- Self-test exit code is 1 on failure, but the error message points at the inlined JS line number, which is hundreds of lines into the file. Open the inlined JS in
/tmp/inlined-app-test.js to map it back to the source โ but only IF the file wasn't auto-cleaned. The self-test deletes the temp file in its finally block, so if you re-run the build to investigate, you get a fresh node --check error pointing back at the inlined line.
Fixes applied in the bundled script (2026-06-08):
- All inline JS rewritten in multi-line, properly indented form. Source structure = inlined structure. Brace count by eye is now feasible.
- Image + link regexes use
[^)\s]+ (no unescaped )).
renderSearchResults rewritten with explicit braces, no one-liner density.
- CHANGE LOG added at the top of the script explaining the three fixes.
- A new "alternative path" added below: if the bundled script keeps giving you grief, just use pandoc for a single-file MD โ HTML โ see
Pandoc single-file fallback (when bundled script misbehaves) below.
Lesson to encode in any future inline-JS-in-template-literal scripts:
- Never write the inlined JS as a one-liner. Use multi-line, properly indented.
- Always run
node --check against the inlined JS (which the bundled script does) โ but also load the file in a browser or cat the inlined content and inspect brace count by eye.
- For regex literals, prefer
new RegExp(string, 'g') over /literal/ when the regex contains ( or ) in a character class. The constructor form is more permissive.
- The self-test exit code is a necessary but not sufficient signal. Add a
console.log("โ inlined JS verified") AFTER the self-test, not just inside the try block โ so you can tell the self-test actually ran.
Pandoc single-file fallback (when bundled script misbehaves) โ 2026-06-08
If build_bundled_html.cjs keeps self-test-failing on your MD content (e.g. your MD has a code block that contains a JS-breaking regex), don't fight the bundled script โ fall back to pandoc for a single-file MD โ HTML. This is fine for one-off user manuals, READMEs, or any non-engineering doc that doesn't need the in-page search / sidebar nav.
pandoc docs/USER-MANUAL.md -o docs/<project>-user-manual.html \
--standalone --metadata title="<title>" --toc --toc-depth=2 \
--syntax-highlighting=none
python3 -c "
import re
html = open('docs/<project>-user-manual.html').read()
css = open('~/.hermes/profiles/developer/skills/doc-html-preview/templates/github-like.css').read()
html = html.replace('</head>', f'<style>{css}</style>\n<style>body{{max-width:980px;margin:0 auto;padding:32px;font-family:-apple-system,\"PingFang TC\",\"Microsoft JhengHei\",sans-serif}}</style>\n</head>', 1)
html = html.replace('<title>...</title>', '<title>... โ ็จๆถๆๅ</title>')
html = html.replace('<body>', '<body class=\"markdown-body\">')
open('docs/<project>-user-manual.html', 'w').write(html)
"
When to use bundled vs pandoc fallback (decision table):
| Audience | Use bundled | Use pandoc fallback |
|---|
| 1-3 docs, want sidebar nav + search | โ
| โ |
| 1 doc (user manual / README) | โ (overkill) | โ
|
| โฅ 10 docs, navigation matters | โ
| โ |
| MD contains JS code samples that break inline regex | โ | โ
|
| Need print-friendly PDF export | โ (use pandoc --pdf-engine=wkhtmltopdf) | โ
|
| Need dark/light theme toggle | โ
| โ |
| David explicitly asks for "one HTML file" | depends on doc count | depends on doc count |
| Time-box: pandoc builds in < 1s, bundled 5-15s for โฅ 10 docs | fallback OK | bundled OK |
The pandoc fallback is strictly worse for navigation / search but strictly more robust for any MD content. Default to bundled for โฅ 5 docs, default to pandoc for 1-2 docs.
๐ Sub-folder MDs (2026-06-07 crm-system lesson)
build.sh --project <name> only globs docs/*.md โ it does NOT recurse into subdirectories like docs/retros/, docs/architecture/, or docs/handoff/. To render a sub-folder MD, pass it as a positional argument:
bash ~/.hermes/profiles/developer/skills/doc-html-preview/scripts/build.sh \
--project crm-system \
docs/retros/2026-06-07-system-settings-plan.md \
docs/architecture/0001-ai-assistant-architecture.md
Each path produces <basename>.html + <basename>-boss.html in docs/_html/ (just the basename โ sub-folder prefix is dropped). This is fine for one-off retro / plan / ADR documents; if you have many sub-folder MDs to publish regularly, consider either (a) flattening into docs/ with a numbering scheme, or (b) writing a wrapper script that calls build.sh per-path.
๐ Boss JSON key is filename-derived, not semantic (2026-06-07 crm-system)
build_html.py looks up the boss summary JSON by exact basename match: <md-basename>.json in docs/_meta/. If you write your MD as 2026-06-07-system-settings-plan.md, the script looks for docs/_meta/2026-06-07-system-settings-plan.json โ NOT a more semantically named file like plan-system-settings.json.
Three workable patterns:
- Match the name โ name the MD and JSON with the same basename from the start.
- Copy/symlink โ write JSON to the semantic name, then
cp it to the filename-derived name before building:
cp docs/_meta/plan-system-settings.json \
docs/_meta/2026-06-07-system-settings-plan.json
- Use the MD override โ add
## ๐ ่้็ๆ่ฆ section to the MD and the script will use that body content instead of looking up JSON. Best when you want full control over the boss view and don't want to maintain a separate JSON.
The build script does NOT log which key it looked up, so if your boss HTML renders as placeholder, the first thing to check is "does the JSON basename match the MD basename exactly?"
How to use
After writing a project doc
bash ~/.hermes/profiles/developer/skills/doc-html-preview/scripts/build.sh \
--project <project-name>
This produces:
docs/_html/<doc>.html (engineering)
docs/_html/<doc>-boss.html (boss; renders placeholder if no JSON yet)
Generating the boss summary JSON
The docs/_meta/<doc>.json is generated by an LLM reading the MD. Generate it BEFORE running build.sh โ see "๐ Boss HTML verification (mandatory)" above for why post-hoc detection is broken.
Who should write the JSON โ CEO subagent vs main agent (2026-06-06 lesson):
| Scenario | Recommended | Why |
|---|
| โค 5 docs, fresh project, no prior context | CEO subagent (or any LLM in isolation) | The MD is the only context needed; CEO can derive the full summary from MD alone. |
| 6+ docs in an existing project with rich history (e.g. crm-system Day 9) | Main agent directly | Subagents lack the conversation history (Prisma enum drift, bun build pitfall, redact JWT block, etc.) that informs the "risks_boss_speak" and "mitigation" cards. A CEO subagent will hallucinate generic risks; the main agent can write concrete, project-specific ones (e.g. "Prisma enum drift ๅฐ่ด production 42704 error โ Day 9 ๆ้, ๅทฒ patch skill + ๅ CREATE TYPE ๆ ก้ฉ"). |
| Mixed: some docs have prior context, some don't | Main agent for known docs, subagent for fresh docs | The known docs' risks reference real incidents; the fresh docs need only MD-internal context. |
Schema (use exactly this โ build_html.py reads these keys literally):
{
"doc": "PRD",
"source_md": "docs/PRD.md",
"generated_at": "2026-06-06T14:50:00+08:00",
"generated_by": "AI (developer profile / CEO subagent)",
"one_liner": "<ONE sentence, non-technical, what is this for>",
"cards": {
"timeline": "<short estimate>",
"cost": "<short estimate>",
"mitigation": "<what's already planned to de-risk>",
"disclaimer": "<one-line warning that estimates are preliminary>"
},
"decisions": [
{
"question": "...",
"options": [
{ "label": "A. ...", "pros": "...", "cons": "..." },
{ "label": "B. ...", "pros": "...", "cons": "..." }
],
"default": "A",
"blocking": true
}
],
"risks_boss_speak": [
"<risk 1 in plain language>",
"<risk 2 in plain language>"
]
}
Reference sample โ the crm-system docs/_meta/PRD.json is the gold standard:
- 3 cards covering timeline / cost / mitigation (each โค 1 sentence)
- 3 decisions: at least 1 marked
"blocking": true (David must pick before the project can ship)
- 2-4
risks_boss_speak items in plain Cantonese-็นไธญ, ideally referencing real incidents from the project, not generic platitudes
The pseudocode workflow:
import json, datetime
md_text = open("docs/PRD.md").read()
summary = {
"doc": "PRD",
"source_md": "docs/PRD.md",
"generated_at": datetime.datetime.now().astimezone().isoformat(timespec="seconds"),
"generated_by": "AI (developer profile / main agent)",
"one_liner": "...",
"cards": {...},
"decisions": [...],
"risks_boss_speak": [...]
}
open("docs/_meta/PRD.json", "w").write(json.dumps(summary, ensure_ascii=False, indent=2))
Then run build.sh once, then verify_boss_html.sh. Done.
โ ๏ธ Pitfall: Bundled build_bundled_html.cjs has 2 real Node 22 bugs (2026-06-09 pm-system)
Bug A โ Node 22 V8 regex parser rejects unescaped ) inside character class. The original template inlined /!\[\[^\]\]*\]\([^)\s]+.../g for the markdown image regex, and /\[\[^\]]+\]\([^)\s]+\)/g for the link regex. Node 22's node --check (called by the build script's self-test) refuses the literal form with "Unmatched ')'" even though new RegExp(string, 'g') with the same string is accepted. Result: node build_bundled_html.cjs <project> exits 1 with no useful error.
Fix: rewrite the two regexes using the new RegExp('...', 'g') constructor form (the string literal is run through JS string escaping once instead of twice). The script does this in renderInline(...). If you add new regex-based markdown features (e.g. footnotes, definition lists), use the constructor form too.
Bug B โ renderSearchResults j-loop missing } close brace. The original one-liner for(var j=...){...html+='<li>...</li>'}html+='</ul>...' closes the j-loop once but is followed immediately by html+='</ul>...' which closes the for-in block. So the j-loop body ended with </li>'} and the for-in block never closed properly โ but on some Node versions the parser tolerated it (crm-system 2026-06-06 likely hid this). Node 22 surfaces "Unexpected token '}'" on the inlined JS. The script now writes renderSearchResults across multiple lines for clarity and the brace structure is explicit.
Detection: if build_bundled_html.cjs exits 1 with node:internal/modules/cjs/loader:1620:18 or wrapSafe in the stack trace, suspect either bug. Run node --check on /tmp/inlined-app-test.js after extracting the inlined <script> block to localize. Already done by the script's self-test โ so the failure is the self-test, not the build itself.
โ ๏ธ Pitfall: Boss HTML can silently render as placeholder when JSON is missing (2026-06-06 crm-system)
Symptom: build.sh --project <name> completes without error, all *-boss.html files exist with similar size (~20โ24 KB), but opening them in a browser shows a "๐ ่้็ๆ่ฆๅพ
็ๆ" placeholder card. The build script does NOT fail when JSONs are missing โ it silently falls back to an empty template.
Why file size is misleading: The boss template (header pill, one-liner skeleton, 3 cards, decision skeleton, risks skeleton, footer) is large enough that even an empty placeholder renders at ~20 KB. Do NOT trust file size as a content signal. Verify with grep instead:
for f in docs/_html/*-boss.html; do
if awk '/<\/head>/,/<script>/' "$f" | grep -q 'boss-placeholder'; then
echo "โ PLACEHOLDER $f"
else
echo "โ
HAS CONTENT $f"
fi
done
Root cause: Boss version requires either docs/_meta/<doc>.json or a ## ๐ ่้็ๆ่ฆ section inside the MD to render real content. The build script generates HTML from MD + (optional) JSON; if neither exists, it falls back to the placeholder template without warning.
Fix: Generate docs/_meta/<doc>.json for each doc (schema + example in references/crm-system-2026-06-06-boss-json-placeholder-incident.md). See also ## ๐ ่้็ๆ่ฆ MD override below.
Main agent vs CEO subagent for JSON generation: When the agent already has full project context (mid-development, single-developer project, MD count is small), the main agent writing the JSONs directly is more reliable than spawning a CEO subagent โ subagents tend to hallucinate risks that don't match the project's actual failure modes. Use the main agent for: โค15 docs, when the agent has read most/all MDs recently, when consistency across docs matters. Spawn CEO subagents only for: 15+ docs, when the agent has zero context, or when each doc needs deep cross-doc analysis.
MD override (escape hatch)
If you want a manual boss version (skipping AI generation), add this section to the MD:
## ๐ ่้็ๆ่ฆ
ๅขๅ PRD ๅ
้้ปไฟ blah blah...
ไธป่ฆๆๆฟไบ้
๏ผ
- ๆ A ๅฎ B?
- ๆ X ๅฎ Y?
The build script will:
- Use the content under this heading as the boss body (replacing AI cards/decisions/risks)
- Use the first
<p> paragraph as the one-liner in the top blue callout
This is for when David wants full control over what he sees, and doesn't want AI-generated content for sensitive decisions.
What this skill does NOT do
- โ Does NOT call LLM inside the build script (purely local render)
- โ Does NOT set up a web server
- โ Does NOT push to a hosting platform
- โ Does NOT send Discord notifications
- โ Does NOT commit HTML or meta JSON to git
๐จ Verify the artifact is on origin before reporting done (2026-06-06 lesson)
Hit twice on crm-system โ David git reverted the entire HTML viewer (8c39210) and the agent's memory/conversation history had no record of the revert. The agent kept reporting "done, pushed" while the file was actually gone from the remote.
Mandatory end-of-task check (3 seconds, saves hours):
cd ~/www/<project> && \
echo "=== Local ahead of origin ===" && \
git log --oneline origin/main..HEAD && \
echo "=== File tracked on origin ===" && \
git ls-files docs/ | grep -E '(bundled\.html|_html/)' && \
echo "=== Working tree clean ===" && \
git status --short
All three must check out:
- No commits left unpushed (or push them).
- The HTML file is in
git ls-files (for the crm-system exception case) or docs/_html/<doc>.html is generated locally (for the per-doc case).
- No uncommitted drift in
docs/*.md source files.
If the build script regenerated HTML but git status shows uncommitted _html/ changes, that means .gitignore is missing the entry โ fix .gitignore (per the table above) before declaring done.
This check applies to ANY skill that produces a build artifact, not just doc-html-preview. The pattern: produce โ push โ verify-on-remote. Skipping the third step is how reverts / untracked drift slip through.
Bundled single-file variant (alternative output)
The default build.sh produces one HTML per source MD plus a
boss variant (Nร2 files for N docs). For projects where the user
asks for a single portable / shareable artifact, use the bundled
variant instead:
node scripts/build_bundled_html.cjs <project-name>
The bundled variant is one self-contained HTML file containing
every MD in the project, with in-browser sidebar navigation, search,
and theme toggle. Opens directly via file:// on any machine
without a local server โ ideal for emailing or archiving.
Differences from the per-doc output:
| Aspect | Per-doc (build.sh) | Bundled (build_bundled_html.cjs) | Pandoc single-file (fallback) |
|---|
| File count | Nร2 (engineering + boss per MD) | 1 | 1 |
| Navigation | Browser tab-switching | In-page sidebar + hash routing | TOC only (no nav) |
| Search | None (browsers do it per file) | Client-side full-text, / shortcut | None |
| Themes | Light + dark per file | Light + dark, persisted in localStorage | Light only (default pandoc CSS) |
| Server | None (file://) | None (file://) | None (file://) |
| Gitignore | docs/_html/ | docs/<project>-docs.html | docs/<project>-user-manual.html |
| MD feature support | Full pandoc | GFM subset (headings, code, lists, tables, blockquotes, inline) โ no LaTeX/Mermaid | Full pandoc |
| Size | ~50 KB per doc | ~20 KB + MD content (~170 KB for an 11-doc project) | ~40 KB for a 600-line user manual |
| Robustness | High (Python, no JS escaping) | Lower (Node.js inline template literal โ see "๐ Bundled HTML: node --check self-test FAILS to catch all bugs" above) | Highest (pandoc is bulletproof) |
| Build time | 1-3s per doc | 5-15s for โฅ 10 docs | < 1s |
The bundled script auto-discovers every docs/*.md and the repo-root
README.md, so you don't have to list them. For a different list,
pass extra MD paths as positional args.
Recipe + gotchas (read this before running the bundled script
on a new project โ covers the .cjs extension requirement for
"type": "module" projects, the </script> escape in content,
and the regex-extraction footgun when verifying): see
references/bundled-html-recipe.md.
Boss-audit checklist (run this BEFORE handing the bundled file
to the boss โ build script only verifies structure, not audience
appropriateness): see
references/bundled-html-boss-audit-checklist.md.
When David asks for "html version of the docs" or "show me the
project docs": load this skill and run the bundled variant โ
it gives the most shareable artifact in one command.
Future evolution
| If you want... | Then consider... |
|---|
| Cross-project nav, search, sidebar | Upgrade to MkDocs + Material |
| Live reload on MD save | Add watchdog / fswatch |
| Embed previews inside Hermes dashboard 9119 | Write a Hermes plugin |
| Auto-generate boss JSON on MD write | Hook into developer profile's write step |
| Interactive decision capture (click A/B) | Add a tiny JS layer + localStorage |
| Encode boss-audit as a CI check | Run the checklist in build_bundled_html.cjs post-build; fail loud |
| Replace bundled script with multi-file format | Migrate to vite-plugin-md or markdown-it; drop the inlined JS entirely |
| Drop bundled script in favor of vite + markdown-it | Pure ESM, no inline JS template literal, no node --check fragility |
Files in this skill
| Path | Purpose |
|---|
SKILL.md | This file |
scripts/build.sh | Bash orchestrator โ calls Python for each doc |
scripts/build_html.py | Python โ renders engineering + boss HTMLs from one MD + optional meta JSON |
templates/github-like.css | Engineering version stylesheet (light + dark) โ also used for the pandoc single-file fallback |
templates/boss-template.html | Boss version template (placeholder + content slots) |
scripts/build_bundled_html.cjs | Bundled variant โ one self-contained HTML file with sidebar + search + theme toggle. Multi-line indented JS (2026-06-08 fix) + node --check self-test. See SKILL.md "๐ Bundled HTML: node --check self-test FAILS to catch all bugs" for the brace-count + regex-character-class pitfalls. CHANGELOG at top of script. |
references/bundled-html-recipe.md | Recipe + gotchas for the bundled variant (.cjs extension, </script> escape, node --check self-test, regex-extraction footgun) |
references/bundled-html-boss-audit-checklist.md | 9-point boss-audit (default landing, <title> lang, skip link, print button, footer hint, print CSS, aria-label, tabindex). Run this before handing the artifact to the boss โ build script only verifies structure, not audience appropriateness. Triggered by vague phrases like "check your settings" / "for the boss" / "ๆๅฉๆฐ่ฆๆฑ" without David enumerating them. |
references/crm-system-2026-06-06-bundled-html-incident.md | Session-specific log of the line-4898 SyntaxError + the audience-mismatch correction (David asked for "PRD ๅ Design ็ตฆ่้็", got engineering docs first). Read before running this skill on a new project. |
references/pm-system-2026-06-08-bundled-brace-bug.md | NEW 2026-06-08 โ session log of the line-188 missing-} bug that node --check self-test failed to make easy to diagnose. Includes the 3-bug investigation timeline + the multi-line rewrite fix. Read before trusting the bundled script on a new project. |
references/boss-summary-schema-and-recipe.md | Boss JSON schema (frozen by template), copy-pasteable example, main-agent generation pattern, and common pitfalls. Read before writing your first docs/_meta/<doc>.json. |
scripts/verify-boss-html.sh | One-shot verifier: greps every *-boss.html for the boss-placeholder class, exits non-zero if any are placeholder. Run after every build.sh invocation โ the build script does not fail when JSONs are missing, so this is your only safety net. |
references/crm-system-2026-06-06-boss-placeholder-silent-failure.md | Session log: 10/12 boss HTMLs silently rendered placeholders despite the build exiting 0. Documents the body-region grep recipe and the main-agent-vs-CEO trade-off for JSON generation. Read this if you ever ship a project with docs/_html/*-boss.html files. |
scripts/verify_boss_html.sh | One-shot bulk verification: scans all *-boss.html in a project, returns exit 0 (all good) / 1 (lists placeholders) / 2 (build never ran). Uses the body-region awk pattern from the lesson. Run this after every build.sh. |
gitignore-snippet.md | What to add to project .gitignore |
Related files in developer profile
SOUL.md ยง"๐ ่ฝๅฏฆๅพๅฟ
็ขๆไปถ" โ documents that trigger this skill
SOUL.md ยงSubagent matrix (CEO role โ Think + Plan) โ boss summary generation owner
docs/project-documentation-standard.md โ the 8 required documents per project
docs/environment-isolation.md โ dev/prod boundary (this skill is dev-only)