ui_guardrails 1.7.0 → 2.0.0.rc1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +254 -46
- data/doc/A11Y.md +4 -4
- data/doc/PRD.md +7 -7
- data/doc/PUBLISHING.md +4 -0
- data/doc/ROADMAP.md +30 -10
- data/doc/VISUAL-DIFF.md +8 -8
- data/exe/guardrails-hook +1 -1
- data/exe/ui_guardrails-mcp +32 -0
- data/lib/guardrails/a11y_deep.rb +1 -1
- data/lib/guardrails/audit/markdown_writer.rb +5 -0
- data/lib/guardrails/audit.rb +129 -6
- data/lib/guardrails/class_itis.rb +2 -45
- data/lib/guardrails/erb_parser.rb +53 -0
- data/lib/guardrails/feed_ramp/agent_rules.rb +309 -0
- data/lib/guardrails/feed_ramp/components_markdown.rb +135 -0
- data/lib/guardrails/feed_ramp/config.rb +113 -0
- data/lib/guardrails/feed_ramp/design_tokens.rb +112 -0
- data/lib/guardrails/feed_ramp/discovery.rb +201 -0
- data/lib/guardrails/feed_ramp/libraries.rb +27 -0
- data/lib/guardrails/feed_ramp/manifest.rb +142 -0
- data/lib/guardrails/feed_ramp/mcp.rb +325 -0
- data/lib/guardrails/feed_ramp/mcp_install.rb +150 -0
- data/lib/guardrails/feed_ramp/modern_web.rb +83 -0
- data/lib/guardrails/feed_ramp/rails_config.rb +144 -0
- data/lib/guardrails/feed_ramp/root_element.rb +361 -0
- data/lib/guardrails/feed_ramp/ruby_source.rb +230 -0
- data/lib/guardrails/feed_ramp/score.rb +256 -0
- data/lib/guardrails/feed_ramp/snippet_check.rb +221 -0
- data/lib/guardrails/feed_ramp/usages.rb +174 -0
- data/lib/guardrails/feed_ramp.rb +27 -0
- data/lib/guardrails/hooks/config_file.rb +28 -2
- data/lib/guardrails/hooks/installer.rb +5 -2
- data/lib/guardrails/hooks.rb +24 -1
- data/lib/guardrails/init/config_writer.rb +21 -4
- data/lib/guardrails/init/prompter.rb +1 -1
- data/lib/guardrails/railtie.rb +2 -0
- data/lib/guardrails/report/detectors.rb +1 -1
- data/lib/guardrails/report/run.rb +6 -5
- data/lib/guardrails/tokens.rb +9 -9
- data/lib/guardrails/tui.rb +2 -2
- data/lib/guardrails/version.rb +1 -1
- data/lib/tasks/guardrails.rake +16 -156
- data/lib/tasks/ui_guardrails.rake +304 -0
- metadata +21 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c33f6b8ca0162e78f0c45e98b72b6474e0e4d2f8f1be918240fd33e08951233c
|
|
4
|
+
data.tar.gz: 2a08781acbc344a263564b64a825e18fd798537d81bf615e560a54c79575eb20
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9bf82903a9e3d5401dabc2bfe23e13cbf23392eaf167af810b0c937fbae4f0209a75fa7d0d16fac868d24c864ae94dc61968811851efa0a7e613ff918195c8c5
|
|
7
|
+
data.tar.gz: fad3769d0a40a69377dd5a1d5c31e473837f6717c0e174709ede199dcaff3c7c4e0260826c35c26a752593d7a8f2989dc90a22c75990d4e31ed9e137ab9aa921
|
data/README.md
CHANGED
|
@@ -4,7 +4,7 @@ A Rails toolset that prevents UI drift in AI-assisted applications. Static audit
|
|
|
4
4
|
|
|
5
5
|
Built and maintained by [Meticulous](https://meticulous.com).
|
|
6
6
|
|
|
7
|
-
**Current release:**
|
|
7
|
+
**Current release:** 2.0.0, which adds [Feed Ramp](#feed-ramp): your component library handed to coding agents before they write markup, not just drift caught after. Published on [RubyGems.org as `ui_guardrails`](https://rubygems.org/gems/ui_guardrails). The Ruby module stays `Guardrails` (so `require "guardrails"` is unchanged) — only the gem package name on rubygems carries the `ui_` prefix, to clear RubyGems' similarity rule against the unrelated [`guard-rails`](https://rubygems.org/gems/guard-rails) gem. See [`doc/ROADMAP.md`](doc/ROADMAP.md) for status, [`CHANGELOG.md`](CHANGELOG.md) for the full naming rationale.
|
|
8
8
|
|
|
9
9
|
---
|
|
10
10
|
|
|
@@ -33,14 +33,14 @@ Guardrails works two ways: **installed in your project** (auto-loaded via a Rail
|
|
|
33
33
|
```ruby
|
|
34
34
|
# Gemfile
|
|
35
35
|
group :development, :test do
|
|
36
|
-
gem "ui_guardrails", "~>
|
|
36
|
+
gem "ui_guardrails", "~> 2.0"
|
|
37
37
|
end
|
|
38
38
|
```
|
|
39
39
|
|
|
40
40
|
```bash
|
|
41
41
|
bundle install
|
|
42
|
-
bundle exec rake
|
|
43
|
-
bundle exec rake
|
|
42
|
+
bundle exec rake ui_guardrails:init # writes guardrails.yml, scaffolds MQs
|
|
43
|
+
bundle exec rake ui_guardrails:audit # run the full audit
|
|
44
44
|
```
|
|
45
45
|
|
|
46
46
|
The gem's Railtie auto-loads rake tasks and (when [Lookbook](https://lookbook.build) is also in your Gemfile) registers the **Guardrails** panel inside every preview's inspector.
|
|
@@ -56,8 +56,8 @@ cd ~/code/guardrails && bundle install
|
|
|
56
56
|
# From your Rails app's root:
|
|
57
57
|
cd ~/code/my-rails-app
|
|
58
58
|
bundle exec --gemfile=~/code/guardrails/Gemfile \
|
|
59
|
-
rake -f ~/code/guardrails/lib/tasks/
|
|
60
|
-
|
|
59
|
+
rake -f ~/code/guardrails/lib/tasks/ui_guardrails.rake \
|
|
60
|
+
ui_guardrails:audit
|
|
61
61
|
```
|
|
62
62
|
|
|
63
63
|
Every rake task accepts the same env vars in sidecar mode as in-project. The tasks default `root` to `Rails.root` when Rails is loaded, else `Dir.pwd` — so just run from the target app's root.
|
|
@@ -66,22 +66,58 @@ Every rake task accepts the same env vars in sidecar mode as in-project. The tas
|
|
|
66
66
|
|
|
67
67
|
## Quick start
|
|
68
68
|
|
|
69
|
+
### Feed Ramp in 60 seconds
|
|
70
|
+
|
|
71
|
+
Give your coding agents the components you already have:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
bundle add ui_guardrails --group "development, test"
|
|
75
|
+
|
|
76
|
+
# 1. Component manifest + agent rules: writes .ui_guardrails/manifest.json and
|
|
77
|
+
# COMPONENTS.md, and a managed block in CLAUDE.md and AGENTS.md plus a Cursor rule
|
|
78
|
+
bundle exec rake ui_guardrails:feed_ramp:rules
|
|
79
|
+
|
|
80
|
+
# 2. Optional: the MCP server (search_components, get_component, check_snippet)
|
|
81
|
+
bundle exec rake ui_guardrails:feed_ramp:mcp:install # AGENT=all adds Codex, Cursor and Gemini CLI
|
|
82
|
+
|
|
83
|
+
# 3. Optional: the Modern Web Guidance skill (Google Chrome and contributors), so
|
|
84
|
+
# agents also look up current HTML, CSS and JS practice. Run step 1 again after it
|
|
85
|
+
bundle exec rake ui_guardrails:feed_ramp:modern_web:install
|
|
86
|
+
|
|
87
|
+
# 4. Commit it, so every clone and every agent session has it
|
|
88
|
+
# (and .mcp.json if you ran step 2, the skill's files if you ran step 3)
|
|
89
|
+
git add .ui_guardrails CLAUDE.md AGENTS.md .cursor/rules
|
|
90
|
+
|
|
91
|
+
# 5. In CI: fail when the manifest or rules are out of date, and on drift,
|
|
92
|
+
# including hand-written markup that copies a component (component_match)
|
|
93
|
+
CHECK=1 bundle exec rake ui_guardrails:feed_ramp:rules
|
|
94
|
+
bundle exec rake ui_guardrails:audit
|
|
95
|
+
|
|
96
|
+
# 6. Measure it: score now, again after an agent session, and compare
|
|
97
|
+
bundle exec rake ui_guardrails:feed_ramp:score
|
|
98
|
+
bundle exec rake ui_guardrails:feed_ramp:score BASELINE=last
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Nothing boots your app, and ViewComponent, Phlex and Lookbook are all optional. `component_match` is a warning, so it fails the audit; set `feed_ramp.component_match: suggestion` in `guardrails.yml` to see what it catches before it gates builds. Details are in [Feed Ramp](#feed-ramp).
|
|
102
|
+
|
|
103
|
+
### The audit
|
|
104
|
+
|
|
69
105
|
```bash
|
|
70
106
|
# One-time setup — detects your token stack, writes guardrails.yml,
|
|
71
107
|
# scaffolds prefers-color-scheme media queries if your stylesheet
|
|
72
108
|
# doesn't already have them. Interactive when on a TTY; CI-safe
|
|
73
109
|
# default fallback otherwise.
|
|
74
|
-
bundle exec rake
|
|
110
|
+
bundle exec rake ui_guardrails:init
|
|
75
111
|
|
|
76
112
|
# Full audit — exits 1 on any violation.
|
|
77
|
-
bundle exec rake
|
|
113
|
+
bundle exec rake ui_guardrails:audit
|
|
78
114
|
|
|
79
115
|
# Get a markdown checklist of fixable findings:
|
|
80
|
-
SUGGEST=1 bundle exec rake
|
|
116
|
+
SUGGEST=1 bundle exec rake ui_guardrails:audit
|
|
81
117
|
# → writes doc/guardrails-suggestions-{TIMESTAMP}.md
|
|
82
118
|
|
|
83
119
|
# Auto-fix raw_color and tailwind_arbitrary where a token matches:
|
|
84
|
-
APPLY=1 bundle exec rake
|
|
120
|
+
APPLY=1 bundle exec rake ui_guardrails:audit
|
|
85
121
|
```
|
|
86
122
|
|
|
87
123
|
---
|
|
@@ -90,20 +126,27 @@ APPLY=1 bundle exec rake guardrails:audit
|
|
|
90
126
|
|
|
91
127
|
| Task | What it does |
|
|
92
128
|
|---|---|
|
|
93
|
-
| `
|
|
94
|
-
| `
|
|
95
|
-
| `
|
|
96
|
-
| `
|
|
97
|
-
| `
|
|
98
|
-
| `
|
|
99
|
-
| `
|
|
100
|
-
| `
|
|
129
|
+
| `ui_guardrails:init` | Stack detection, writes `guardrails.yml`, scaffolds prefers-color-scheme / prefers-contrast media queries. Refuses to overwrite an existing config — `FORCE=1` overrides. |
|
|
130
|
+
| `ui_guardrails:audit` | Runs every detector — view drift, stimulus, partial similarity, view-components, a11y, cross-codebase patterns, class-itis. Exits 1 on violations. |
|
|
131
|
+
| `ui_guardrails:tui` | The same audit, browsable: a severity roll-up you drill into (category → finding → source), regroup by file, filter, and jump from into your editor or the HTML report. See [Browsing findings](#browsing-findings). |
|
|
132
|
+
| `ui_guardrails:icons` | Generates an SVG sprite from `app/assets/images/icons/`, flags inline `<svg>` in views, catches emoji / glyph icons across views + helpers + models + JS + locales, reports unused sprite entries. **Exits 1** on any inline SVG, unknown sprite reference, or emoji/glyph icon (1.5.0). Dead icons stay reporting-only. `SUGGEST=1` prints sprite-name suggestions; `FORMAT=json` emits the machine-readable payload. |
|
|
133
|
+
| `ui_guardrails:tokens` | Parses your color and type-scale tokens (CSS vars / SCSS vars / Tailwind v3 config / Tailwind v4 `@theme`), reports hex literals in stylesheets that should reference a token. |
|
|
134
|
+
| `ui_guardrails:a11y:deep` | Reads axe-core JSON output and folds it into the unified report. Doesn't run axe itself (no Capybara / headless Chrome runtime deps) — point it at axe output your existing tooling produces. |
|
|
135
|
+
| `ui_guardrails:visual:deep` | Consumes screenshot-diff tool output (snap_diff-capybara today; BackstopJS in flight, [#15](https://github.com/meticulous/guardrails/issues/15)) and reports visual regressions. Same parse-only design — your existing test toolchain runs screenshots; Guardrails reports. |
|
|
136
|
+
| `ui_guardrails:hooks:install` | Adds Guardrails to coding agents' hooks (Claude Code, Codex, Cursor, Gemini CLI), so drift an agent introduces goes back to it while it works. See [Coding agent hooks](#coding-agent-hooks). |
|
|
137
|
+
| `ui_guardrails:feed_ramp:manifest` | Writes `.ui_guardrails/manifest.json` and `COMPONENTS.md`: the app's components and tokens, for coding agents. `CHECK=1` exits 1 when they're out of date. See [Feed Ramp](#feed-ramp). |
|
|
138
|
+
| `ui_guardrails:feed_ramp:score` | Scores UI drift per 100 templates (hardcoded values, reinvented components, a11y) and saves it under `.ui_guardrails/scores/`. `BASELINE=last` or a path compares two runs; `FORMAT=json`. Never fails. See [Drift score](#drift-score). |
|
|
139
|
+
| `ui_guardrails:feed_ramp:mcp:install` | Adds the Feed Ramp MCP server to `.mcp.json` (Claude Code); `AGENT=codex,cursor,gemini` or `all` for the others. See [MCP server](#mcp-server). |
|
|
140
|
+
| `ui_guardrails:feed_ramp:modern_web:install` | Installs the [Modern Web Guidance](https://developer.chrome.com/docs/modern-web-guidance) skill with its own installer (needs Node.js 22+). The agent rules mention it once it's in the project. See [Modern Web Guidance](#modern-web-guidance). |
|
|
141
|
+
| `ui_guardrails:feed_ramp:rules` | Writes the manifest, then a block pointing coding agents at it in `CLAUDE.md` and `AGENTS.md`, plus `.cursor/rules/ui-guardrails.mdc`. `TARGETS=` picks files, `CHECK=1` exits 1 when anything is out of date, `REMOVE=1` takes the blocks out. See [Feed Ramp](#feed-ramp). |
|
|
142
|
+
|
|
143
|
+
Before 2.0 the tasks were `guardrails:*`. Those names still run the `ui_guardrails:` task, with a deprecation notice, until 3.0.
|
|
101
144
|
|
|
102
145
|
---
|
|
103
146
|
|
|
104
147
|
## Detectors
|
|
105
148
|
|
|
106
|
-
### View-level drift (`
|
|
149
|
+
### View-level drift (`ui_guardrails:audit`)
|
|
107
150
|
|
|
108
151
|
Static AST walk over every `.html.erb` under `app/views/` and `app/components/` (via [Herb](https://github.com/marcoroth/herb), the ERB-aware parser):
|
|
109
152
|
|
|
@@ -113,12 +156,13 @@ Static AST walk over every `.html.erb` under `app/views/` and `app/components/`
|
|
|
113
156
|
| `raw_color` | Hex / rgb literals in color-bearing attributes (`fill`, `stroke`, `color`, `bgcolor`, `background`, `data-*color*`, etc.). |
|
|
114
157
|
| `tailwind_arbitrary` | `bg-[#fa3]`, `text-[14px]`, `p-[7px]` — arbitrary values that bypass the theme. |
|
|
115
158
|
| `helper_recommended` | `<button>` / `<a>` wrapping `<%= … %>` ERB output. Suggests `tag.button` / `link_to` / `button_to` for clean accessible names. Skips elements that already have `aria-label`. |
|
|
159
|
+
| `component_match` | Hand-written markup copying one of your components: its signature class (`class="button …"` for ButtonComponent) on any tag, or its root tag with most of its classes. Suggests the component's real usage example. Needs a [Feed Ramp](#component-match) manifest; warning by default. |
|
|
116
160
|
| `image_alt` / `button_name` / `link_name` / `input_label` | The four most common static a11y misses (see [`doc/A11Y.md`](doc/A11Y.md)). |
|
|
117
161
|
|
|
118
162
|
### Suggest mode and auto-fix
|
|
119
163
|
|
|
120
|
-
- `SUGGEST=1 rake
|
|
121
|
-
- `APPLY=1 rake
|
|
164
|
+
- `SUGGEST=1 rake ui_guardrails:audit` writes `doc/guardrails-suggestions-{TIMESTAMP}.md` — a markdown checklist with one `[ ]` per fixable finding, the rule that fired, and the proposed replacement (closest token by exact match, else near-match within a configurable channel-distance).
|
|
165
|
+
- `APPLY=1 rake ui_guardrails:audit` auto-fixes `raw_color` (→ `var(--token)`) and `tailwind_arbitrary` (→ Tailwind theme color shorthand) when a matching token exists. Near-match auto-fix is gated by `near_match_policy` in `guardrails.yml` (`fix` / `leave` / `notify`).
|
|
122
166
|
|
|
123
167
|
### Tokens
|
|
124
168
|
|
|
@@ -167,10 +211,10 @@ Then scans every other stylesheet for hex literals and reports drift, matching e
|
|
|
167
211
|
bundle exec rspec spec/system/
|
|
168
212
|
|
|
169
213
|
# Fold visual-diff findings into the audit:
|
|
170
|
-
VISUAL_DIFF=1 bundle exec rake
|
|
214
|
+
VISUAL_DIFF=1 bundle exec rake ui_guardrails:audit
|
|
171
215
|
```
|
|
172
216
|
|
|
173
|
-
Or standalone via `rake
|
|
217
|
+
Or standalone via `rake ui_guardrails:visual:deep`. Parse-only — same trade as deep a11y, no Capybara/Chromium runtime deps in the gem. BackstopJS adapter tracked in [#15](https://github.com/meticulous/guardrails/issues/15). See [`doc/VISUAL-DIFF.md`](doc/VISUAL-DIFF.md).
|
|
174
218
|
|
|
175
219
|
### Deep a11y via axe-core JSON (0.6.0)
|
|
176
220
|
|
|
@@ -178,10 +222,10 @@ Or standalone via `rake guardrails:visual:deep`. Parse-only — same trade as de
|
|
|
178
222
|
|
|
179
223
|
```bash
|
|
180
224
|
npx @axe-core/cli http://localhost:3000/ --save axe.json
|
|
181
|
-
AXE_JSON=axe.json bundle exec rake
|
|
225
|
+
AXE_JSON=axe.json bundle exec rake ui_guardrails:audit
|
|
182
226
|
```
|
|
183
227
|
|
|
184
|
-
Or standalone via `rake
|
|
228
|
+
Or standalone via `rake ui_guardrails:a11y:deep`. Stays parse-only — your existing test toolchain runs axe (axe-core-rspec, the CLI, Puppeteer, a CDP script — anything that emits axe v4 JSON), Guardrails provides the merge + report. See [`doc/A11Y.md`](doc/A11Y.md).
|
|
185
229
|
|
|
186
230
|
### Lookbook auto-panel (0.5.0)
|
|
187
231
|
|
|
@@ -189,7 +233,7 @@ When [Lookbook](https://lookbook.build) is in the Gemfile, Guardrails auto-regis
|
|
|
189
233
|
|
|
190
234
|
### Emoji and glyph icons (1.5.0)
|
|
191
235
|
|
|
192
|
-
The `
|
|
236
|
+
The `ui_guardrails:icons` task now catches emoji and Unicode-dingbat glyphs used as UI iconography — the most common way AI-assisted code bypasses the sprite. Two tiers, both on by default:
|
|
193
237
|
|
|
194
238
|
| Tier | What matches | Example |
|
|
195
239
|
|---|---|---|
|
|
@@ -200,7 +244,7 @@ Grapheme-cluster iteration counts VS16-styled pictographs / ZWJ families / flag
|
|
|
200
244
|
|
|
201
245
|
**Scans**, by default: `app/views`, `app/components`, `app/helpers`, `app/models`, `app/presenters`, `app/javascript`, and `config/locales` — where the emoji-as-icon pattern usually lives (a helper returning `"📄"` per content type, a Stimulus controller setting `textContent = "✓"`). Comments are masked per file type: Prism-parsed for `.rb`, `<%# %>` for ERB, `//` and `/* */` for JS/SCSS/CSS, `#` for YAML. Line and column stay accurate.
|
|
202
246
|
|
|
203
|
-
**Config** in `guardrails.yml` (defaults shown; `
|
|
247
|
+
**Config** in `guardrails.yml` (defaults shown; `ui_guardrails:init` writes this block for you):
|
|
204
248
|
|
|
205
249
|
```yaml
|
|
206
250
|
guardrails:
|
|
@@ -227,11 +271,11 @@ Works on the same line or the preceding line, in Ruby `#` comments, ERB `<%# %>`
|
|
|
227
271
|
|
|
228
272
|
**Suggestions** with `SUGGEST=1`: when the consumer's sprite contains a conventional name for the flagged emoji (`📄 → check for icon-file or icon-document`, `✓ → icon-check` / `icon-checkmark`), the task prints `→ use <use href="#icon-document"/>`. When no equivalent exists, it prints `→ no sprite equivalent — add one`. Only names the consumer's `collect_icon_names` includes are surfaced. No auto-fix path (`APPLY=1`) — the right sprite name depends on the consumer's semantics.
|
|
229
273
|
|
|
230
|
-
**No-fix policy**: emoji contribute to `
|
|
274
|
+
**No-fix policy**: emoji contribute to `ui_guardrails:icons` exit code (part of Result#violations?). Dead icons stay reporting-only — deleting a file is a human decision.
|
|
231
275
|
|
|
232
276
|
### Detectors from other gems
|
|
233
277
|
|
|
234
|
-
Other gems add detectors to `
|
|
278
|
+
Other gems add detectors to `ui_guardrails:audit` with `Guardrails::Report::Detectors.register(name, klass)`. They run after the built-ins and show up in the text report, the summary, `FORMAT=json` (under `extensions.<name>`), `FORMAT=html` and the TUI. The class answers:
|
|
235
279
|
|
|
236
280
|
| Method | Does |
|
|
237
281
|
| --- | --- |
|
|
@@ -253,7 +297,7 @@ Registration checks the class has all of these. Detectors only read what's alrea
|
|
|
253
297
|
Every task prints a human-readable text report by default and exits 1 when violations or failing findings exist. For machine consumption:
|
|
254
298
|
|
|
255
299
|
```bash
|
|
256
|
-
FORMAT=json bundle exec rake
|
|
300
|
+
FORMAT=json bundle exec rake ui_guardrails:audit > findings.json
|
|
257
301
|
```
|
|
258
302
|
|
|
259
303
|
The JSON payload has a `summary:` block with finding counts per category plus per-detector arrays — see `Guardrails::Report::Run#to_h` for the exact shape.
|
|
@@ -263,11 +307,11 @@ The JSON payload has a `summary:` block with finding counts per category plus pe
|
|
|
263
307
|
On a real codebase the text report is long (one of our test apps produces 981 findings). Two ways to navigate it instead of scrolling it:
|
|
264
308
|
|
|
265
309
|
```bash
|
|
266
|
-
bundle exec rake
|
|
267
|
-
FORMAT=html bundle exec rake
|
|
310
|
+
bundle exec rake ui_guardrails:tui # interactive, in the terminal
|
|
311
|
+
FORMAT=html bundle exec rake ui_guardrails:audit # one self-contained HTML file
|
|
268
312
|
```
|
|
269
313
|
|
|
270
|
-
**`
|
|
314
|
+
**`ui_guardrails:tui`** opens on the same severity roll-up the text report leads with. `⏎` drills in — category → findings → a single finding with its suggestion, every location, and the source around it. `tab` regroups the whole thing by file (the files with the most findings first), `/` filters across category, title, suggestion, and path, `o` opens the highlighted location in your editor at the right line, `w` writes and opens the HTML report, `r` re-runs the audit, `?` lists the rest. It takes the same env vars as `ui_guardrails:audit` except `APPLY` and `SUGGEST` — browsing never rewrites files. No new dependencies: it's built on Ruby's bundled `io/console`.
|
|
271
315
|
|
|
272
316
|
The editor is `$GUARDRAILS_EDITOR`, then `$VISUAL`, then `$EDITOR`. VS Code-family, Zed, Sublime, TextMate, JetBrains, Helix, and vim/nvim/nano/emacs all open at the finding's line; anything else gets the file path. With no editor set, it falls back to `open` / `xdg-open`.
|
|
273
317
|
|
|
@@ -282,10 +326,10 @@ The editor is `$GUARDRAILS_EDITOR`, then `$VISUAL`, then `$EDITOR`. VS Code-fami
|
|
|
282
326
|
| `SEVERITY=error` / `SEVERITY=warning` | Severity floor. `error` checks errors only; `warning` drops suggestions; default is everything. Applies to the report, the exit code, JSON, HTML, and the TUI alike. Detectors that can only produce muted findings aren't run. An unrecognized value aborts rather than being ignored. |
|
|
283
327
|
| `FORMAT=json` | Emit one JSON document to stdout (all other audit output is suppressed). |
|
|
284
328
|
| `FORMAT=html` / `OUTPUT=path` | Write the self-contained HTML report (default `tmp/guardrails/audit.html`). |
|
|
285
|
-
| `GUARDRAILS_EDITOR=cmd` | Editor `
|
|
329
|
+
| `GUARDRAILS_EDITOR=cmd` | Editor `ui_guardrails:tui` opens locations in. Falls back to `$VISUAL`, then `$EDITOR`. |
|
|
286
330
|
| `FORCE=1` | Bypass `init`'s refuse-to-overwrite default. |
|
|
287
331
|
| `AXE_JSON=path` | Fold axe-core findings into the unified report. |
|
|
288
|
-
| `VISUAL_DIFF=1` | Fold visual-diff findings into `
|
|
332
|
+
| `VISUAL_DIFF=1` | Fold visual-diff findings into `ui_guardrails:audit`. Embedded installs can flip this on permanently via `Guardrails.configure { \|c\| c.visual_diff.enabled = true }`. |
|
|
289
333
|
| `VISUAL_DIFF_DIR=path` / `VISUAL_DIFF_THRESHOLD=0.02` | Tune the visual-diff snap_diff adapter and mismatch threshold. |
|
|
290
334
|
| `SIMILARITY_THRESHOLD=0.85` | Override the partial-similarity Jaccard threshold. |
|
|
291
335
|
| `PATTERN_MIN_SIZE=8` / `PATTERN_MIN_OCCURRENCES=4` | Tune the cross-codebase pattern detector. |
|
|
@@ -293,12 +337,169 @@ The editor is `$GUARDRAILS_EDITOR`, then `$VISUAL`, then `$EDITOR`. VS Code-fami
|
|
|
293
337
|
|
|
294
338
|
---
|
|
295
339
|
|
|
340
|
+
## Feed Ramp
|
|
341
|
+
|
|
342
|
+
The audit catches drift after it's written. Feed Ramp gives coding agents the app's real component library first, so they render the components you have instead of reinventing them.
|
|
343
|
+
|
|
344
|
+
```bash
|
|
345
|
+
bundle exec rake ui_guardrails:feed_ramp:rules # manifest + agent rules (CLAUDE.md, AGENTS.md, Cursor)
|
|
346
|
+
CHECK=1 bundle exec rake ui_guardrails:feed_ramp:rules # in CI: exit 1 if any of them is out of date
|
|
347
|
+
bundle exec rake ui_guardrails:feed_ramp:manifest # just .ui_guardrails/manifest.json + COMPONENTS.md
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
The manifest lists every ViewComponent and Phlex component: class, file, template, constructor params with their defaults, slots, the variants and states from its Lookbook / ViewComponent previews, its doc comment, and one usage example. It also lists the design tokens: everything `ui_guardrails:tokens` reads, plus custom properties in the `:root { }` and Tailwind v4 `@theme { }` blocks of your stylesheets.
|
|
351
|
+
|
|
352
|
+
- **No app boot, network or model call.** Everything is read by parsing the source with Prism. Neither ViewComponent, Phlex nor Lookbook has to be installed: a class is a component when its superclass chain (through your own `ApplicationComponent` or `Components::Base`) reaches `ViewComponent::Base` or `Phlex::HTML`.
|
|
353
|
+
- **Examples are copied, never written.** Each component's example is the shortest render of it (`render X.new(…)`, or a Phlex kit call like `Alert(…)`) in a visible preview, or else in a template or Ruby view, copied verbatim. A usage that names the component in full wins, since it works in any view. A component nothing renders gets no example.
|
|
354
|
+
- **Deterministic.** Same source, byte-identical files: no timestamps, everything sorted. Commit both files so agents on a fresh clone have them and component API changes show up in review. `CHECK=1` rebuilds and compares, so it fails only when the committed files are actually wrong, not on every view edit.
|
|
355
|
+
- **Phlex slots are a heuristic.** Phlex has no slot declarations, so public methods taking a block are listed instead and marked `heuristic`.
|
|
356
|
+
|
|
357
|
+
### Agent rules
|
|
358
|
+
|
|
359
|
+
`ui_guardrails:feed_ramp:rules` writes the manifest, then tells agents it exists, in the files they read at the start of every session:
|
|
360
|
+
|
|
361
|
+
| Target | File | |
|
|
362
|
+
| --- | --- | --- |
|
|
363
|
+
| `claude` | `CLAUDE.md` | default |
|
|
364
|
+
| `agents` | `AGENTS.md` (Codex, Cursor, and others) | default |
|
|
365
|
+
| `cursor` | `.cursor/rules/ui-guardrails.mdc` | default; attached when the agent works in `app/views` or a component path |
|
|
366
|
+
| `gemini` | `GEMINI.md` | opt in |
|
|
367
|
+
| `copilot` | `.github/copilot-instructions.md` | opt in |
|
|
368
|
+
|
|
369
|
+
Pick them with `TARGETS=claude,agents,copilot` or `rules_targets:` in `guardrails.yml`. The block names the components (up to 50) and token categories, and says to render an existing component before writing markup by hand.
|
|
370
|
+
|
|
371
|
+
- **Everything outside the markers is kept.** The block sits between `<!-- ui_guardrails:feed_ramp:begin -->` and `<!-- ui_guardrails:feed_ramp:end -->`; only that part is ever rewritten. A file without the markers gets the block at the end, and a second run with the same manifest changes nothing. Move the block anywhere in the file and it stays there.
|
|
372
|
+
- **Broken markers stop the run.** With an unpaired, duplicated or reversed marker, nothing is written and the error names the file.
|
|
373
|
+
- **One copy per file.** When `CLAUDE.md` imports `@AGENTS.md`, or is a link to it, the block only goes in `AGENTS.md`.
|
|
374
|
+
- **`REMOVE=1`** takes the blocks out and deletes the files that held nothing else.
|
|
375
|
+
|
|
376
|
+
The Cursor rule file is Feed Ramp's own and is rewritten whole. If a file at that path wasn't written by Feed Ramp, it's left alone and the run fails.
|
|
377
|
+
|
|
378
|
+
When [agent hooks](#coding-agent-hooks) are installed, each new session also gets a one-line pointer to `COMPONENTS.md`.
|
|
379
|
+
|
|
380
|
+
### MCP server
|
|
381
|
+
|
|
382
|
+
`ui_guardrails-mcp` serves the manifest to coding agents as [MCP](https://modelcontextprotocol.io) tools over stdio:
|
|
383
|
+
|
|
384
|
+
| Tool | Gives |
|
|
385
|
+
| --- | --- |
|
|
386
|
+
| `list_components` | every component in brief (kind, one-line description, params, slots), and the token names by category |
|
|
387
|
+
| `get_component(name)` | one component's full entry: params with defaults, slots, previews, usage example. `Badge` finds `Admin::BadgeComponent` |
|
|
388
|
+
| `search_components(query)` | components ranked by name, description, params, slots and preview labels |
|
|
389
|
+
| `check_snippet(source, language?, path?)` | for ERB: the audit's and static a11y's findings, `component_match` included. For ERB and Ruby (Phlex): every `SomeComponent.new(...)` and Phlex kit call (`Alert(...)`) checked against the manifest (unknown component, keyword it doesn't take, required one missing, wrong positional count, and a likely typo a `**options` param would swallow) |
|
|
390
|
+
|
|
391
|
+
```bash
|
|
392
|
+
bundle exec rake ui_guardrails:feed_ramp:mcp:install # .mcp.json (Claude Code)
|
|
393
|
+
AGENT=all bundle exec rake ui_guardrails:feed_ramp:mcp:install # + .codex/config.toml, .cursor/mcp.json, .gemini/settings.json
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
The install keeps every other server and setting in those files and is safe to run again. It uses `bundle exec` when your Gemfile has the gem, and the bare executable otherwise. Cursor's entry passes `--root ${workspaceFolder}`, since Cursor doesn't promise to start servers in the project. Claude Code asks you to approve a project server the first time, and Codex reads `.codex/config.toml` only in projects you've trusted. In that file the install owns the `[mcp_servers.ui_guardrails]` table and leaves every other line as it was.
|
|
397
|
+
|
|
398
|
+
The server never boots the app. It builds the manifest from source when it starts and rebuilds it whenever a component, preview, view, stylesheet or `guardrails.yml` is added, removed or changed. If that changes the committed files it rewrites `.ui_guardrails/`, so `component_match` sees the same components. Pass `--no-write` (or `WRITE=0` for `rake ui_guardrails:feed_ramp:mcp`) to leave them alone. Once a project config starts the server, `ui_guardrails:feed_ramp:rules` mentions the tools in the agent rules block.
|
|
399
|
+
|
|
400
|
+
### Modern Web Guidance
|
|
401
|
+
|
|
402
|
+
Feed Ramp tells agents what your app already has. [Modern Web Guidance](https://developer.chrome.com/docs/modern-web-guidance) tells them what the web platform already has: a skill that has an agent look up current practice for dialogs, popovers, forms, layout, animation, performance and accessibility before it writes HTML, CSS or client-side JavaScript, instead of the older patterns in its training data.
|
|
403
|
+
|
|
404
|
+
```bash
|
|
405
|
+
bundle exec rake ui_guardrails:feed_ramp:modern_web:install # runs `npx -y modern-web-guidance@latest install`
|
|
406
|
+
bundle exec rake ui_guardrails:feed_ramp:rules # the rules now mention the skill
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
- **Their installer, not a copy.** The task runs Modern Web Guidance's own interactive install in your project and nothing of it ships in this gem, so the guides are always theirs and current. It needs Node.js 22 or later. `ARGS="…"` is passed through to the installer.
|
|
410
|
+
- **Your library comes first.** Once the skill is in a project skills directory (`.claude/skills`, `.agents/skills` and the like), the agent rules block tells agents to search it for platform HTML, CSS and JavaScript, and to use it where no component of yours does the job and inside the components themselves.
|
|
411
|
+
- **Commit the skill** with the rules, or `CHECK=1` in CI sees rules that mention a skill the checkout doesn't have. If your team installs it per user or as an agent plugin instead, set `feed_ramp.modern_web_guidance: true` in `guardrails.yml`; `false` keeps it out of the rules.
|
|
412
|
+
- **Telemetry is theirs.** The Modern Web Guidance CLI sends Google anonymous usage data: installs, the calling agent, guide IDs and the agent's search queries. The task says so before it runs. `DISABLE_TELEMETRY=1` in your shell profile opts out.
|
|
413
|
+
|
|
414
|
+
Modern Web Guidance is by Google Chrome and its contributors, stewarded with the Microsoft Edge, Mozilla Firefox and MDN teams. Its code is Apache-2.0 and its guides are CC BY 4.0. Source: [GoogleChrome/modern-web-guidance](https://github.com/GoogleChrome/modern-web-guidance).
|
|
415
|
+
|
|
416
|
+
### Component match
|
|
417
|
+
|
|
418
|
+
Once a manifest exists, `ui_guardrails:audit` (and so the agent hooks and the Lookbook panel) flags markup that copies one of your components instead of rendering it:
|
|
419
|
+
|
|
420
|
+
```
|
|
421
|
+
! WARNING — component_match (1 finding)
|
|
422
|
+
[warning] component_match: <button type="button" class="button button--primary"> looks like ButtonComponent
|
|
423
|
+
→ render ButtonComponent instead, e.g. render(ButtonComponent.new(label: "Save"))
|
|
424
|
+
app/views/welcome/broken.html.erb:42:1
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
Each component's root element is read when the manifest is built: the first element of its ERB template (or the first `<%= tag.div … %>` it opens with), or the first tag its Phlex `view_template` or ViewComponent `call` builds, following the component's own methods. Its classes are the ones it always has, read without rendering: literal strings, and the unconditional string arguments of a class helper (`class_names("kbd", "kbd--bare": @bare)` and `build_css("button", …)` give `kbd` and `button`), followed through methods such as `class: css_classes`, `**wrapper_attributes` or `attrs[:class] = …`. Conditional and interpolated classes (`btn--<%= @variant %>`, `alert--#{@tone}`) don't count. Markup matches with either:
|
|
428
|
+
|
|
429
|
+
- **the component's signature class, on any tag:** a root class named after the component (`button` or `button-component` for ButtonComponent, `icon-button` for IconButtonComponent), even on its own. Any tag, because a button component may render a link; or
|
|
430
|
+
- **the same tag and most of a long class list:** at least `coverage` (0.8) of the root's classes, when the root has at least `min_classes` (3). Shorter lists of generic utilities never match.
|
|
431
|
+
|
|
432
|
+
A component whose classes all depend on its arguments (a `class:` it's given, or one picked in an `if`) has nothing to match on.
|
|
433
|
+
|
|
434
|
+
A component's own template is never matched against itself. The rule reads the committed manifest rather than parsing the app, so it adds nothing noticeable to a hook call, and it says nothing until `ui_guardrails:feed_ramp:manifest` has run.
|
|
435
|
+
|
|
436
|
+
It's a **warning**, so it fails `ui_guardrails:audit` like any other warning. To try it out first, lower it to a suggestion, which reports without failing:
|
|
437
|
+
|
|
438
|
+
```yaml
|
|
439
|
+
guardrails:
|
|
440
|
+
feed_ramp:
|
|
441
|
+
component_match: suggestion # or warning (default), error, off
|
|
442
|
+
# or, to tune matching too:
|
|
443
|
+
# component_match:
|
|
444
|
+
# severity: suggestion
|
|
445
|
+
# min_classes: 3
|
|
446
|
+
# coverage: 0.8
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
### Drift score
|
|
450
|
+
|
|
451
|
+
```bash
|
|
452
|
+
bundle exec rake ui_guardrails:feed_ramp:score # before
|
|
453
|
+
# ...an agent session, a branch, turning Feed Ramp on...
|
|
454
|
+
bundle exec rake ui_guardrails:feed_ramp:score BASELINE=last # after, compared with the last saved run
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
```
|
|
458
|
+
UI drift score: 116.7 issues per 100 templates (7 issues in 6 templates)
|
|
459
|
+
|
|
460
|
+
Hardcoded values 1 stylesheet_color 1
|
|
461
|
+
Reinvented components 4 class_itis 4
|
|
462
|
+
Accessibility 2 image_alt 1, link_name 1
|
|
463
|
+
|
|
464
|
+
Against 2026-10-19T12:00:00Z, 3f2c1aa: 8 → 7 issues (−1), 133.3 → 116.7 per 100 templates
|
|
465
|
+
Hardcoded values 3 → 1 (−2)
|
|
466
|
+
Reinvented components 4 → 4 (0)
|
|
467
|
+
Accessibility 1 → 2 (+1)
|
|
468
|
+
Most changed: app/views/pages/home.html.erb −3, app/views/pages/about.html.erb +2
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
| Bucket | Counts |
|
|
472
|
+
| --- | --- |
|
|
473
|
+
| Hardcoded values | raw colours, arbitrary Tailwind values and inline styles in templates; hex literals in stylesheets outside the token files |
|
|
474
|
+
| Reinvented components | markup copying a component (`component_match`), each occurrence of a class-itis cluster or repeated markup, and each pair of near-identical partials |
|
|
475
|
+
| Accessibility | static a11y findings (missing alt text, unnamed buttons and links, unlabelled inputs) |
|
|
476
|
+
|
|
477
|
+
The headline is issues per 100 templates, so apps and branches of different sizes compare. Templates are the ones `ui_guardrails:audit` scans: its `scan_paths` and `ignore` apply to every bucket.
|
|
478
|
+
|
|
479
|
+
Each run is saved as JSON under `.ui_guardrails/scores/` (which keeps itself out of git) with the totals, per-type and per-file counts, the git commit, the gem version and a digest of `guardrails.yml`. `BASELINE=` takes `last` or a path to any saved score, and the comparison says when the config or gem version differ between the two. To keep runs comparable it uses the detectors' default thresholds and ignores the audit's env vars. It always exits 0; gate CI with `ui_guardrails:audit`.
|
|
480
|
+
|
|
481
|
+
Config in `guardrails.yml` (all optional; defaults shown):
|
|
482
|
+
|
|
483
|
+
```yaml
|
|
484
|
+
guardrails:
|
|
485
|
+
feed_ramp:
|
|
486
|
+
component_paths: [app/components, app/views/components]
|
|
487
|
+
preview_paths: [test/components/previews, spec/components/previews, lookbook/previews]
|
|
488
|
+
token_files: [] # extra files whose custom properties are all tokens
|
|
489
|
+
output_dir: .ui_guardrails
|
|
490
|
+
rules_targets: [claude, agents, cursor] # also: gemini, copilot
|
|
491
|
+
```
|
|
492
|
+
|
|
493
|
+
Preview directories your Rails config adds are found too, without booting the app: `config.view_component.previews.paths << Rails.root.join("…").to_s` (or `preview_paths`, `+=`, `concat`) and `config.lookbook.preview_paths` in `config/application.rb` or `config/environments/{development,test}.rb`, written as strings or built from `Rails.root` or `__dir__`. List any computed at boot here.
|
|
494
|
+
|
|
495
|
+
---
|
|
496
|
+
|
|
296
497
|
## Coding agent hooks
|
|
297
498
|
|
|
298
499
|
```bash
|
|
299
|
-
bundle exec rake
|
|
300
|
-
AGENT=claude,codex bundle exec rake
|
|
301
|
-
AGENT=all bundle exec rake
|
|
500
|
+
bundle exec rake ui_guardrails:hooks:install # Claude Code
|
|
501
|
+
AGENT=claude,codex bundle exec rake ui_guardrails:hooks:install
|
|
502
|
+
AGENT=all bundle exec rake ui_guardrails:hooks:install # Claude Code, Codex, Cursor, Gemini CLI
|
|
302
503
|
```
|
|
303
504
|
|
|
304
505
|
This adds Guardrails to each agent's project hook config. It keeps whatever else is in that file and is safe to run again. Each hook calls `guardrails-hook <agent> <event>`, which runs only the parse-only checks: view drift (raw colours, Tailwind arbitrary values, inline styles, helper recommendations) and static a11y. A typical call takes a fraction of a second.
|
|
@@ -309,7 +510,7 @@ It reports only what the agent introduced. A template is compared with its versi
|
|
|
309
510
|
| --- | --- |
|
|
310
511
|
| After the agent edits a template | The drift it just introduced goes back to it as context. Nothing is blocked. |
|
|
311
512
|
| When it tries to finish | New **errors** in any template changed since `HEAD` send it back to work, once per turn. Warnings alone don't. |
|
|
312
|
-
| When a session starts |
|
|
513
|
+
| When a session starts | Where the [Feed Ramp](#feed-ramp) component list is, when the app has one, and notes from other gems (ui_guardrails-browser adds open visual-review rejections). |
|
|
313
514
|
|
|
314
515
|
| Agent | Config | Notes |
|
|
315
516
|
| --- | --- | --- |
|
|
@@ -318,7 +519,7 @@ It reports only what the agent introduced. A template is compared with its versi
|
|
|
318
519
|
| Cursor | `.cursor/hooks.json` | `stop` and `sessionStart` only: Cursor's after-edit output doesn't reliably reach the model, so the stop check covers every changed template. Cursor also runs `.claude/settings.json` hooks; the hook recognises Cursor's input and doesn't run twice. |
|
|
319
520
|
| Gemini CLI | `.gemini/settings.json` | `AfterTool` (`write_file`, `replace`), `AfterAgent`, `SessionStart` |
|
|
320
521
|
|
|
321
|
-
The hook never breaks the agent's turn: if anything goes wrong it prints `{}`, explains on stderr, and exits 0. Other gems add to the same events with `Guardrails::Hooks.register(name, contributor)`, shipped at `lib/guardrails/hook_contributors/<name>.rb` so the hook finds them without booting the app.
|
|
522
|
+
The hook never breaks the agent's turn: if anything goes wrong it prints `{}`, explains on stderr, and exits 0. The installed command ends in `|| echo '{}'`, so the same holds when the hook can't start at all. That happens on a branch whose Gemfile doesn't have the gem, since the agent's config usually isn't tracked by git and outlives the branch. Running the install again replaces an earlier install's hooks instead of adding new ones beside them. Other gems add to the same events with `Guardrails::Hooks.register(name, contributor)`, shipped at `lib/guardrails/hook_contributors/<name>.rb` so the hook finds them without booting the app.
|
|
322
523
|
|
|
323
524
|
---
|
|
324
525
|
|
|
@@ -329,14 +530,14 @@ The audit task is a single shell command:
|
|
|
329
530
|
```yaml
|
|
330
531
|
# .github/workflows/ci.yml
|
|
331
532
|
- name: Guardrails audit
|
|
332
|
-
run: bundle exec rake
|
|
533
|
+
run: bundle exec rake ui_guardrails:audit
|
|
333
534
|
```
|
|
334
535
|
|
|
335
536
|
Adopting Guardrails on an existing codebase usually means a backlog of warnings you can't clear in one PR. Gate on errors now and ratchet down later:
|
|
336
537
|
|
|
337
538
|
```yaml
|
|
338
539
|
- name: Guardrails audit (errors only)
|
|
339
|
-
run: bundle exec rake
|
|
540
|
+
run: bundle exec rake ui_guardrails:audit SEVERITY=error
|
|
340
541
|
```
|
|
341
542
|
|
|
342
543
|
`SEVERITY=error` exits 1 only when there are error-level findings (raw colors, arbitrary Tailwind values, a11y, visual diffs) and skips the warning- and suggestion-tier detectors entirely, so it's also the fastest way to run the audit. The report ends with a line saying what wasn't checked, so a green run can't be mistaken for a clean codebase.
|
|
@@ -345,7 +546,7 @@ For richer integration:
|
|
|
345
546
|
|
|
346
547
|
```yaml
|
|
347
548
|
- name: Guardrails audit (JSON)
|
|
348
|
-
run: bundle exec rake
|
|
549
|
+
run: bundle exec rake ui_guardrails:audit FORMAT=json > findings.json
|
|
349
550
|
continue-on-error: true
|
|
350
551
|
|
|
351
552
|
- name: Upload findings
|
|
@@ -359,7 +560,7 @@ For richer integration:
|
|
|
359
560
|
|
|
360
561
|
## Configuration
|
|
361
562
|
|
|
362
|
-
`guardrails.yml` at the repo root. `
|
|
563
|
+
`guardrails.yml` at the repo root. `ui_guardrails:init` writes a sensible default after detecting your stack — what's below is annotated to show every available key:
|
|
363
564
|
|
|
364
565
|
```yaml
|
|
365
566
|
guardrails:
|
|
@@ -391,7 +592,7 @@ guardrails:
|
|
|
391
592
|
|
|
392
593
|
## Demo app
|
|
393
594
|
|
|
394
|
-
[`examples/demo`](examples/demo) is a bootable Rails 7.2 app with intentionally seeded findings — every detector trips at least once. Clone, `bin/setup`, `bin/rails server`, then visit `/`, `/broken`, and `/rails/lookbook` to see the auto-registered Guardrails panel. `bundle exec rake
|
|
595
|
+
[`examples/demo`](examples/demo) is a bootable Rails 7.2 app with intentionally seeded findings — every detector trips at least once. Clone, `bin/setup`, `bin/rails server`, then visit `/`, `/broken`, and `/rails/lookbook` to see the auto-registered Guardrails panel. `bundle exec rake ui_guardrails:audit` runs the full audit against the seeded tree. See [`examples/demo/README.md`](examples/demo/README.md).
|
|
395
596
|
|
|
396
597
|
---
|
|
397
598
|
|
|
@@ -409,7 +610,7 @@ Full status table and decision log: [`doc/ROADMAP.md`](doc/ROADMAP.md).
|
|
|
409
610
|
|
|
410
611
|
```bash
|
|
411
612
|
bundle install
|
|
412
|
-
bundle exec rspec #
|
|
613
|
+
bundle exec rspec # 883 examples
|
|
413
614
|
```
|
|
414
615
|
|
|
415
616
|
Real-world signal lives in `examples/demo` (integration spec) and in the dogfood patches against four real codebases (Patchvault, Talos, Forem, Avo — see `CHANGELOG.md` for what each round caught).
|
|
@@ -418,6 +619,13 @@ Issues and PRs: <https://github.com/meticulous/guardrails>
|
|
|
418
619
|
|
|
419
620
|
---
|
|
420
621
|
|
|
622
|
+
## Credits
|
|
623
|
+
|
|
624
|
+
- [Modern Web Guidance](https://developer.chrome.com/docs/modern-web-guidance), by Google Chrome and its contributors with the Microsoft Edge, Mozilla Firefox and MDN teams (Apache-2.0; guides CC BY 4.0). Feed Ramp installs it with its own installer and points agents at it; none of it is bundled.
|
|
625
|
+
- [Herb](https://github.com/marcoroth/herb), the ERB parser every template detector is built on.
|
|
626
|
+
|
|
627
|
+
---
|
|
628
|
+
|
|
421
629
|
## License
|
|
422
630
|
|
|
423
631
|
MIT
|
data/doc/A11Y.md
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
# Accessibility (a11y) checks
|
|
2
2
|
|
|
3
|
-
Guardrails ships **static** a11y checks — element-level rules that can be answered from your view source without rendering. These run as part of `rails
|
|
3
|
+
Guardrails ships **static** a11y checks — element-level rules that can be answered from your view source without rendering. These run as part of `rails ui_guardrails:audit` and contribute to the exit code.
|
|
4
4
|
|
|
5
5
|
For full a11y coverage (color contrast, focus order, ARIA tree, dynamic content), wire **axe-core** alongside Guardrails in your test suite. Static checks catch the obvious template-level mistakes; runtime checks catch everything else.
|
|
6
6
|
|
|
7
7
|
## Static checks (built-in)
|
|
8
8
|
|
|
9
|
-
Run via `rails
|
|
9
|
+
Run via `rails ui_guardrails:audit`. Findings appear under "Guardrails a11y" in the report and as `a11y` in `FORMAT=json` output.
|
|
10
10
|
|
|
11
11
|
| Rule | Catches |
|
|
12
12
|
|---|---|
|
|
@@ -27,13 +27,13 @@ npx @axe-core/cli http://localhost:3000/ http://localhost:3000/dashboard --save
|
|
|
27
27
|
# or via axe-core-rspec, axe-puppeteer, your CDP script — anything that emits axe JSON v4
|
|
28
28
|
|
|
29
29
|
# Hand the JSON to Guardrails:
|
|
30
|
-
AXE_JSON=axe.json bundle exec rake
|
|
30
|
+
AXE_JSON=axe.json bundle exec rake ui_guardrails:audit
|
|
31
31
|
```
|
|
32
32
|
|
|
33
33
|
Or run the deep check standalone:
|
|
34
34
|
|
|
35
35
|
```bash
|
|
36
|
-
AXE_JSON=axe.json bundle exec rake
|
|
36
|
+
AXE_JSON=axe.json bundle exec rake ui_guardrails:a11y:deep
|
|
37
37
|
```
|
|
38
38
|
|
|
39
39
|
The report groups findings by URL and prints rule + impact + selector + help URL per node. Exit code is 1 when any finding's impact is in the failing set (default: `minor`, `moderate`, `serious`, `critical` — i.e. any impact fails). The full payload is also included in `FORMAT=json` output under the `a11y_deep` key.
|
data/doc/PRD.md
CHANGED
|
@@ -53,7 +53,7 @@ Rails developers without dedicated designers have no tooling to catch this befor
|
|
|
53
53
|
|
|
54
54
|
## Features
|
|
55
55
|
|
|
56
|
-
### 1. Component Audit (`rails
|
|
56
|
+
### 1. Component Audit (`rails ui_guardrails:audit`)
|
|
57
57
|
|
|
58
58
|
Scans views and partials for:
|
|
59
59
|
- Near-duplicate partials (structural similarity, not just naming)
|
|
@@ -63,14 +63,14 @@ Scans views and partials for:
|
|
|
63
63
|
|
|
64
64
|
Outputs: summary report to stdout + optional JSON/HTML report
|
|
65
65
|
|
|
66
|
-
### 2. Icon Management (`rails
|
|
66
|
+
### 2. Icon Management (`rails ui_guardrails:icons`)
|
|
67
67
|
|
|
68
68
|
- Scans configured SVG source directory
|
|
69
69
|
- Generates optimized SVG sprite sheet
|
|
70
70
|
- Audits for inline SVGs in views that should be sprite references
|
|
71
71
|
- Reports icon usage frequency (find dead icons)
|
|
72
72
|
|
|
73
|
-
### 3. Design Token Enforcement (`rails
|
|
73
|
+
### 3. Design Token Enforcement (`rails ui_guardrails:tokens`)
|
|
74
74
|
|
|
75
75
|
- Reads a `guardrails.yml` token definition (colors, type scale, spacing)
|
|
76
76
|
- Scans CSS/SCSS/Tailwind config for values that don't match defined tokens
|
|
@@ -116,7 +116,7 @@ guardrails:
|
|
|
116
116
|
|
|
117
117
|
## Success Metrics
|
|
118
118
|
|
|
119
|
-
- Developers can run `rails
|
|
119
|
+
- Developers can run `rails ui_guardrails:audit` in < 30 seconds on a mid-sized app
|
|
120
120
|
- Catches at least 80% of common UI drift patterns in a test app
|
|
121
121
|
- Zero false positives on a clean, well-structured Rails app
|
|
122
122
|
- Used by at least 3 Meticulous clients within 6 months of launch
|
|
@@ -139,7 +139,7 @@ guardrails:
|
|
|
139
139
|
|-----------|--------|-------|
|
|
140
140
|
| PRD + README stub | 2026-05-04 | ✅ Done |
|
|
141
141
|
| Gem skeleton + Railtie | TBD | First Claude Code session |
|
|
142
|
-
| `
|
|
143
|
-
| `
|
|
144
|
-
| `
|
|
142
|
+
| `ui_guardrails:audit` v0 | TBD | |
|
|
143
|
+
| `ui_guardrails:icons` v0 | TBD | |
|
|
144
|
+
| `ui_guardrails:tokens` v0 | TBD | |
|
|
145
145
|
| Public beta / RubyGems publish | TBD | Before next speaking slot |
|
data/doc/PUBLISHING.md
CHANGED
|
@@ -35,6 +35,10 @@ git pull --ff-only
|
|
|
35
35
|
grep VERSION lib/guardrails/version.rb
|
|
36
36
|
head -20 CHANGELOG.md
|
|
37
37
|
|
|
38
|
+
# A version bump changes the generator line in the demo's committed
|
|
39
|
+
# Feed Ramp manifest; regenerate it (the demo spec fails until you do)
|
|
40
|
+
(cd examples/demo && bundle exec rake ui_guardrails:feed_ramp:manifest)
|
|
41
|
+
|
|
38
42
|
# Tag the merge commit and push the tag (replace X.Y.Z with the
|
|
39
43
|
# version you're cutting — the next-release example would be the
|
|
40
44
|
# version in lib/guardrails/version.rb on main right now).
|