Skip to content
Merged
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
81 changes: 81 additions & 0 deletions docs/.claude/skills/write-docs-page/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
---
name: write-docs-page
description: "Write, add, or update a documentation page in this docs-kit site. Use when asked to document a feature, endpoint, class, or workflow, or to add or edit a page under app/views/docs/pages/. Scaffolds with `rails g docs_kit:page`, writes Markdown-first content, and runs the verification gates."
---

# Write a docs page

This is a [docs-kit](https://github.com/mhenrixon/docs-kit) site (Phlexy ui docs).
Every page is a `DocsUI::Page` subclass; the shell, sidebar, "On this page" TOC,
search, and the `.md` twin all come free. Your job is to scaffold a page and
write its `#content` — never hand-write HTML or daisyUI markup.

The full authoring contract is in the repo's `AGENTS.md`. This is the recipe.

## 1. Gather the subject

Identify exactly what to document (the code, endpoint, or workflow) and where it
belongs in the sidebar (the `--group`). Read the relevant source first — do not
invent behavior. If the subject is an HTTP endpoint, prefer `DocsUI::RequestExample`
/ `DocsUI::FieldTable`; if it's a Ruby API, prefer `DocsUI::PropTable`.

## 2. Scaffold (one command)

```bash
rails g docs_kit:page "Page Title" --group=Guide
```

This writes `app/views/docs/pages/<slug>.rb` **and** injects the required
`page "…"` registry line into `app/models/doc.rb`. Overrides: `--slug`, `--view`,
`--eyebrow`, `--registry`. If it reports a legacy `entries [...]` registry, add
the printed line by hand.

## 3. Write `#content` — Markdown first

- Set `title`, `eyebrow`, and a one-sentence `lead`.
- One `DocsUI::Section("…")` per part of the page — **Sections own structure and
the TOC.** Never use a Markdown `##` for page structure.
- Prose is `md <<~'MD' … MD` — a **single-quoted** heredoc (no escaping, no
interpolation; Phlex escapes author text). Markdown `###` is only for
sub-headings inside a Section.
- Reference material: `DocsUI::PropTable`, `DocsUI::FieldTable`,
`DocsUI::RequestExample`, `DocsUI::Code(source, filename:)`,
`DocsUI::Callout(:note | :tip | :warning)`.

```ruby
class Views::Docs::Pages::PageTitle < DocsUI::Page
title "Page Title"
eyebrow "Guide"

def lead = "One sentence describing the page."

def content
DocsUI::Section("Overview", description: "What this covers.") do
md <<~'MD'
Prose as **Markdown**. Fenced ```ruby``` blocks highlight; `inline code`,
lists, links, and GFM tables all render styled.
MD
end
end
end
```

## 4. Self-review against the checklist

- [ ] The `page "…"` registry line exists (the generator adds it).
- [ ] Structure is `DocsUI::Section`s, not Markdown `##` headings.
- [ ] Prose uses `md <<~'MD'` (single-quoted); no `html_safe`, no `raw`.
- [ ] No hand-written HTML/daisyUI markup, no per-feature Stimulus controller.
- [ ] Reads correctly with JavaScript off (the server renders it fully).
- [ ] Any new theme is in both `c.themes` and the Tailwind `@plugin` block.
- [ ] No inline `rubocop:disable` to force layout.

## 5. Run the gates

```bash
bundle exec rspec && bundle exec rubocop
bun run build:css # only if you added classes the CSS must scan
```

Then render locally (`bin/dev`, open `/docs/<slug>`) and confirm it reads well.
For depth on any idiom, read the live [Authoring pages](/docs/authoring) doc.
118 changes: 29 additions & 89 deletions docs/.rubocop.yml
Original file line number Diff line number Diff line change
@@ -1,156 +1,96 @@
---
plugins:
- rubocop-performance
- rubocop-rails
- rubocop-rake
- rubocop-rspec

# Local custom cop: enforce the DocsUI/DaisyUI Phlex kit form
# (DocsUI::Code(...)) over `render DocsUI::Code.new(...)`.
- rubocop-performance
- rubocop-rails
- rubocop-rake
- rubocop-rspec
require:
- ./lib/rubocop/cop/docs_kit/render_component_preferred.rb

- "./lib/rubocop/cop/docs_kit/render_component_preferred.rb"
- docs_kit/rubocop
DocsKit/RenderComponentPreferred:
Enabled: true
Include:
- app/**/*.rb
- app/**/*.rb
Exclude:
- lib/rubocop/cop/**/*.rb

- lib/rubocop/cop/**/*.rb
AllCops:
TargetRubyVersion: 3.4
NewCops: enable
SuggestExtensions: false
Exclude:
- "bin/**/*"
- "db/schema.rb"
- "node_modules/**/*"
- "tmp/**/*"
- "vendor/**/*"

#===============
#=== LAYOUT ===
#===============

- bin/**/*
- db/schema.rb
- node_modules/**/*
- tmp/**/*
- vendor/**/*
Layout/BlockAlignment:
EnforcedStyleAlignWith: start_of_block

Layout/LineLength:
Enabled: false

#===============
#=== LINT ===
#===============

Lint/AmbiguousBlockAssociation:
Exclude:
- "spec/**/*"

- spec/**/*
Lint/EmptyBlock:
Exclude:
- "app/views/**/*"

- app/views/**/*
Lint/MissingSuper:
Exclude:
- "app/views/**/*"
- "app/components/**/*"

- app/views/**/*
- app/components/**/*
Lint/UselessConstantScoping:
Enabled: false

#===============
#=== METRICS ===
#===============

Metrics:
Enabled: false

#===============
#=== NAMING ===
#===============

Naming/VariableNumber:
Exclude:
- "app/views/**/*"
- "app/components/**/*"

#===============
#=== RAILS ===
#===============

- app/views/**/*
- app/components/**/*
Rails:
Enabled: true

Rails/Exit:
Exclude:
- "lib/generators/**/*"

- lib/generators/**/*
Rails/FindEach:
Exclude:
- "app/views/**/*"

- app/views/**/*
Rails/FilePath:
EnforcedStyle: arguments

Rails/I18nLocaleTexts:
Enabled: false

Rails/OutputSafety:
Enabled: false

Rails/SkipsModelValidations:
Enabled: false

#===============
#=== RAKE ===
#===============

Rake/Desc:
Exclude:
- "lib/tasks/annotate.rake"
- "lib/tasks/auto_annotate_models.rake"

#===============
#=== STYLE ===
#===============

- lib/tasks/annotate.rake
- lib/tasks/auto_annotate_models.rake
Style/ClassAndModuleChildren:
Enabled: false

Style/Documentation:
Enabled: false

Style/ClassVars:
Exclude:
- "app/models/category.rb"
- "app/models/component.rb"

- app/models/category.rb
- app/models/component.rb
Style/Lambda:
EnforcedStyle: literal

Style/OpenStructUse:
Enabled: false

Style/StringLiterals:
EnforcedStyle: double_quotes

Style/SymbolArray:
EnforcedStyle: brackets

Style/TrailingCommaInHashLiteral:
EnforcedStyleForMultiline: comma

Style/TrailingCommaInArrayLiteral:
EnforcedStyleForMultiline: comma

#=============
#=== RSpec ===
#=============

RSpec/ExampleLength:
Enabled: false

RSpec/MultipleExpectations:
Enabled: false

RSpec/NestedGroups:
Enabled: false
inherit_gem:
docs-kit:
- config/rubocop/docs_kit.yml
100 changes: 100 additions & 0 deletions docs/AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
# AGENTS.md

Guidance for AI coding agents working in this repository. `AGENTS.md` is the
cross-tool convention (Claude Code, Cursor, Copilot, Aider, …); Claude Code also
reads it through the bundled `write-docs-page` skill. Edit freely — a
`docs_kit:install` re-run only touches the delimited block below.

<!-- BEGIN docs-kit -->
## Writing docs pages (docs-kit)

Phlexy ui docs is a [docs-kit](https://github.com/mhenrixon/docs-kit) site: a
Phlex/daisyUI chrome where **every page is a `DocsUI::Page` subclass** and the
sidebar, TOC, search, and Markdown twin come free. To document something, you
scaffold a page, then write its `#content`. Never hand-write HTML or daisyUI
markup — compose the kit's `DocsUI::` helpers.

### 1. Scaffold the page (one command)

```bash
rails g docs_kit:page "Getting Started" --group=Guide
```

That writes `app/views/docs/pages/getting_started.rb` **and** injects
`page "Getting Started", group: "Guide"` into the `Doc` registry — so the page is
routed and in the sidebar the moment you fill in `#content`. Overrides:
`--slug=auth`, `--view=OauthGuide`, `--eyebrow="Advanced"`, `--registry=Guide`.
Re-running is idempotent.

> The registry line is **required** — a page with no `page "…"` line in
> `app/models/doc.rb` is not routed and not in the nav. The generator adds it;
> if you hand-write a page, add the line yourself.

### 2. Write `#content` — Markdown first

Prose is `md` with a **single-quoted** heredoc (`<<~'MD'`) so `#{…}` stays
literal (Phlex escapes author text — never `html_safe` or interpolate):

```ruby
class Views::Docs::Pages::Guide < DocsUI::Page
title "Guide"
eyebrow "Getting started"

def lead = "One sentence under the page title."

def content
DocsUI::Section("First steps", description: "What this covers.") do
md <<~'MD'
Prose as **Markdown** — lists, `inline code`, links, GFM tables, and
fenced ```ruby``` blocks all render styled. Use Markdown `###` only for
sub-headings *inside* a Section.
MD

DocsUI::Code(<<~RUBY, filename: "config/routes.rb")
Rails.application.routes.draw { mount DocsKit::Engine, at: "/docs" }
RUBY
end
end
end
```

### The authoring contract

- **`DocsUI::Section` owns page structure and the "On this page" TOC.** One
Section per part of the page; each heading becomes a TOC entry. **Never** use a
Markdown `##` for page structure — only for sub-headings inside a Section.
- **The primary argument is positional; modifiers are keywords.**
`Section("Title", description:)`, `Code(source, filename:)`,
`Header("Title", eyebrow:)`.
- **Wrappers that take no positional arg use lowercase page helpers** so a block
needs no parens: `md <<~'MD' … MD`, `prose { … }`, `example { |ex| … }`. (A bare
`DocsUI::Prose do` is a Ruby SyntaxError; the helpers sidestep it.)
- **Reference material has dedicated helpers** — reach for these before prose:
`DocsUI::PropTable`, `DocsUI::FieldTable`, `DocsUI::RequestExample`,
`DocsUI::Callout(:note | :tip | :warning)`.

### Invariants — do not break

- **The registry line is required** (see above) — no line, no page.
- **The page must work with JavaScript off.** The server renders it fully;
the one `docs-nav` controller only *enhances*. Never require JS to read a page.
- **Themes offered must exist in the CSS build** — `c.themes` in
`config/initializers/docs_kit.rb` must match the `@plugin "daisyui" { themes: … }`
block in `app/assets/stylesheets/application.tailwind.css`. Don't add one
without the other.
- **No inline `rubocop:disable`** to force layout — write idiomatic Ruby the
site's cops accept.

### 3. Verify before you finish

```bash
bundle exec rspec && bundle exec rubocop # tests + lint must pass
bun run build:css # if you added classes the CSS scans
```

Then render the page locally (`bin/dev`, open `/docs/<slug>`) and confirm it
reads correctly — with JavaScript off, too.

**Depth:** the live [Authoring pages](/docs/authoring) doc is the full,
always-current version of this contract. When in doubt, read it.
<!-- END docs-kit -->
8 changes: 7 additions & 1 deletion docs/Gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
GIT
remote: https://github.com/mhenrixon/docs-kit.git
revision: 73e52fd390a4236f460b984dc4c22b9ba3b2252f
revision: 82d76a761cf21f1380f71eccdbbae49fe8c1ad6f
specs:
docs-kit (0.1.0)
commonmarker (~> 2.0)
daisyui (>= 1.2)
nokogiri (>= 1.15)
phlex-rails (>= 2.0, < 3)
rails_icons (~> 1.1)
rouge (>= 4.0)
Expand Down Expand Up @@ -133,6 +135,10 @@ GEM
addressable
capybara
playwright-ruby-client (>= 1.16.0)
commonmarker (2.8.2-aarch64-linux)
commonmarker (2.8.2-arm64-darwin)
commonmarker (2.8.2-x86_64-darwin)
commonmarker (2.8.2-x86_64-linux)
concurrent-ruby (1.3.7)
connection_pool (3.0.2)
crass (1.0.7)
Expand Down
Loading
Loading