Skip to content

Commit 8d91365

Browse files
committed
feat(docs): add LLM site files
1 parent b56b7d8 commit 8d91365

7 files changed

Lines changed: 446 additions & 2 deletions

File tree

docs/README.md

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,14 @@ gem "ruby_ui", path: "../ruby_ui"
2525

2626
This workflow allows you to iterate quickly on components while maintaining the gem's structure.
2727

28-
Would you like me to expand on any part of the contributing guide?
28+
## Site Files
29+
30+
The docs app serves `/llms.txt`, `/llms-full.txt`, and `/sitemap.xml` through Rails routes backed by `SiteFiles`.
31+
32+
To refresh static copies in `public/`, run:
33+
34+
```bash
35+
bin/rails site_files:generate
36+
```
37+
38+
Set `SITE_FILES_OUTPUT_DIR=tmp/site_files` to generate into a temporary directory instead.
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# frozen_string_literal: true
2+
3+
class SiteFilesController < ApplicationController
4+
def llms
5+
render plain: site_files.llms_txt, content_type: "text/plain; charset=utf-8"
6+
end
7+
8+
def llms_full
9+
render plain: site_files.llms_full_txt, content_type: "text/plain; charset=utf-8"
10+
end
11+
12+
def sitemap
13+
render plain: site_files.sitemap_xml, content_type: "application/xml; charset=utf-8"
14+
end
15+
16+
private
17+
18+
def site_files
19+
@site_files ||= SiteFiles.new
20+
end
21+
end

docs/app/lib/site_files.rb

Lines changed: 340 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,340 @@
1+
# frozen_string_literal: true
2+
3+
require "cgi"
4+
5+
class SiteFiles
6+
DEFAULT_BASE_URL = "https://rubyui.com"
7+
8+
CORE_DOCS = [
9+
{
10+
title: "Introduction",
11+
path: "/docs/introduction",
12+
description: "Overview of RubyUI, its Phlex, Tailwind CSS, and Stimulus foundations, and its design goals.",
13+
priority: 0.9,
14+
changefreq: "weekly"
15+
},
16+
{
17+
title: "Installation",
18+
path: "/docs/installation",
19+
description: "Entry point for choosing a Rails installation path.",
20+
priority: 0.9,
21+
changefreq: "weekly"
22+
},
23+
{
24+
title: "Rails - JS Bundler",
25+
path: "/docs/installation/rails_bundler",
26+
description: "Install RubyUI in a Rails app that uses JavaScript bundling.",
27+
priority: 0.8,
28+
changefreq: "monthly"
29+
},
30+
{
31+
title: "Rails - Importmaps",
32+
path: "/docs/installation/rails_importmaps",
33+
description: "Install RubyUI in a Rails app that uses import maps.",
34+
priority: 0.8,
35+
changefreq: "monthly"
36+
},
37+
{
38+
title: "Theming",
39+
path: "/docs/theming",
40+
description: "Use CSS variables and shadcn/ui-compatible theme tokens with RubyUI.",
41+
priority: 0.8,
42+
changefreq: "monthly"
43+
},
44+
{
45+
title: "Dark mode",
46+
path: "/docs/dark_mode",
47+
description: "Configure dark mode with the Tailwind CSS class strategy and RubyUI theme toggle.",
48+
priority: 0.8,
49+
changefreq: "monthly"
50+
},
51+
{
52+
title: "Customizing components",
53+
path: "/docs/customizing_components",
54+
description: "Adapt generated RubyUI components when theme-level customization is not enough.",
55+
priority: 0.8,
56+
changefreq: "monthly"
57+
},
58+
{
59+
title: "Components",
60+
path: "/docs/components",
61+
description: "Catalog of available RubyUI components.",
62+
priority: 0.9,
63+
changefreq: "weekly"
64+
},
65+
{
66+
title: "Changelog",
67+
path: "/docs/changelog",
68+
description: "Recent RubyUI component and documentation changes.",
69+
priority: 0.6,
70+
changefreq: "weekly"
71+
}
72+
].freeze
73+
74+
COMPONENT_DOCS = [
75+
{title: "Accordion", path: "/docs/accordion", description: "Vertically stacked interactive headings that reveal sections of content."},
76+
{title: "Alert", path: "/docs/alert", description: "Callout component for drawing attention to contextual information."},
77+
{title: "Alert Dialog", path: "/docs/alert_dialog", description: "Modal dialog for interruptive content that expects a response."},
78+
{title: "Aspect Ratio", path: "/docs/aspect_ratio", description: "Container for displaying content within a desired ratio."},
79+
{title: "Avatar", path: "/docs/avatar", description: "Image and fallback primitives for representing a user."},
80+
{title: "Badge", path: "/docs/badge", description: "Small status or label element."},
81+
{title: "Breadcrumb", path: "/docs/breadcrumb", description: "Navigation trail showing the current location in a hierarchy."},
82+
{title: "Button", path: "/docs/button", description: "Button component and button-like variants."},
83+
{title: "Calendar", path: "/docs/calendar", description: "Date field component for entering and editing dates."},
84+
{title: "Card", path: "/docs/card", description: "Content container with header, content, and footer primitives."},
85+
{title: "Carousel", path: "/docs/carousel", description: "Embla-powered carousel with motion and swipe interactions."},
86+
{title: "Checkbox", path: "/docs/checkbox", description: "Control for toggling between checked and unchecked states."},
87+
{title: "Checkbox Group", path: "/docs/checkbox_group", description: "Grouped checkbox controls."},
88+
{title: "Clipboard", path: "/docs/clipboard", description: "Control for copying content to the clipboard."},
89+
{title: "Codeblock", path: "/docs/codeblock", description: "Highlighted code display component."},
90+
{title: "Collapsible", path: "/docs/collapsible", description: "Interactive component for expanding and collapsing a panel."},
91+
{title: "Combobox", path: "/docs/combobox", description: "Autocomplete input and command palette with suggestions."},
92+
{title: "Command", path: "/docs/command", description: "Composable command menu for Phlex applications."},
93+
{title: "Context Menu", path: "/docs/context_menu", description: "Right-click menu for contextual actions."},
94+
{title: "Data Table", path: "/docs/data_table", description: "Data table primitives for search, sorting, pagination, visibility, and bulk actions."},
95+
{title: "Date Picker", path: "/docs/date_picker", description: "Date picker component with input."},
96+
{title: "Dialog", path: "/docs/dialog", description: "Modal window that renders background content inert."},
97+
{title: "Dropdown Menu", path: "/docs/dropdown_menu", description: "Button-triggered menu for actions or functions."},
98+
{title: "Form", path: "/docs/form", description: "Form fields with built-in client-side validations."},
99+
{title: "Hover Card", path: "/docs/hover_card", description: "Preview content exposed behind a link or trigger."},
100+
{title: "Input", path: "/docs/input", description: "Styled input field primitive."},
101+
{title: "Link", path: "/docs/link", description: "Link component with button-like and underline variants."},
102+
{title: "Masked Input", path: "/docs/masked_input", description: "Form input with an applied mask."},
103+
{title: "Pagination", path: "/docs/pagination", description: "Page navigation with next and previous links."},
104+
{title: "Popover", path: "/docs/popover", description: "Triggered rich content panel."},
105+
{title: "Progress", path: "/docs/progress", description: "Progress bar for task completion state."},
106+
{title: "Radio Button", path: "/docs/radio_button", description: "Single-selection control for option lists."},
107+
{title: "Native Select", path: "/docs/native_select", description: "Styled native HTML select element."},
108+
{title: "Select", path: "/docs/select", description: "Button-triggered option picker."},
109+
{title: "Separator", path: "/docs/separator", description: "Visual or semantic divider."},
110+
{title: "Sheet", path: "/docs/sheet", description: "Side panel for content that complements the main screen."},
111+
{title: "Shortcut Key", path: "/docs/shortcut_key", description: "Keyboard shortcut display component."},
112+
{title: "Sidebar", path: "/docs/sidebar", description: "Composable, themeable sidebar component."},
113+
{title: "Skeleton", path: "/docs/skeleton", description: "Placeholder for loading states."},
114+
{title: "Switch", path: "/docs/switch", description: "Toggle control for binary settings."},
115+
{title: "Table", path: "/docs/table", description: "Responsive table component."},
116+
{title: "Tabs", path: "/docs/tabs", description: "Layered tab panels displayed one at a time."},
117+
{title: "Textarea", path: "/docs/textarea", description: "Styled multiline text input."},
118+
{title: "Theme Toggle", path: "/docs/theme_toggle", description: "Toggle control for switching between light and dark themes."},
119+
{title: "Tooltip", path: "/docs/tooltip", description: "Popup information shown on keyboard focus or hover."},
120+
{title: "Typography", path: "/docs/typography", description: "Text primitives and sensible typography defaults."}
121+
].freeze
122+
123+
EXAMPLE_DOCS = [
124+
{
125+
title: "Data Table Demo",
126+
path: "/docs/data_table_demo",
127+
description: "Interactive data table example using RubyUI table primitives.",
128+
priority: 0.5,
129+
changefreq: "monthly"
130+
},
131+
{
132+
title: "Sidebar Example",
133+
path: "/docs/sidebar/example",
134+
description: "Standalone sidebar example page.",
135+
priority: 0.5,
136+
changefreq: "monthly"
137+
},
138+
{
139+
title: "Sidebar Inset Example",
140+
path: "/docs/sidebar/inset",
141+
description: "Sidebar inset layout example page.",
142+
priority: 0.5,
143+
changefreq: "monthly"
144+
}
145+
].freeze
146+
147+
SITE_PAGES = [
148+
{
149+
title: "RubyUI",
150+
path: "/",
151+
description: "Home page for RubyUI, a UI component library for Ruby developers.",
152+
priority: 1.0,
153+
changefreq: "weekly"
154+
},
155+
{
156+
title: "Themes",
157+
path: "/themes/default",
158+
description: "Theme preview and CSS variable copy tool.",
159+
priority: 0.7,
160+
changefreq: "monthly"
161+
}
162+
].freeze
163+
164+
PROJECT_RESOURCES = [
165+
{
166+
title: "GitHub repository",
167+
url: "https://github.com/ruby-ui/ruby_ui",
168+
description: "Source code for the RubyUI gem and documentation app."
169+
},
170+
{
171+
title: "RubyGems package",
172+
url: "https://rubygems.org/gems/ruby_ui",
173+
description: "Published ruby_ui gem package."
174+
}
175+
].freeze
176+
177+
def initialize(base_url: DEFAULT_BASE_URL)
178+
@base_url = base_url.delete_suffix("/")
179+
end
180+
181+
def llms_txt
182+
lines = [
183+
"# RubyUI",
184+
"",
185+
"> Beautifully designed, accessible, customizable UI components for Ruby and Rails apps, built with Phlex, Tailwind CSS, and Stimulus.",
186+
"",
187+
"RubyUI is a development-time gem for generating or copying reusable Ruby UI components into an application. The generated code belongs to the host app and can be customized directly. Documentation pages usually contain live examples, code snippets, setup instructions, and source file references.",
188+
"",
189+
"Use the core docs first for installation, theming, dark mode, and customization context. Use component docs when you need exact component APIs, examples, and related source files. Use llms-full.txt when a single expanded reference is more useful than a curated link map.",
190+
"",
191+
"## Core docs",
192+
"",
193+
*markdown_links(CORE_DOCS),
194+
"",
195+
"## Component docs",
196+
"",
197+
*markdown_links(COMPONENT_DOCS),
198+
"",
199+
"## Examples",
200+
"",
201+
*markdown_links(EXAMPLE_DOCS),
202+
"",
203+
"## Project resources",
204+
"",
205+
*resource_links,
206+
"",
207+
"## Optional",
208+
"",
209+
"- [Full LLM reference](#{absolute_url("/llms-full.txt")}): Expanded single-file overview of RubyUI installation, conventions, and component catalog.",
210+
"- [Sitemap](#{absolute_url("/sitemap.xml")}): XML sitemap for public RubyUI pages."
211+
]
212+
213+
"#{lines.join("\n")}\n"
214+
end
215+
216+
def llms_full_txt
217+
lines = [
218+
"# RubyUI Full LLM Reference",
219+
"",
220+
"> RubyUI provides accessible, customizable UI components for Ruby and Rails applications. It is built on Phlex for Ruby-rendered views, Tailwind CSS for styling, and Stimulus for small client-side behaviors.",
221+
"",
222+
"This file expands the curated /llms.txt map into a compact reference that can be loaded as one document. It is intentionally prose-heavy and link-rich so language models can answer common questions without crawling the whole documentation site.",
223+
"",
224+
"## Product model",
225+
"",
226+
"- RubyUI is distributed as the `ruby_ui` gem.",
227+
"- RubyUI is intended primarily for development-time generation and copy-paste workflows.",
228+
"- Components are Ruby classes that render HTML through Phlex.",
229+
"- Styling uses Tailwind CSS utilities and CSS variables.",
230+
"- Interactive components use lightweight Stimulus controllers.",
231+
"- The design language is inspired by shadcn/ui and keeps theme tokens compatible with shadcn/ui-style CSS variables.",
232+
"",
233+
"## Common installation workflow",
234+
"",
235+
"1. Add the gem to a Rails application with `bundle add ruby_ui --group development --require false`.",
236+
"2. Run `bin/rails g ruby_ui:install` to install RubyUI support files.",
237+
"3. Generate a component with `bin/rails g ruby_ui:component ComponentName`, for example `bin/rails g ruby_ui:component Button`.",
238+
"4. Customize generated Ruby, Tailwind classes, and Stimulus controllers inside the host app as needed.",
239+
"",
240+
"## Important implementation notes",
241+
"",
242+
"- Treat generated components as application code, not a sealed runtime dependency.",
243+
"- Prefer the docs installation path matching the Rails JavaScript setup: JS bundler or import maps.",
244+
"- Use the theming docs for CSS variable setup before making broad visual changes.",
245+
"- Use the dark mode docs when adding or moving `ThemeToggle`.",
246+
"- Component documentation pages usually end with setup tabs and a table of component files.",
247+
"",
248+
"## Core documentation",
249+
"",
250+
*expanded_pages(CORE_DOCS),
251+
"",
252+
"## Component catalog",
253+
"",
254+
*expanded_pages(COMPONENT_DOCS),
255+
"",
256+
"## Examples and demos",
257+
"",
258+
*expanded_pages(EXAMPLE_DOCS),
259+
"",
260+
"## Themes",
261+
"",
262+
"- [Themes](#{absolute_url("/themes/default")}): Preview and copy hand-picked themes that are compatible with RubyUI and shadcn/ui token conventions.",
263+
"",
264+
"## External resources",
265+
"",
266+
*resource_links,
267+
"",
268+
"## Companion machine-readable files",
269+
"",
270+
"- [llms.txt](#{absolute_url("/llms.txt")}): Curated LLM link map.",
271+
"- [sitemap.xml](#{absolute_url("/sitemap.xml")}): XML sitemap for public pages."
272+
]
273+
274+
"#{lines.join("\n")}\n"
275+
end
276+
277+
def sitemap_xml
278+
lines = [
279+
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>",
280+
"<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">"
281+
]
282+
283+
sitemap_pages.each do |page|
284+
lines << " <url>"
285+
lines << " <loc>#{xml_escape(absolute_url(page.fetch(:path)))}</loc>"
286+
lines << " <changefreq>#{xml_escape(page.fetch(:changefreq))}</changefreq>"
287+
lines << " <priority>#{format("%.1f", page.fetch(:priority))}</priority>"
288+
lines << " </url>"
289+
end
290+
291+
lines << "</urlset>"
292+
"#{lines.join("\n")}\n"
293+
end
294+
295+
private
296+
297+
def sitemap_pages
298+
SITE_PAGES + CORE_DOCS + component_sitemap_pages + EXAMPLE_DOCS
299+
end
300+
301+
def component_sitemap_pages
302+
COMPONENT_DOCS.map do |page|
303+
page.merge(priority: 0.7, changefreq: "monthly")
304+
end
305+
end
306+
307+
def markdown_links(pages)
308+
pages.map do |page|
309+
"- [#{page.fetch(:title)}](#{absolute_url(page.fetch(:path))}): #{page.fetch(:description)}"
310+
end
311+
end
312+
313+
def expanded_pages(pages)
314+
pages.flat_map do |page|
315+
[
316+
"### #{page.fetch(:title)}",
317+
"",
318+
"- URL: #{absolute_url(page.fetch(:path))}",
319+
"- Summary: #{page.fetch(:description)}",
320+
""
321+
]
322+
end
323+
end
324+
325+
def resource_links
326+
PROJECT_RESOURCES.map do |resource|
327+
"- [#{resource.fetch(:title)}](#{resource.fetch(:url)}): #{resource.fetch(:description)}"
328+
end
329+
end
330+
331+
def xml_escape(value)
332+
CGI.escapeHTML(value.to_s)
333+
end
334+
335+
def absolute_url(path)
336+
return "#{@base_url}/" if path == "/"
337+
338+
"#{@base_url}#{path}"
339+
end
340+
end

docs/config/routes.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
11
Rails.application.routes.draw do
2+
get "llms.txt", to: "site_files#llms", as: :llms_txt, format: false
3+
get "llms-full.txt", to: "site_files#llms_full", as: :llms_full_txt, format: false
4+
get "sitemap.xml", to: "site_files#sitemap", as: :sitemap_xml, format: false
5+
26
get "themes/:theme", to: "themes#show", as: :theme
37

48
scope "docs" do

0 commit comments

Comments
 (0)