browsable 0.1.0

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 8fbd1c4932fbe740cf4d0560fbeb4738c51c3129efdb3c7a3e48b86bc47a743d
4
+ data.tar.gz: f6d98e30cc6e54c6c5fe643dbf3eada45a08af171e2632561c8d91f1b49c74b6
5
+ SHA512:
6
+ metadata.gz: 5f21b860c35d76a1011a2152a40ab9cbb232e7378d93f61fec3c75f4cea1cd5b69829ea4e77ffd2ae5f7406334e436d4997a17ad41fb769f253362e579d5baa6
7
+ data.tar.gz: 9e996ecdb1dded29f78c2b30c3b5c37178bd7c339800680bca51947934c6addf965534d09a1b3b03b5358b2134b542b056eab354b9582ba9864dc2dc806f57d7
data/CHANGELOG.md ADDED
@@ -0,0 +1,12 @@
1
+ # Changelog
2
+
3
+ All notable changes to the `browsable` gem are documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ ## [0.1.0]
11
+
12
+ - Initial release.
data/README.md ADDED
@@ -0,0 +1,240 @@
1
+ # browsable
2
+
3
+ **Rails-aware browser-compatibility auditing for your frontend code.**
4
+
5
+ `browsable` audits a Rails application's CSS, HTML, ERB, and JavaScript and
6
+ reports which browsers can actually render and run it — then compares that
7
+ against the project's declared `allow_browser` policy.
8
+
9
+ The name is a play on Rails 8's `allow_browser` controller API. Instead of
10
+ *declaring* which browsers you allow, `browsable` tells you which browsers your
11
+ code is actually **browsable by**.
12
+
13
+ > This is the core gem of the [`browsable` monorepo](https://github.com/romanhood/browsable).
14
+ > See also [`browsable-lsp`](../browsable-lsp) (editor diagnostics) and
15
+ > [`browsable.nvim`](../browsable.nvim) (Neovim plugin).
16
+
17
+ ## Philosophy
18
+
19
+ - **The gem owns no parsing or compat-data logic.** It shells out to mature
20
+ tools that already do this well — [Herb](https://github.com/marcoroth/herb)
21
+ for ERB, [stylelint](https://stylelint.io/) for CSS,
22
+ [eslint-plugin-compat](https://github.com/amilajack/eslint-plugin-compat) for
23
+ JavaScript. The gem's value is the Rails-aware glue.
24
+ - **No `package.json`, no `node_modules` in your Rails repo.** The external
25
+ tools live globally on your machine. `browsable doctor` detects them and
26
+ guides installation.
27
+ - **It reports, it doesn't decide.** It tells you what your code requires and
28
+ what your config permits. You decide what to do.
29
+ - **Configuration is optional.** `browsable` runs with zero config.
30
+
31
+ ## Installation
32
+
33
+ Add it to your Rails app's `Gemfile`:
34
+
35
+ ```ruby
36
+ group :development, :test do
37
+ gem "browsable"
38
+ end
39
+ ```
40
+
41
+ Then `bundle install`. Or install it standalone:
42
+
43
+ ```bash
44
+ gem install browsable
45
+ ```
46
+
47
+ ## System dependencies — the `doctor` workflow
48
+
49
+ `browsable` shells out to `stylelint` and `eslint` (and the `node` runtime they
50
+ need). Those are *not* gem dependencies — they live globally on your machine.
51
+ Check what you have:
52
+
53
+ ```bash
54
+ bundle exec browsable doctor
55
+ ```
56
+
57
+ `doctor` prints, for each missing tool, the exact command to install it. Let it
58
+ do the work for you:
59
+
60
+ ```bash
61
+ bundle exec browsable doctor --fix # installs missing tools via brew / npm
62
+ ```
63
+
64
+ ERB and HTML analysis needs nothing extra — the `herb` gem is a dependency and
65
+ runs in-process.
66
+
67
+ ## Quick start
68
+
69
+ ```bash
70
+ bundle exec browsable audit
71
+ ```
72
+
73
+ That's it. With no configuration, `browsable`:
74
+
75
+ 1. reads `ApplicationController`'s `allow_browser` policy to learn your target,
76
+ 2. discovers your stylesheets, views, JavaScript, and importmap pins,
77
+ 3. audits each against that target, and
78
+ 4. prints a report grouped by file.
79
+
80
+ ## CLI reference
81
+
82
+ | Command | Purpose |
83
+ | --- | --- |
84
+ | `browsable` / `browsable audit [PATH]` | Full project audit |
85
+ | `browsable doctor` | Check system dependencies |
86
+ | `browsable doctor --fix` | Install missing dependencies (opt-in) |
87
+ | `browsable check FILE [FILE...]` | Audit specific files (used by editors) |
88
+ | `browsable target [PATH]` | Show the inferred browser-support target |
89
+ | `browsable init` | Generate a `.browsable.yml` (non-Rails projects) |
90
+ | `browsable version` | Print the version |
91
+
92
+ ### Flags
93
+
94
+ | Flag | Effect |
95
+ | --- | --- |
96
+ | `--target QUERY` | Override the inferred browserslist query |
97
+ | `--json` | Emit findings as JSON (shortcut for `--format json`) |
98
+ | `--format human\|json\|github` | Choose the output formatter |
99
+ | `--no-build` | Scan only what is on disk (`browsable` never builds assets itself) |
100
+ | `--include GLOB` | Add a path glob to the audit (repeatable) |
101
+ | `--exclude GLOB` | Exclude a path glob (repeatable) |
102
+ | `--fail-on warning\|error` | Exit-code policy for CI |
103
+ | `--config PATH` | Override the config file location |
104
+
105
+ The `--json` output is the universal interface: the LSP server (and any future
106
+ MCP server) consume exactly that structure. The human and GitHub formatters are
107
+ just alternate presentations of the same data.
108
+
109
+ ## Rails generator
110
+
111
+ ```bash
112
+ rails g browsable:install
113
+ ```
114
+
115
+ This writes a fully-commented `config/browsable.yml` — every option present,
116
+ commented out, set to its default. It is a self-documenting reference: uncomment
117
+ a line to override it. Flags: `--minimal`, `--target QUERY`, `--force`.
118
+
119
+ Non-Rails projects use `browsable init`, which writes `.browsable.yml` instead.
120
+
121
+ ## Configuration
122
+
123
+ `browsable` needs no config file. When one is present it is discovered in this
124
+ order:
125
+
126
+ 1. the path passed to `--config`
127
+ 2. `config/browsable.yml` (preferred in Rails apps)
128
+ 3. `.browsable.yml` in the working directory
129
+
130
+ Resolution precedence (highest wins): **CLI flags → config file → inferred Rails
131
+ config → gem defaults**. See the generated `config/browsable.yml` for the full,
132
+ commented option reference.
133
+
134
+ ## How it works — the inference chain
135
+
136
+ ```
137
+ ApplicationController.allow_browser → Target (browserslist query) → Analyzers
138
+ :modern chrome 120, safari 17.2 │
139
+
140
+ config/importmap.rb ─┐ CSS → stylelint
141
+ app/assets/** ─┼─→ Sources ─→ files by kind ───────── ERB → Herb + BCD
142
+ app/views/** ─┤ HTML → Herb + BCD
143
+ app/javascript/** ─┘ JS → eslint
144
+
145
+
146
+ Report → Formatter
147
+ ```
148
+
149
+ `browsable` translates between Rails-land and browserslist-land: it reads
150
+ `allow_browser :modern`, expands it to concrete browser versions, configures
151
+ stylelint/eslint with that target, and runs Herb against the bundled MDN
152
+ browser-compat-data snapshot for ERB/HTML.
153
+
154
+ ### Partial `allow_browser` policies
155
+
156
+ If your `allow_browser` policy is a hash that pins only some browsers — say
157
+ `versions: { safari: 16.4, firefox: 121 }` — Rails leaves every browser you
158
+ *don't* list allowed at **any** version (it only blocks a browser it was given a
159
+ minimum, or `false`, for). browsable audits exactly the browsers you pinned and
160
+ prints a note naming the rest. To audit against more, set an explicit `target:`
161
+ in `config/browsable.yml`. The same note-and-fall-back-to-`defaults` behaviour
162
+ applies when browsable cannot resolve your policy statically.
163
+
164
+ ### Where `defaults` comes from
165
+
166
+ When there is no `allow_browser` policy at all, browsable audits against the
167
+ [browserslist](https://github.com/browserslist/browserslist) `defaults` query —
168
+ the "reasonable broad support" baseline the wider frontend ecosystem uses. It is
169
+ resolved **live** from caniuse data when the `browserslist` CLI is installed
170
+ (`npm install -g browserslist`); otherwise browsable uses a small **built-in
171
+ approximation** and says so in a note. Either way these versions are *not* a
172
+ Rails concept — Rails blocks nothing unless you call `allow_browser` — and they
173
+ are not derived from stylelint or eslint. For a precise, stable target, set
174
+ `target:` in `config/browsable.yml`.
175
+
176
+ ### Suggested `allow_browser` fix
177
+
178
+ When an audit finds errors that are purely a version conflict — your code needs
179
+ a browser version newer than your policy permits — browsable prints a ready-to-paste
180
+ `allow_browser` line that raises *only* the offending browsers to the minimum
181
+ those features require, leaving every other browser untouched:
182
+
183
+ ```
184
+ Suggested allow_browser policy
185
+ allow_browser versions: { chrome: 120, edge: 120, firefox: 125, safari: 17.2, opera: 106 }
186
+ firefox: 121 → 125
187
+ ```
188
+
189
+ It is a suggestion, not an instruction: tightening the policy is one fix, changing
190
+ the code (a fallback, a `@supports` rule) is another. browsable reports; you
191
+ decide. The suggestion is derived from HTML/ERB findings, which carry exact
192
+ version data; it also appears in `--json` (`suggested_policy`) and as a GitHub
193
+ Actions notice.
194
+
195
+ ### Per-controller and per-action policies
196
+
197
+ Rails lets any controller override `allow_browser`, and scope the override to
198
+ certain actions with `only:`/`except:`. browsable scans every file under
199
+ `app/controllers/` (including `concerns/`) and lists each `allow_browser` call
200
+ it finds — its versions and any action scope — under **Browser policies** in the
201
+ report.
202
+
203
+ The audit itself runs against a single target (ApplicationController's policy,
204
+ or your `config/browsable.yml`). browsable does **not** try to map each frontend
205
+ asset to the exact endpoints — and policies — that serve it. CSS and importmap
206
+ JavaScript are global assets, included via layout helpers on nearly every page,
207
+ so they have no single owning controller action; a per-asset policy graph would
208
+ be guesswork. Instead, browsable shows you the whole policy landscape: if a
209
+ controller serves shared assets to a broader range of browsers than
210
+ ApplicationController, audit against that policy explicitly with `--target` or
211
+ `config/browsable.yml`. Per-action auditing of `app/views/<controller>/`
212
+ templates against their controller's policy is a planned refinement.
213
+
214
+ ## Rake tasks
215
+
216
+ Inside a Rails app, the railtie registers:
217
+
218
+ - `rake browsable:audit` — audit `app/assets/builds/` as it stands
219
+ - `rake browsable:audit:fresh` — run `assets:precompile` first, then audit
220
+ - `rake browsable:doctor` — run the dependency check
221
+
222
+ `browsable` never precompiles assets on its own. In CI, compose the pipeline
223
+ explicitly: `bundle exec rails assets:precompile && bundle exec browsable audit`.
224
+
225
+ ## Contributing
226
+
227
+ This gem lives in the `browsable/` subdirectory of the
228
+ [monorepo](https://github.com/romanhood/browsable). To work on it:
229
+
230
+ ```bash
231
+ cd browsable
232
+ bundle install
233
+ bundle exec rspec
234
+ ```
235
+
236
+ Refresh the bundled compat data with `ruby bin/update-bcd-snapshot`.
237
+
238
+ ## License
239
+
240
+ MIT — see the [LICENSE](../LICENSE) at the monorepo root.
@@ -0,0 +1,65 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Maintenance script — refresh data/bcd-snapshot.json from the latest published
5
+ # @mdn/browser-compat-data. The gem ships the generated snapshot; this script
6
+ # is run by maintainers, never at gem runtime.
7
+ #
8
+ # ruby bin/update-bcd-snapshot
9
+ #
10
+ # It downloads the full dataset (several MB) and keeps only the subtrees
11
+ # browsable's ERB/HTML analyzer actually consults, so the committed file stays
12
+ # small.
13
+
14
+ require "net/http"
15
+ require "uri"
16
+ require "json"
17
+ require "time"
18
+
19
+ SOURCE = "https://cdn.jsdelivr.net/npm/@mdn/browser-compat-data/data.json"
20
+ OUTPUT = File.expand_path("../data/bcd-snapshot.json", __dir__)
21
+
22
+ # Only these subtrees are retained.
23
+ SUBTREES = [
24
+ %w[html elements],
25
+ %w[html global_attributes],
26
+ %w[css properties]
27
+ ].freeze
28
+
29
+ def fetch(url, redirects: 5)
30
+ raise "Too many redirects" if redirects.negative?
31
+
32
+ response = Net::HTTP.get_response(URI.parse(url))
33
+ case response
34
+ when Net::HTTPSuccess then response.body
35
+ when Net::HTTPRedirection then fetch(response["location"], redirects: redirects - 1)
36
+ else raise "HTTP #{response.code} fetching #{url}"
37
+ end
38
+ end
39
+
40
+ puts "Fetching #{SOURCE} ..."
41
+ full = JSON.parse(fetch(SOURCE))
42
+
43
+ snapshot = {
44
+ "__meta" => {
45
+ "source" => SOURCE,
46
+ "generated_at" => Time.now.utc.iso8601,
47
+ "subtrees" => SUBTREES.map { |path| path.join(".") }
48
+ }
49
+ }
50
+
51
+ SUBTREES.each do |path|
52
+ node = full.dig(*path)
53
+ unless node
54
+ warn " ! subtree #{path.join('.')} not found — skipping"
55
+ next
56
+ end
57
+
58
+ # Rebuild the nested structure: ["html", "elements"] => { "html" => { "elements" => node } }
59
+ nested = path[1..].reverse.reduce(node) { |acc, key| { key => acc } }
60
+ snapshot[path.first] = (snapshot[path.first] || {}).merge(nested)
61
+ puts " + kept #{path.join('.')} (#{node.size} entries)"
62
+ end
63
+
64
+ File.write(OUTPUT, "#{JSON.pretty_generate(snapshot)}\n")
65
+ puts "Wrote #{OUTPUT} (#{File.size(OUTPUT)} bytes)"
@@ -0,0 +1,341 @@
1
+ {
2
+ "__meta": {
3
+ "source": "https://cdn.jsdelivr.net/npm/@mdn/browser-compat-data/data.json",
4
+ "generated_at": "2026-05-01T00:00:00Z",
5
+ "subtrees": ["html.elements", "html.global_attributes", "css.properties"],
6
+ "note": "A curated subset of MDN browser-compat-data. Regenerate with bin/update-bcd-snapshot."
7
+ },
8
+ "html": {
9
+ "elements": {
10
+ "dialog": {
11
+ "__compat": {
12
+ "mdn_url": "https://developer.mozilla.org/docs/Web/HTML/Element/dialog",
13
+ "status": { "baseline": "high" },
14
+ "support": {
15
+ "chrome": { "version_added": "37" },
16
+ "edge": { "version_added": "79" },
17
+ "firefox": { "version_added": "98" },
18
+ "safari": { "version_added": "15.4" }
19
+ }
20
+ }
21
+ },
22
+ "details": {
23
+ "__compat": {
24
+ "status": { "baseline": "high" },
25
+ "support": {
26
+ "chrome": { "version_added": "12" },
27
+ "edge": { "version_added": "79" },
28
+ "firefox": { "version_added": "49" },
29
+ "safari": { "version_added": "6" }
30
+ }
31
+ }
32
+ },
33
+ "summary": {
34
+ "__compat": {
35
+ "status": { "baseline": "high" },
36
+ "support": {
37
+ "chrome": { "version_added": "12" },
38
+ "edge": { "version_added": "79" },
39
+ "firefox": { "version_added": "49" },
40
+ "safari": { "version_added": "6" }
41
+ }
42
+ }
43
+ },
44
+ "search": {
45
+ "__compat": {
46
+ "mdn_url": "https://developer.mozilla.org/docs/Web/HTML/Element/search",
47
+ "status": { "baseline": "low" },
48
+ "support": {
49
+ "chrome": { "version_added": "118" },
50
+ "edge": { "version_added": "118" },
51
+ "firefox": { "version_added": "118" },
52
+ "safari": { "version_added": "17" }
53
+ }
54
+ }
55
+ },
56
+ "picture": {
57
+ "__compat": {
58
+ "status": { "baseline": "high" },
59
+ "support": {
60
+ "chrome": { "version_added": "38" },
61
+ "edge": { "version_added": "13" },
62
+ "firefox": { "version_added": "38" },
63
+ "safari": { "version_added": "9.1" }
64
+ }
65
+ }
66
+ },
67
+ "template": {
68
+ "__compat": {
69
+ "status": { "baseline": "high" },
70
+ "support": {
71
+ "chrome": { "version_added": "35" },
72
+ "edge": { "version_added": "13" },
73
+ "firefox": { "version_added": "22" },
74
+ "safari": { "version_added": "8" }
75
+ }
76
+ }
77
+ },
78
+ "slot": {
79
+ "__compat": {
80
+ "status": { "baseline": "high" },
81
+ "support": {
82
+ "chrome": { "version_added": "53" },
83
+ "edge": { "version_added": "79" },
84
+ "firefox": { "version_added": "63" },
85
+ "safari": { "version_added": "10.1" }
86
+ }
87
+ }
88
+ },
89
+ "datalist": {
90
+ "__compat": {
91
+ "status": { "baseline": "high" },
92
+ "support": {
93
+ "chrome": { "version_added": "20" },
94
+ "edge": { "version_added": "12" },
95
+ "firefox": { "version_added": "4" },
96
+ "safari": { "version_added": "12.1" }
97
+ }
98
+ }
99
+ },
100
+ "output": {
101
+ "__compat": {
102
+ "status": { "baseline": "high" },
103
+ "support": {
104
+ "chrome": { "version_added": "10" },
105
+ "edge": { "version_added": "13" },
106
+ "firefox": { "version_added": "6" },
107
+ "safari": { "version_added": "5.1" }
108
+ }
109
+ }
110
+ },
111
+ "meter": {
112
+ "__compat": {
113
+ "status": { "baseline": "high" },
114
+ "support": {
115
+ "chrome": { "version_added": "8" },
116
+ "edge": { "version_added": "13" },
117
+ "firefox": { "version_added": "16" },
118
+ "safari": { "version_added": "6" }
119
+ }
120
+ }
121
+ },
122
+ "progress": {
123
+ "__compat": {
124
+ "status": { "baseline": "high" },
125
+ "support": {
126
+ "chrome": { "version_added": "8" },
127
+ "edge": { "version_added": "12" },
128
+ "firefox": { "version_added": "6" },
129
+ "safari": { "version_added": "6" }
130
+ }
131
+ }
132
+ },
133
+ "canvas": {
134
+ "__compat": {
135
+ "status": { "baseline": "high" },
136
+ "support": {
137
+ "chrome": { "version_added": "4" },
138
+ "edge": { "version_added": "12" },
139
+ "firefox": { "version_added": "2" },
140
+ "safari": { "version_added": "3.1" }
141
+ }
142
+ }
143
+ },
144
+ "video": {
145
+ "__compat": {
146
+ "status": { "baseline": "high" },
147
+ "support": {
148
+ "chrome": { "version_added": "4" },
149
+ "edge": { "version_added": "12" },
150
+ "firefox": { "version_added": "3.5" },
151
+ "safari": { "version_added": "3.1" }
152
+ }
153
+ }
154
+ },
155
+ "audio": {
156
+ "__compat": {
157
+ "status": { "baseline": "high" },
158
+ "support": {
159
+ "chrome": { "version_added": "4" },
160
+ "edge": { "version_added": "12" },
161
+ "firefox": { "version_added": "3.5" },
162
+ "safari": { "version_added": "3.1" }
163
+ }
164
+ }
165
+ }
166
+ },
167
+ "global_attributes": {
168
+ "popover": {
169
+ "__compat": {
170
+ "mdn_url": "https://developer.mozilla.org/docs/Web/HTML/Global_attributes/popover",
171
+ "status": { "baseline": "low" },
172
+ "support": {
173
+ "chrome": { "version_added": "114" },
174
+ "edge": { "version_added": "114" },
175
+ "firefox": { "version_added": "125" },
176
+ "safari": { "version_added": "17" }
177
+ }
178
+ }
179
+ },
180
+ "inert": {
181
+ "__compat": {
182
+ "status": { "baseline": "high" },
183
+ "support": {
184
+ "chrome": { "version_added": "102" },
185
+ "edge": { "version_added": "102" },
186
+ "firefox": { "version_added": "112" },
187
+ "safari": { "version_added": "15.5" }
188
+ }
189
+ }
190
+ },
191
+ "loading": {
192
+ "__compat": {
193
+ "status": { "baseline": "high" },
194
+ "support": {
195
+ "chrome": { "version_added": "77" },
196
+ "edge": { "version_added": "79" },
197
+ "firefox": { "version_added": "75" },
198
+ "safari": { "version_added": "16.4" }
199
+ }
200
+ }
201
+ },
202
+ "enterkeyhint": {
203
+ "__compat": {
204
+ "status": { "baseline": "high" },
205
+ "support": {
206
+ "chrome": { "version_added": "77" },
207
+ "edge": { "version_added": "79" },
208
+ "firefox": { "version_added": "94" },
209
+ "safari": { "version_added": "13.1" }
210
+ }
211
+ }
212
+ },
213
+ "autocapitalize": {
214
+ "__compat": {
215
+ "status": { "baseline": "low" },
216
+ "support": {
217
+ "chrome": { "version_added": "43" },
218
+ "edge": { "version_added": "79" },
219
+ "firefox": { "version_added": "111" },
220
+ "safari": { "version_added": "5" }
221
+ }
222
+ }
223
+ },
224
+ "is": {
225
+ "__compat": {
226
+ "mdn_url": "https://developer.mozilla.org/docs/Web/HTML/Global_attributes/is",
227
+ "status": { "baseline": false },
228
+ "support": {
229
+ "chrome": { "version_added": "67" },
230
+ "edge": { "version_added": "79" },
231
+ "firefox": { "version_added": "63" },
232
+ "safari": { "version_added": false }
233
+ }
234
+ }
235
+ },
236
+ "contenteditable": {
237
+ "__compat": {
238
+ "status": { "baseline": "high" },
239
+ "support": {
240
+ "chrome": { "version_added": "4" },
241
+ "edge": { "version_added": "12" },
242
+ "firefox": { "version_added": "3" },
243
+ "safari": { "version_added": "3.1" }
244
+ }
245
+ }
246
+ },
247
+ "hidden": {
248
+ "__compat": {
249
+ "status": { "baseline": "high" },
250
+ "support": {
251
+ "chrome": { "version_added": "6" },
252
+ "edge": { "version_added": "12" },
253
+ "firefox": { "version_added": "4" },
254
+ "safari": { "version_added": "5" }
255
+ }
256
+ }
257
+ },
258
+ "draggable": {
259
+ "__compat": {
260
+ "status": { "baseline": "high" },
261
+ "support": {
262
+ "chrome": { "version_added": "4" },
263
+ "edge": { "version_added": "12" },
264
+ "firefox": { "version_added": "3.5" },
265
+ "safari": { "version_added": "6" }
266
+ }
267
+ }
268
+ }
269
+ }
270
+ },
271
+ "css": {
272
+ "properties": {
273
+ "aspect-ratio": {
274
+ "__compat": {
275
+ "status": { "baseline": "high" },
276
+ "support": {
277
+ "chrome": { "version_added": "88" },
278
+ "edge": { "version_added": "88" },
279
+ "firefox": { "version_added": "89" },
280
+ "safari": { "version_added": "15" }
281
+ }
282
+ }
283
+ },
284
+ "gap": {
285
+ "__compat": {
286
+ "status": { "baseline": "high" },
287
+ "support": {
288
+ "chrome": { "version_added": "84" },
289
+ "edge": { "version_added": "84" },
290
+ "firefox": { "version_added": "63" },
291
+ "safari": { "version_added": "14.1" }
292
+ }
293
+ }
294
+ },
295
+ "inset": {
296
+ "__compat": {
297
+ "status": { "baseline": "high" },
298
+ "support": {
299
+ "chrome": { "version_added": "87" },
300
+ "edge": { "version_added": "87" },
301
+ "firefox": { "version_added": "66" },
302
+ "safari": { "version_added": "14.1" }
303
+ }
304
+ }
305
+ },
306
+ "container-type": {
307
+ "__compat": {
308
+ "status": { "baseline": "low" },
309
+ "support": {
310
+ "chrome": { "version_added": "105" },
311
+ "edge": { "version_added": "105" },
312
+ "firefox": { "version_added": "110" },
313
+ "safari": { "version_added": "16" }
314
+ }
315
+ }
316
+ },
317
+ "accent-color": {
318
+ "__compat": {
319
+ "status": { "baseline": "high" },
320
+ "support": {
321
+ "chrome": { "version_added": "93" },
322
+ "edge": { "version_added": "93" },
323
+ "firefox": { "version_added": "92" },
324
+ "safari": { "version_added": "15.4" }
325
+ }
326
+ }
327
+ },
328
+ "text-wrap": {
329
+ "__compat": {
330
+ "status": { "baseline": "low" },
331
+ "support": {
332
+ "chrome": { "version_added": "114" },
333
+ "edge": { "version_added": "114" },
334
+ "firefox": { "version_added": "121" },
335
+ "safari": { "version_added": "17.4" }
336
+ }
337
+ }
338
+ }
339
+ }
340
+ }
341
+ }
data/exe/browsable ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "browsable"
5
+
6
+ Browsable::CLI.start(ARGV)