qss 0.3.1 → 0.4.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 298d080077a7200c16e8bb30144a56c1dd876db158ad236455f297a281ce287b
4
- data.tar.gz: 6288a783b524391c779c63cbf2ff35f3c1ef10e5934cb4f93cb51539222a2cc7
3
+ metadata.gz: c68b04b280ac5a32cc4f515273e2209c7f406800fff01205e6b26842ba0b1412
4
+ data.tar.gz: e4b7069f106a33390f8052b21fb9e569e84ef501dc957858ec82f33f638d73b2
5
5
  SHA512:
6
- metadata.gz: 961d47aad2c2bc470c07ca6a3c247d43f687b83291d792ab0222f00308f33f66933ddcabae06bf31ca6a0a559dc2a19244a03ff0f7699ac32879eccb1094954d
7
- data.tar.gz: 1d9c4be7c083119a1195f428dfc982b3965de12f105c25a962902317966c30a57a07b34358433084b7894da9fec7be7d05b70bae13b7db5a5bf22556f77033f9
6
+ metadata.gz: b98cd11a7ff2726c2939ead5f8f1a8cb93374cc26b6e1b188cd90c0c06e330feed753ff87f486db086b22aaa0b155ca3ca9f47bd25f8d309175e8bb9755bc644
7
+ data.tar.gz: ae1ad6f606a874f5fbb1b09df7463e290a2d268d97ff0e913d1ce24134d5b9354902e2636eca296d38e5c56d1f9d91093dff777b439464a9545d818ae2833fa4
data/CHANGELOG.md CHANGED
@@ -5,6 +5,31 @@ versions follow [Semantic Versioning](https://semver.org/) (before 1.0, minor ve
5
5
 
6
6
  ## [Unreleased]
7
7
 
8
+ ## [0.4.0] - 2026-10-19
9
+
10
+ ### Added
11
+ - Three-tier stylesheet audit. The auditor now resolves classes against up to three tiers, and tiers 1 and 2 are
12
+ integrity-checked and otherwise silent (they surface only as an integrity failure):
13
+ 1. **Core**: the gem's own `qss_core.css`, always known, never configured. `rake compile` marks it DO NOT EDIT and
14
+ writes `config/integrity.json` (its SHA-256, line endings normalized, plus the gem version); the auditor
15
+ verifies it before use. A mismatch, a missing file or a manifest from another version raises
16
+ `QSS::CoreIntegrityError`.
17
+ 2. **Tool**: `auditor.tool_stylesheets`, a dev tool's own compiled chrome (for example Parapet's
18
+ `qss_application.css`). The tool seals each file when it builds it with the new `qss-seal` executable (or
19
+ `QSS::Integrity.seal`), which writes `<file>.qss-seal`; `--header` also marks the file DO NOT EDIT. An unsealed or
20
+ changed file raises `QSS::ToolIntegrityError`.
21
+ 3. **Host**: `auditor.host_stylesheets`, the end consumer's own compiled output, as paths or globs relative to the
22
+ project root, plus whatever `QSS.configure(host_stylesheets: -> { ... })` returns (evaluated each time the
23
+ settings are read, in the host's own process). With nothing configured, a Rails host gets
24
+ `app/assets/builds/*.css`.
25
+ - `QSS::IntegrityError`, the base of both integrity errors, so a host's CI can rescue and report one class.
26
+
27
+ ### Changed
28
+ - Core's classes are now always known to `unresolved_class`. Before, setting `stylesheet_path` replaced core's
29
+ stylesheet, so a consumer that listed only its own file lost every core class. `stylesheet_path` and
30
+ `drywall_stylesheet_path` keep working (`stylesheet_path` counts as a host stylesheet). The Tailwind/QSS boundary
31
+ check is unchanged and still reads `drywall_stylesheet_path` only.
32
+
8
33
  ## [0.3.1] - 2026-10-19
9
34
 
10
35
  ### Fixed
data/README.md CHANGED
@@ -153,6 +153,12 @@ to a rule setting a structural property (`display`, `position`, sizing, `flex*`/
153
153
  `QSS::QssAuditor` is the older class-whitelist auditor: it predates the bracket vocabulary (it reports `qss-w[200]`
154
154
  as unknown) and is kept for projects that still use its whitelist.
155
155
 
156
+ **Three tiers of stylesheet.** Class resolution reads core's own `qss_core.css` (always, verified against the manifest
157
+ shipped with the gem, `QSS::CoreIntegrityError` on a mismatch), any sealed tool stylesheets
158
+ (`auditor.tool_stylesheets`, sealed by the tool's build with `qss-seal`, `QSS::ToolIntegrityError`), and the host's
159
+ own output (`auditor.host_stylesheets`, `stylesheet_path`, `QSS.configure(host_stylesheets: -> { ... })`; a Rails host
160
+ defaults to `app/assets/builds/*.css`).
161
+
156
162
  Appearance classes (colors, type styling, shadows, filters) are **deprecated** as of 0.2.0: the auditor reports each as
157
163
  orange `deprecated_decorative`, since appearance belongs to whatever fills Drywall; QSS's quantized bracket scale
158
164
  (`qss-border[2]`, `qss-text-size[14]`) is unaffected. A Drywall stylesheet compiled with the `qss` prefix is a red
@@ -1,4 +1,5 @@
1
1
  @charset "UTF-8";
2
+ /* GENERATED FILE - DO NOT EDIT. Any change is caught by the QSS integrity check (qss-seal) at audit time. */
2
3
  /*
3
4
  * QSS Core — Quantized Spatial Styling DNA
4
5
  * Driven by config/grammar.yml (via exe/qss-sync -> _qss_defaults.scss; a consumer overrides it)
data/config/grammar.yml CHANGED
@@ -281,6 +281,20 @@ auditor:
281
281
  # Drywall framework configured).
282
282
  drywall_stylesheet_path: ""
283
283
 
284
+ # Three-tier stylesheet audit (0.4.0). Tier 1, core's own qss_core.css, is always known and always integrity-
285
+ # checked against the manifest shipped with the gem (QSS::CoreIntegrityError); it is not configured here.
286
+ #
287
+ # Tier 2, tool_stylesheets: a dev tool's own compiled chrome (for example Parapet's qss_application.css). Paths or
288
+ # globs relative to the project root. Each must be sealed by its owner (`qss-seal`); an unsealed or changed one
289
+ # raises QSS::ToolIntegrityError. Its classes resolve silently, nothing else is reported.
290
+ tool_stylesheets: []
291
+
292
+ # Tier 3, host_stylesheets: the end consumer's own compiled output (QSS overrides and Drywall). Paths or globs
293
+ # relative to the project root, plus whatever QSS.configure(host_stylesheets: -> { ... }) returns. When both are
294
+ # empty (and stylesheet_path is too), a Rails host gets app/assets/builds/*.css. stylesheet_path above still
295
+ # works and counts as a host stylesheet.
296
+ host_stylesheets: []
297
+
284
298
  # Files exe/qss-sync generates into. core_variables, engine, auditor and native_auditor are core's own files,
285
299
  # relative to core's root; variables is the consumer's, relative to the directory the sync is run from, and a consumer
286
300
  # normally sets it in its own config file. (Keeps file locations out of the script itself.)
@@ -0,0 +1,6 @@
1
+ {
2
+ "version": "0.4.0",
3
+ "files": {
4
+ "qss_core.css": "1212d0a43bc833894dd1fe5eac7f0446d34d47dc68ee2130cf6b40aee741cd80"
5
+ }
6
+ }
data/exe/qss-seal ADDED
@@ -0,0 +1,26 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Seals a tool-tier stylesheet (a dev tool's own compiled chrome) so the QSS auditor can verify it has not been
5
+ # changed since it was built: writes <stylesheet>.qss-seal next to it. Run it as the last step of the tool's build.
6
+ #
7
+ # qss-seal path/to/qss_application.css [--header]
8
+ #
9
+ # --header also marks the file DO NOT EDIT (idempotent). An audit that finds the file changed after sealing
10
+ # raises QSS::ToolIntegrityError.
11
+ require_relative "../lib/qss/version"
12
+ require_relative "../lib/qss/integrity"
13
+
14
+ header = ARGV.delete("--header")
15
+ paths = ARGV
16
+ if paths.empty? || paths.any? { |p| p.start_with?("-") }
17
+ warn "usage: qss-seal STYLESHEET... [--header]"
18
+ exit 1
19
+ end
20
+
21
+ paths.each do |path|
22
+ puts "Sealed #{path} -> #{QSS::Integrity.seal(path, header: !header.nil?)}"
23
+ rescue QSS::IntegrityError => e
24
+ warn e.message
25
+ exit 1
26
+ end
@@ -41,8 +41,11 @@ module QSS
41
41
  class Configuration
42
42
  attr_reader :root, :config_path
43
43
 
44
- def initialize(root: nil, config: nil)
44
+ # host_stylesheets: an optional callable returning the host app's compiled stylesheet paths (or globs), called
45
+ # each time the settings are read, in the host's own process. Added to the config file's host_stylesheets.
46
+ def initialize(root: nil, config: nil, host_stylesheets: nil)
45
47
  @root = root && File.expand_path(root.to_s)
48
+ @host_stylesheets = host_stylesheets
46
49
  @config_path = config && resolve(config.to_s)
47
50
  @cache = {}
48
51
  end
@@ -83,6 +86,11 @@ module QSS
83
86
  settings["registry_path"] = resolve(settings["registry_path"])
84
87
  settings["stylesheet_path"] = resolve(settings["stylesheet_path"])
85
88
  settings["drywall_stylesheet_path"] = resolve(settings["drywall_stylesheet_path"])
89
+ settings["tool_stylesheets"] = stylesheet_list("tool_stylesheets", settings["tool_stylesheets"]) if settings.key?("tool_stylesheets")
90
+ if settings.key?("host_stylesheets")
91
+ settings["host_stylesheets"] = stylesheet_list("host_stylesheets", settings["host_stylesheets"], @host_stylesheets&.call)
92
+ settings["host_stylesheets"] = rails_default_stylesheets if settings["host_stylesheets"].empty? && settings["stylesheet_path"].empty?
93
+ end
86
94
  settings.freeze
87
95
  end
88
96
 
@@ -93,6 +101,30 @@ module QSS
93
101
 
94
102
  private
95
103
 
104
+ # tool_stylesheets / host_stylesheets: a list of paths or globs (relative to the root), expanded to the files that
105
+ # exist now, in a stable order. Anything but a list of strings is a ConfigurationError.
106
+ def stylesheet_list(key, configured, extra = nil)
107
+ entries = configured.nil? ? [] : configured
108
+ unless entries.is_a?(Array) && entries.all?(String)
109
+ raise ConfigurationError, "auditor.#{key} must be a list of paths or globs, got #{entries.inspect}"
110
+ end
111
+
112
+ (entries + Array(extra).map(&:to_s)).flat_map do |entry|
113
+ next [] if entry.empty?
114
+
115
+ path = resolve(entry)
116
+ entry.match?(/[*?\[{]/) ? Dir.glob(path).sort : [ path ]
117
+ end.uniq
118
+ end
119
+
120
+ # A Rails host that configures nothing gets its built CSS (cssbundling-rails / tailwindcss-rails output).
121
+ def rails_default_stylesheets
122
+ base = root_or_default
123
+ return [] unless base && defined?(Rails) && Rails.respond_to?(:root)
124
+
125
+ Dir.glob(File.join(base, "app", "assets", "builds", "*.css")).sort
126
+ end
127
+
96
128
  def deep_merge(base, over)
97
129
  return over unless base.is_a?(Hash) && over.is_a?(Hash)
98
130
 
@@ -105,8 +137,8 @@ module QSS
105
137
  @configuration ||= Configuration.new
106
138
  end
107
139
 
108
- def configure(root:, config: nil)
109
- @configuration = Configuration.new(root: root, config: config)
140
+ def configure(root:, config: nil, host_stylesheets: nil)
141
+ @configuration = Configuration.new(root: root, config: config, host_stylesheets: host_stylesheets)
110
142
  end
111
143
  end
112
144
  end
@@ -0,0 +1,124 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "digest"
4
+ require "json"
5
+
6
+ module QSS
7
+ # Base class for every integrity failure, so a host app's CI can rescue one thing and report it.
8
+ class IntegrityError < StandardError; end
9
+
10
+ # The gem's own compiled stylesheet no longer matches what this gem version shipped (edited, truncated, or from
11
+ # another version). QSS never audits against a core it cannot vouch for.
12
+ class CoreIntegrityError < IntegrityError; end
13
+
14
+ # A tool-tier stylesheet (a dev tool's own chrome, for example Parapet's qss_application.css) is unsealed or no
15
+ # longer matches its seal. The seal is owned by the tool: it writes it at build time with `qss-seal`.
16
+ class ToolIntegrityError < IntegrityError; end
17
+
18
+ # Tier 1 (core) and tier 2 (tool) stylesheet integrity. A stylesheet's fingerprint is the SHA-256 of its content
19
+ # with line endings normalized to LF, so a CRLF working tree (this repo's own) and an LF checkout agree.
20
+ #
21
+ # Core's fingerprint lives in config/integrity.json, written by `rake compile` and shipped in the gem together
22
+ # with the version it belongs to. A tool's lives in a sidecar `<stylesheet>.qss-seal` written by `qss-seal`.
23
+ module Integrity
24
+ CORE_CSS = File.expand_path("../../app/assets/stylesheets/qss/qss_core.css", __dir__)
25
+ CORE_MANIFEST = File.expand_path("../../config/integrity.json", __dir__)
26
+ SEAL_SUFFIX = ".qss-seal"
27
+ HEADER = "/* GENERATED FILE - DO NOT EDIT. Any change is caught by the QSS integrity check (qss-seal) at audit time. */"
28
+
29
+ VERIFIED = {}
30
+ private_constant :VERIFIED
31
+
32
+ class << self
33
+ def digest(path)
34
+ Digest::SHA256.hexdigest(File.binread(path).gsub("\r\n", "\n"))
35
+ end
36
+
37
+ # Raises CoreIntegrityError unless core's stylesheet matches the manifest shipped with this gem version.
38
+ def verify_core!
39
+ check(CORE_CSS, :core, CORE_MANIFEST) do
40
+ manifest = read_json(CORE_MANIFEST) { |why| raise CoreIntegrityError, "QSS core integrity manifest #{why}" }
41
+ unless manifest["version"] == QSS::VERSION
42
+ raise CoreIntegrityError, "QSS core integrity manifest is for qss #{manifest['version'].inspect}, " \
43
+ "this is qss #{QSS::VERSION}"
44
+ end
45
+ raise CoreIntegrityError, "QSS core stylesheet #{CORE_CSS} is missing" unless File.file?(CORE_CSS)
46
+
47
+ expected = manifest.dig("files", "qss_core.css")
48
+ raise CoreIntegrityError, "QSS core integrity manifest lists no qss_core.css" if expected.to_s.empty?
49
+ next if expected == digest(CORE_CSS)
50
+
51
+ raise CoreIntegrityError, "#{CORE_CSS} does not match the stylesheet qss #{QSS::VERSION} shipped; " \
52
+ "reinstall the gem, never edit it"
53
+ end
54
+ end
55
+
56
+ # Raises ToolIntegrityError unless the tool's stylesheet exists, is sealed, and still matches its seal.
57
+ def verify_tool!(path)
58
+ check(path, :tool) do
59
+ raise ToolIntegrityError, "tool stylesheet #{path} does not exist" unless File.file?(path)
60
+
61
+ seal = seal_path(path)
62
+ unless File.file?(seal)
63
+ raise ToolIntegrityError, "tool stylesheet #{path} is not sealed; run `qss-seal #{path}` when it is built"
64
+ end
65
+ expected = read_json(seal) { |why| raise ToolIntegrityError, "seal #{seal} #{why}" }["sha256"]
66
+ next if expected == digest(path)
67
+
68
+ raise ToolIntegrityError, "#{path} does not match its seal (#{File.basename(seal)}); it was changed after " \
69
+ "it was built. Rebuild it, then run `qss-seal`"
70
+ end
71
+ end
72
+
73
+ # Writes the seal for a tool stylesheet. With header: true, first marks the file DO NOT EDIT (idempotent).
74
+ def seal(path, header: false)
75
+ raise ToolIntegrityError, "cannot seal #{path}: no such file" unless File.file?(path)
76
+
77
+ add_header(path) if header
78
+ File.write(seal_path(path), JSON.pretty_generate("sha256" => digest(path), "sealed_by" => "qss #{QSS::VERSION}") + "\n")
79
+ VERIFIED.clear
80
+ seal_path(path)
81
+ end
82
+
83
+ # Maintainer step (rake compile / rake seal): writes core's manifest for the current qss_core.css.
84
+ def write_core_manifest!
85
+ add_header(CORE_CSS)
86
+ File.write(CORE_MANIFEST, JSON.pretty_generate("version" => QSS::VERSION, "files" => { "qss_core.css" => digest(CORE_CSS) }) + "\n")
87
+ VERIFIED.clear
88
+ end
89
+
90
+ def seal_path(path)
91
+ "#{path}#{SEAL_SUFFIX}"
92
+ end
93
+
94
+ private
95
+
96
+ # Runs the block once per (path, mtime, size): the audit calls this for every file it checks.
97
+ def check(path, tier, *also)
98
+ stamp = File.file?(path) ? [ path, *also ].map { |f| File.file?(f) ? [ File.mtime(f), File.size(f) ] : nil } : nil
99
+ return if VERIFIED[[ tier, path ]] == stamp && stamp
100
+
101
+ yield
102
+ VERIFIED[[ tier, path ]] = stamp
103
+ end
104
+
105
+ def read_json(path)
106
+ return yield("is missing (#{path})") unless File.file?(path)
107
+
108
+ JSON.parse(File.read(path))
109
+ rescue JSON::ParserError
110
+ yield("is not valid JSON (#{path})")
111
+ end
112
+
113
+ # The header goes after a leading @charset (which only counts as the very first thing in the file).
114
+ def add_header(path)
115
+ text = File.binread(path).force_encoding("UTF-8")
116
+ return if text.include?(HEADER)
117
+
118
+ eol = text.include?("\r\n") ? "\r\n" : "\n"
119
+ charset = text[/\A@charset [^;]+;\r?\n/]
120
+ File.binwrite(path, "#{charset}#{HEADER}#{eol}#{text.delete_prefix(charset.to_s)}")
121
+ end
122
+ end
123
+ end
124
+ end
@@ -123,7 +123,7 @@ module QSS
123
123
 
124
124
  BRAND_COLORS = {"black"=>"#000000", "white"=>"#ffffff", "transparent"=>"transparent", "none"=>"none", "slate-50"=>"#f8fafc", "slate-100"=>"#f1f5f9", "slate-200"=>"#e2e8f0", "slate-300"=>"#cbd5e1", "slate-400"=>"#94a3b8", "slate-500"=>"#64748b", "slate-600"=>"#475569", "slate-700"=>"#334155", "slate-800"=>"#1e293b", "slate-900"=>"#0f172a", "slate-950"=>"#020617", "rose-500"=>"#f43f5e"}.freeze
125
125
 
126
- AUDITOR_CONFIG = {"id_prefix"=>"", "structural_words"=>["header", "footer", "sidebar", "workspace", "main", "panel", "frame", "wrapper", "nav", "aside"], "narrow_words"=>["header", "footer", "sidebar", "workspace", "main", "panel", "frame", "wrapper", "nav", "aside"], "icon_prefixes"=>[], "registry_path"=>"", "stylesheet_path"=>"", "drywall_stylesheet_path"=>"", "portal_root"=>"#qss-portal"}.freeze
126
+ AUDITOR_CONFIG = {"id_prefix"=>"", "structural_words"=>["header", "footer", "sidebar", "workspace", "main", "panel", "frame", "wrapper", "nav", "aside"], "narrow_words"=>["header", "footer", "sidebar", "workspace", "main", "panel", "frame", "wrapper", "nav", "aside"], "icon_prefixes"=>[], "registry_path"=>"", "stylesheet_path"=>"", "drywall_stylesheet_path"=>"", "tool_stylesheets"=>[], "host_stylesheets"=>[], "portal_root"=>"#qss-portal"}.freeze
127
127
  # QSS-GENERATED-END
128
128
  # rubocop:enable all
129
129
 
@@ -9,7 +9,7 @@ module QSS
9
9
  # GENERATED FROM grammar.yml - DO NOT EDIT
10
10
  PAYLOAD_TAGS = ["p", "h1", "h2", "h3", "h4", "h5", "h6", "span", "label", "blockquote", "li"].freeze
11
11
 
12
- AUDITOR_CONFIG = {"id_prefix"=>"", "structural_words"=>["header", "footer", "sidebar", "workspace", "main", "panel", "frame", "wrapper", "nav", "aside"], "narrow_words"=>["header", "footer", "sidebar", "workspace", "main", "panel", "frame", "wrapper", "nav", "aside"], "icon_prefixes"=>[], "registry_path"=>"", "stylesheet_path"=>"", "drywall_stylesheet_path"=>"", "portal_root"=>"#qss-portal"}.freeze
12
+ AUDITOR_CONFIG = {"id_prefix"=>"", "structural_words"=>["header", "footer", "sidebar", "workspace", "main", "panel", "frame", "wrapper", "nav", "aside"], "narrow_words"=>["header", "footer", "sidebar", "workspace", "main", "panel", "frame", "wrapper", "nav", "aside"], "icon_prefixes"=>[], "registry_path"=>"", "stylesheet_path"=>"", "drywall_stylesheet_path"=>"", "tool_stylesheets"=>[], "host_stylesheets"=>[], "portal_root"=>"#qss-portal"}.freeze
13
13
  # QSS-GENERATED-END
14
14
  # rubocop:enable all
15
15
 
@@ -455,6 +455,7 @@ module QSS
455
455
 
456
456
  def self.load(path)
457
457
  path = path.to_s.empty? ? CORE_CSS_PATH : path
458
+ QSS::Integrity.verify_core! if path == CORE_CSS_PATH
458
459
  return Set.new unless File.exist?(path)
459
460
  mtime = File.mtime(path)
460
461
  cached = CACHE[path]
@@ -914,15 +915,18 @@ module QSS
914
915
  narrow_pattern = narrow_structural_pattern(settings)
915
916
  portal_label = portal_label(settings)
916
917
  registry = Registry.load(settings['registry_path'])
917
- known_classes = Stylesheet.load(settings['stylesheet_path'])
918
+ # Three tiers (0.4.0). Core's own CSS is always known and always integrity-checked; a tool's sealed chrome
919
+ # resolves silently; the host's own output (host_stylesheets, plus the older stylesheet_path) is what its markup
920
+ # renders against. A class resolving in the Drywall framework's own compiled CSS is just as real (DESIGN_HISTORY
921
+ # #12.8: unresolved_class once ignored drywall_stylesheet_path, flagging every real Tailwind class dead).
918
922
  drywall_stylesheet_path = settings['drywall_stylesheet_path'].to_s
919
- # A class resolving in the Drywall framework's own compiled CSS is just as real as one resolving in QSS's
920
- # own -- unresolved_class checked only stylesheet_path before this, so every genuinely real, genuinely
921
- # compiled Drywall decoration class (color, typography, spacing, ...) was flagged dead regardless (confirmed
922
- # bug, DESIGN_HISTORY #12.8: reproduced against a real Tailwind build, outside any consumer's own debt).
923
+ host_stylesheets = ([ settings['stylesheet_path'].to_s ] + Array(settings['host_stylesheets'])).reject(&:empty?).uniq
924
+ Array(settings['tool_stylesheets']).each { |path| QSS::Integrity.verify_tool!(path) }
925
+ known_classes = Stylesheet.load('')
926
+ (Array(settings['tool_stylesheets']) + host_stylesheets).each { |path| known_classes |= Stylesheet.load(path) }
923
927
  known_classes |= Stylesheet.load(drywall_stylesheet_path) unless drywall_stylesheet_path.empty?
924
928
  drywall_properties = Stylesheet.load_properties(settings['drywall_stylesheet_path'])
925
- checked_stylesheets = [ settings['stylesheet_path'].to_s.empty? ? "core's qss_core.css" : settings['stylesheet_path'] ]
929
+ checked_stylesheets = [ "core's qss_core.css" ] + host_stylesheets
926
930
  checked_stylesheets << drywall_stylesheet_path unless drywall_stylesheet_path.empty?
927
931
  stylesheet_label = checked_stylesheets.size > 1 ? "either compiled stylesheet (#{checked_stylesheets.join(' or ')})" : "the compiled stylesheet (#{checked_stylesheets.first})"
928
932
  exempt_prefixes = [ prefix, IdRules::FRAMEWORK_PREFIX ]
data/lib/qss/version.rb CHANGED
@@ -3,7 +3,7 @@
3
3
  module QSS
4
4
  # The gem's version. QSS::VERSION is the usual spelling; QSS::Version::STRING the same value.
5
5
  module Version
6
- STRING = "0.3.1"
6
+ STRING = "0.4.0"
7
7
  end
8
8
 
9
9
  VERSION = Version::STRING
data/lib/qss.rb CHANGED
@@ -10,6 +10,7 @@ require "json"
10
10
  require "qss/version" # QSS::Version, QSS::VERSION
11
11
  require "qss/blueprints" # QSS::Blueprints (QSS.grid, QSS.object: layout arithmetic, object blueprints)
12
12
  require "qss/configuration" # QSS::Configuration (the consuming project's settings, read at runtime)
13
+ require "qss/integrity" # QSS::Integrity (core and tool stylesheet integrity; QSS::IntegrityError)
13
14
  require "qss/qss_auditor" # QSS::QssAuditor (DSL-whitelist auditor)
14
15
  require "qss/qss_native_auditor" # QSS::QssNativeAuditor (native-CSS auditor)
15
16
  require "qss/engine" if defined?(::Rails::Engine) # QSS::Engine (Rails only: serves QSS's assets)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: qss
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.1
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kraig Fairhurst
@@ -108,6 +108,7 @@ email:
108
108
  executables:
109
109
  - qss-core-render-check
110
110
  - qss-engine-render-check
111
+ - qss-seal
111
112
  - qss-sync
112
113
  extensions: []
113
114
  extra_rdoc_files: []
@@ -122,14 +123,17 @@ files:
122
123
  - app/assets/stylesheets/qss/qss_core.css
123
124
  - app/assets/stylesheets/qss/qss_core.scss
124
125
  - config/grammar.yml
126
+ - config/integrity.json
125
127
  - config/qss-objects.json
126
128
  - exe/qss-core-render-check
127
129
  - exe/qss-engine-render-check
130
+ - exe/qss-seal
128
131
  - exe/qss-sync
129
132
  - lib/qss.rb
130
133
  - lib/qss/blueprints.rb
131
134
  - lib/qss/configuration.rb
132
135
  - lib/qss/engine.rb
136
+ - lib/qss/integrity.rb
133
137
  - lib/qss/qss_auditor.rb
134
138
  - lib/qss/qss_native_auditor.rb
135
139
  - lib/qss/version.rb