Shared Phlex chrome for documentation sites built on daisyUI — the shell, sidebar, code blocks, theme switcher, and page kit extracted into one gem so multiple docs sites look identical and are maintained in one place.
- Ruby: >= 3.2 | Rails: >= 7.1 (engine)
- Rendering: phlex-rails (Phlex 2) — the chrome is
DocsUI::components - Styling: daisyUI (via the
daisyuigem) on Tailwind CSS v4, built with the standalone CLI (Bun) - Icons: lucide via
rails_icons - Highlighting: Rouge (~200 languages), inline theme CSS
- Client: ONE Stimulus controller (
docs-nav) auto-pinned by the engine - Autoloading: zeitwerk
- Testing: RSpec (component render + registry + generator)
- Linting: RuboCop (
rubocop)
- NO raw daisyUI markup — the chrome is composed from
DocsUI::Phlex components; a site never hand-writes drawer/menu HTML - NO hardcoded site-specific values in a component — brand, themes, nav, version badge come from
DocsKit.configuration - NO JS-required pages — the server renders a working, fully-expanded page; the
docs-navcontroller only enhances (collapse persistence, auto-TOC). It must work with JS off - NO per-feature Stimulus controllers — there is exactly ONE (
docs-nav), auto-pinned by the engine - NO theme in
config.themesthat the CSS build never generated — the switcher list MUST match the@plugin "daisyui" { themes: ... }block - NO new emitted class without a CSS scan — Tailwind scans Ruby; render-time classes (Drawer) need
@source inline(...) - NO
raw/html_safeon config free text — let Phlex escape text; only gem-authored trusted markup may bypass the escape - NO required setup documented in the README alone — wire it into the install generator AND the
docs-kit newtemplate, or new sites don't get it - NO manual
gem push— release viarake release[X.Y.Z]
- TDD: Write tests BEFORE implementation (RED → GREEN → REFACTOR)
- Read config, with a default — a new knob lives on
DocsKit::Configurationwith a sensible default so existing sites keep working (backwards compatible) - Compose from the kit — new chrome is a
DocsUI::component, not markup - Render through a real view context —
#render_pagerenders withlayout: falsebecauseDocsUI::ShellIS the whole document; CSRF,dom_id, url helpers, and (on reactive sites) the token signer must still work - Keep the switcher and the CSS in sync — themes offered == themes built
- Wire setup into the install path — the generator templates and
docs-kit newtemplate, both - Assert on semantics, not snapshots — a component spec checks an active link / a present theme / a config-driven value, not a brittle full-HTML string
bundle exec rspec # Suite (component render + registry + config + generator)
bundle exec rubocop # Lint (rubocop -A to autocorrect)
bundle exec rake # spec + rubocop
bun run build:css # Rebuild the Tailwind/daisyUI CSS (in a consuming site)| Command | Purpose |
|---|---|
/plan |
Fable-powered planning → GitHub issue or docs/plans/ markdown (read-only; execute with /lfg) |
/lfg |
Full autonomous workflow: branch → understand → explore → plan → TDD → verify → PR |
/tdd |
Enforce RED → GREEN → REFACTOR |
/architect |
Coordinate a change across config → registry → components → client → generator → CSS |
/security |
Security audit (HTML escaping, config trust, the render path, generated files, deploy secrets) |
/review-pr |
Review a PR for pattern compliance |
/github-review-pr |
Full PR pass: fix CI failures, then resolve review comments (in that order) |
/github-review-failures |
Fix failing CI checks until green |
/github-review-comments |
Process unresolved PR review comments |
Layer 4: Client runtime app/javascript/docs_kit/controllers/docs_nav_controller.js (ONE controller: collapse persistence + auto-TOC + scroll-spy)
Layer 3: Components app/components/docs_ui/*.rb (Shell, Sidebar, ThemeSwitcher, Icon, Code, Page, Header, Section, Prose, Callout, Example, OnThisPage)
Layer 2: Registry + values lib/docs_kit/registry.rb (in-memory docs registry mixin), lib/docs_kit/nav_item.rb (sidebar link value object)
Layer 1: Config + controller lib/docs_kit/configuration.rb (per-site knobs), lib/docs_kit/controller.rb (#render_page)
Layer 0: Core + engine lib/docs_kit.rb, lib/docs_kit/engine.rb (auto-pins docs-nav, mounts the controller assets)
Install path lib/generators/docs_kit/install/ (install generator + templates), lib/docs_kit/templates/new_site.rb, exe/docs-kit (docs-kit new)
Deploy .github/workflows/deploy.yml (reusable), deploy-docs.yml (thin caller); Kamal + GHCR + Cloudflare Tunnel
Every docs site gets the SAME chrome. What differs between two sites is configuration, not markup. A component reads
DocsKit.configuration; the server renders a working page; the onedocs-navcontroller enhances it.
Client interactivity is client-only UX polish (which <details> you left open,
the "on this page" TOC) — there is no server round-trip. See README.md.
Commands and agents pin a model tier via frontmatter aliases, not a full model ID — aliases track the latest model in each tier, so pins never go stale:
haiku— mechanical/config work, diff pattern-scanssonnet— layer specialists / pattern-following implementation (the default for/tdd, the review-comment/failure runbooks)opus— orchestration, security, production/PR review (/lfg,/architect,/security,/review-pr,/github-review-pr)fable— pinned only on/plan(read-only planning that hands execution to cheaper models); otherwise choose it per-session with/modelfor architecture and the hardest debugging
When spawning subagents for mechanical work (file finding, pattern scans), pass a
cheaper model explicitly (model: haiku) rather than letting them inherit the
session model. See .claude/rules/agents.md.
- Unit specs (
spec/docs_kit/) cover the config surface and the registry — no Rails boot. - Component specs (
spec/docs_ui/) render aDocsUI::component and assert on the produced markup's semantics (an active link, a present theme option, a config-driven value). - Generator specs (
spec/generators/install_generator_spec.rb) rundocs_kit:installagainst a throwaway destination root (a tmp app skeleton, plain Thor — no Rails boot) and assert the file manifest + key contents. - Coverage: SimpleCov enforces
minimum_coverage 80from within the suite (bundle exec rspec/rakefails below it); 100% aspired forDocsKit::ConfigurationandDocsKit::Registry(the public API sites depend on). - CI:
.github/workflows/ci.ymlrunsbundle exec rakeon Ruby 3.2/3.3/3.4 for every push tomainand every PR. - See
.claude/rules/testing.md.
The build + deploy is defined once in this gem's reusable workflow
(.github/workflows/deploy.yml). docs-kit new scaffolds a thin caller. The
naming invariant: image/service must be the calling repo's OWNER/REPO so
GITHUB_TOKEN can push (build) and pull (deploy) the auto-linked GHCR package.
See the README "Deploy a new docs site" section for the five wiring points and
the naming note.
.claude/commands/— slash command definitions.claude/rules/— coding style, git workflow, testing, agentsREADME.md— the full install/configure/render/deploy guide