Problem / Goal
docs-kit exists to make documentation happen. Today the chrome is excellent but the authoring cost is too high, so pages don't get written (daisyui's docs: 3 registered pages, only 1 authored). Three audits (this gem's own docs site, the daisyui + phlex-reactive sites, and zazu/app-4's API docs) converge on the same evidence:
- Prose is written as Ruby, not prose. One paragraph with two inline code spans costs ~11 lines of
p do code { … } plain " … " end. The gem's own docs/app/views/docs/pages/installation.rb spends 19 plain/code calls on 6–7 sentences; the file is 140 lines for what Markdown says in ~40.
- Every site hand-rolls the same missing pieces. A props/options table (
PropTable) is copy-pasted per site; app-4 has 22 raw table blocks, 216 raw tr/td calls, and 15+ near-duplicate err_row/field_row helpers across 9 files. Both consumer sites carry a byte-identical RuboCop cop and a duplicated render_page.
- API docs restate the same fact up to 7 times. In app-4, one endpoint's example is authored once per client (curl/CLI/JS/Ruby/Python), again as the JSON response, and a third time in a hand-written Markdown
to_text twin (929-line invoices.rb). ~25% of the 4,301 docs LOC is restatement, not content.
- Registration is quadruple bookkeeping.
slug + title + group + view: string, kept in sync by hand with a 4-level-nested class in another file, plus a 10-line nav lambda every site copies. There is no docs_kit:page generator.
- Nothing is machine-readable. No
.md view of a page, no llms.txt, no search, no MCP. app-4 had to invent "Copy for LLM" by hand-writing a Markdown twin of every page — the strongest possible signal that AI consumption is a real need the gem doesn't serve.
Done looks like: a new page is one generator command; prose is Markdown; an API endpoint is one structured declaration that renders every client tab; and every page automatically has a Markdown twin that powers llms.txt, search, "copy for LLM", and an optional built-in MCP server — with zero extra authoring.
Decision
Phlex-native ergonomics + markdown islands + one derivable source of truth. Pages stay Phlex classes (the registry → chrome → TOC pipeline and live demos depend on it), but:
- Prose becomes Markdown (
DocsUI::Markdown / md helper) — kills the p/plain/code chains.
- The missing components ship in the gem (Table/PropTable, Endpoint, FieldTable/ErrorTable, RequestExample, JsonResponse).
- The registry becomes one line per page with derived slug/view and auto-derived nav; a
docs_kit:page generator scaffolds the rest.
- Every page gets an automatic Markdown twin (HTML→Markdown over the bounded tag vocabulary the kit itself emits).
llms.txt, llms-full.txt, server-rendered search, a copy-as-Markdown button, and an optional MCP endpoint all derive from it. One source of truth; the AI surface costs authors nothing.
Alternatives rejected:
- Markdown-first pivot (pages as
.md files) — loses live demos/galleries/reactive components (daisyui's 275 example files, phlex-reactive's demos), breaks every existing page, and turns docs-kit into another static-site generator. Markdown belongs inside pages, not instead of them.
- A new builder DSL (
page do … end) — a second API surface that doesn't fix the actual pain (prose verbosity) and churns all sites.
- Authoring-side MCP server — agents writing docs work inside the repo; they need conventions + generators (an AGENTS.md/skill), not a protocol. MCP is for reading published docs; it comes last, built on the export pipeline.
✅ Delivered — all sub-issues shipped and merged (PRs #25–#39, plus #6/#7 for the adjacent #3/#2). Closed 2026-07-03 after a full deferred-scope audit; see the closing comment.
Sub-issues
Phases are ordered by leverage; issues within a phase are independent unless noted. #23 (CI) should merge first — everything else lands through it.
Phase 1 — Authoring ergonomics
Phase 2 — API-docs kit (the app-4 patterns, upstreamed)
Phase 3 — One source of truth → machine-readable
Phase 4 — AI-native
Phase 5 — Health & consolidation
Invariants every sub-issue must respect
Chrome is DocsUI:: components only; site values come from DocsKit.configuration with defaults (backwards compatible); pages work with JS off; exactly ONE Stimulus controller (docs-nav) — new interactivity extends it; config.themes == the Tailwind @plugin list; new render-time classes need an @source scan; required setup wires into the install generator AND docs-kit new; TDD (specs before code).
Adjacent in-flight work (not part of this epic)
Out of scope
Execution
Execute sub-issues with /lfg <issue-number>, phase by phase (start with /lfg 23).
Problem / Goal
docs-kit exists to make documentation happen. Today the chrome is excellent but the authoring cost is too high, so pages don't get written (daisyui's docs: 3 registered pages, only 1 authored). Three audits (this gem's own docs site, the daisyui + phlex-reactive sites, and zazu/app-4's API docs) converge on the same evidence:
p do code { … } plain " … " end. The gem's owndocs/app/views/docs/pages/installation.rbspends 19plain/codecalls on 6–7 sentences; the file is 140 lines for what Markdown says in ~40.PropTable) is copy-pasted per site; app-4 has 22 rawtableblocks, 216 rawtr/tdcalls, and 15+ near-duplicateerr_row/field_rowhelpers across 9 files. Both consumer sites carry a byte-identical RuboCop cop and a duplicatedrender_page.to_texttwin (929-lineinvoices.rb). ~25% of the 4,301 docs LOC is restatement, not content.slug+title+group+view:string, kept in sync by hand with a 4-level-nested class in another file, plus a 10-line nav lambda every site copies. There is nodocs_kit:pagegenerator..mdview of a page, nollms.txt, no search, no MCP. app-4 had to invent "Copy for LLM" by hand-writing a Markdown twin of every page — the strongest possible signal that AI consumption is a real need the gem doesn't serve.Done looks like: a new page is one generator command; prose is Markdown; an API endpoint is one structured declaration that renders every client tab; and every page automatically has a Markdown twin that powers
llms.txt, search, "copy for LLM", and an optional built-in MCP server — with zero extra authoring.Decision
Phlex-native ergonomics + markdown islands + one derivable source of truth. Pages stay Phlex classes (the registry → chrome → TOC pipeline and live demos depend on it), but:
DocsUI::Markdown/mdhelper) — kills thep/plain/codechains.docs_kit:pagegenerator scaffolds the rest.llms.txt,llms-full.txt, server-rendered search, a copy-as-Markdown button, and an optional MCP endpoint all derive from it. One source of truth; the AI surface costs authors nothing.Alternatives rejected:
.mdfiles) — loses live demos/galleries/reactive components (daisyui's 275 example files, phlex-reactive's demos), breaks every existing page, and turns docs-kit into another static-site generator. Markdown belongs inside pages, not instead of them.page do … end) — a second API surface that doesn't fix the actual pain (prose verbosity) and churns all sites.Sub-issues
Phases are ordered by leverage; issues within a phase are independent unless noted. #23 (CI) should merge first — everything else lands through it.
Phase 1 — Authoring ergonomics
DocsUI::Markdown+mdhelper)DocsUI::Table+DocsUI::PropTablerails g docs_kit:pagegenerator (needs feat(registry): one-line page registration + auto-derived nav #12)Phase 2 — API-docs kit (the app-4 patterns, upstreamed)
DocsUI::Endpoint+FieldTable/ErrorTable(needs feat(table): ship DocsUI::Table + DocsUI::PropTable — stop hand-rolling tables #11)DocsUI::RequestExample+DocsUI::JsonResponsebrand_href, dark/light code themesPhase 3 — One source of truth → machine-readable
.mdformat + copy button)/llms.txt+/llms-full.txt(needs feat(markdown-twin): every page renders as Markdown — .md format + copy-page button #17, feat(registry): one-line page registration + auto-derived nav #12)Phase 4 — AI-native
Phase 5 — Health & consolidation
Invariants every sub-issue must respect
Chrome is
DocsUI::components only; site values come fromDocsKit.configurationwith defaults (backwards compatible); pages work with JS off; exactly ONE Stimulus controller (docs-nav) — new interactivity extends it;config.themes== the Tailwind@pluginlist; new render-time classes need an@sourcescan; required setup wires into the install generator ANDdocs-kit new; TDD (specs before code).Adjacent in-flight work (not part of this epic)
fix/csp-nonce-shell(unmerged).feat/configurable-icon-library(unmerged). Both remove app-4 shims once merged.Out of scope
openapi.yamlfrom request specs but it is unconnected to the docs; an OpenAPI bridge is a natural follow-up epic once feat(api-docs): DocsUI::Endpoint + FieldTable/ErrorTable — the endpoint-reference kit #14/feat(api-docs): DocsUI::RequestExample + JsonResponse — one request definition, every client tab #15 exist as render targets).IconHelper, adoptingDocsKit::Controller) — follow-ups in those repos once fix(generator): idempotent install + a consumer re-sync guide #24 ships the--synctool.Execution
Execute sub-issues with
/lfg <issue-number>, phase by phase (start with/lfg 23).