From ddd1963eba2d0469ed46b6343e6ef18c604db54a Mon Sep 17 00:00:00 2001 From: Michael Ball Date: Sat, 1 Aug 2026 04:11:54 +0000 Subject: [PATCH 1/3] refactor: migrate CSS compilation from sassc to dartsass-sprockets - Replace deprecated `sassc-rails` (LibSass) with `dartsass-sprockets`. - Update `application.scss` to use modern Sass module syntax (`@use "sass:color"`). - Rename DataTables CSS files to SCSS partials to ensure they are inlined during compilation, fixing a production 404 bug. - Add `config/initializers/dartsass.rb` to silence Bootstrap `@import` deprecation warnings. - Fix SVG data URI encoding by adding `%` to `$escaped-characters` to handle Dart Sass color serialization. - Remove LibSass-specific `unquote()` workarounds for CSS variables. Co-authored-by: Claude Code --- Gemfile | 17 ++++-- Gemfile.lock | 52 ++++++++++++++++--- app/assets/stylesheets/application.scss | 36 ++++++++++--- .../datatables/_dataTables.bootstrap5.scss | 4 ++ .../datatables/_responsive.bootstrap5.scss | 0 app/views/layouts/application.html.erb | 12 ----- config/initializers/dartsass.rb | 18 +++++++ 7 files changed, 109 insertions(+), 30 deletions(-) rename public/datatables/dataTables.bootstrap5.css => app/assets/stylesheets/datatables/_dataTables.bootstrap5.scss (98%) rename public/datatables/responsive.bootstrap5.css => app/assets/stylesheets/datatables/_responsive.bootstrap5.scss (100%) create mode 100644 config/initializers/dartsass.rb diff --git a/Gemfile b/Gemfile index 325e916b..9808f109 100644 --- a/Gemfile +++ b/Gemfile @@ -91,10 +91,19 @@ gem 'importmap-rails' gem 'turbo-rails' gem 'bootstrap', '~> 5.3.8' -# dependency for bootstrap # 03-10-2025 this is deprecated but still works -gem 'sassc-rails', '~> 2.1' -# alternative to sassc-rails, this is recommended but bootstrap 5.3.3 is still using "deprecated" @import statements which this gem doesn't like -# gem 'dartsass-sprockets' +# Compiles the SCSS in app/assets/stylesheets with Dart Sass (the only Sass +# implementation still maintained) via Sprockets. Replaces sassc-rails, which +# was deprecated along with its LibSass backend. Everything else about the +# pipeline is unchanged: same stylesheet_link_tag, same manifest.js. +# +# `sassc` itself is still resolved as a transitive dependency (of both +# sassc-embedded and font-awesome-sass), but only as an API shim — LibSass no +# longer compiles anything. +# +# Bootstrap 5.3 still uses `@import`, which Dart Sass deprecated; those warnings +# are silenced in config/initializers/dartsass.rb. Bootstrap 6 moves to `@use`, +# at which point the silencing can go away. +gem 'dartsass-sprockets', '~> 3.2' # Font Awesome for icons gem 'font-awesome-sass' diff --git a/Gemfile.lock b/Gemfile.lock index 65744166..29d77f77 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -169,6 +169,12 @@ GEM cucumber (>= 7, < 12) railties (>= 6.1, < 9) cucumber-tag-expressions (8.1.0) + dartsass-sprockets (3.2.1) + railties (>= 4.0.0) + sassc-embedded (~> 1.80.1) + sprockets (> 3.0) + sprockets-rails + tilt database_cleaner-active_record (2.2.2) activerecord (>= 5.a) database_cleaner-core (~> 2.0) @@ -227,6 +233,27 @@ GEM fugit (>= 1.11.0) railties (>= 6.1.0) thor (>= 1.0.0) + google-protobuf (4.35.1) + bigdecimal + rake (~> 13.3) + google-protobuf (4.35.1-aarch64-linux-gnu) + bigdecimal + rake (~> 13.3) + google-protobuf (4.35.1-arm64-darwin) + bigdecimal + rake (~> 13.3) + google-protobuf (4.35.1-x86-linux-gnu) + bigdecimal + rake (~> 13.3) + google-protobuf (4.35.1-x86-mingw32) + bigdecimal + rake (~> 13.3) + google-protobuf (4.35.1-x86_64-darwin) + bigdecimal + rake (~> 13.3) + google-protobuf (4.35.1-x86_64-linux-gnu) + bigdecimal + rake (~> 13.3) guard (2.20.2) formatador (>= 0.2.4) listen (>= 2.7, < 4.0) @@ -532,14 +559,23 @@ GEM ruby-progressbar (1.13.0) rubyzip (3.4.1) safely_block (1.0.0) + sass-embedded (1.102.0) + google-protobuf (~> 4.31) + rake (>= 13) + sass-embedded (1.102.0-aarch64-linux-gnu) + google-protobuf (~> 4.31) + sass-embedded (1.102.0-arm-linux-gnueabihf) + google-protobuf (~> 4.31) + sass-embedded (1.102.0-arm64-darwin) + google-protobuf (~> 4.31) + sass-embedded (1.102.0-x86_64-darwin) + google-protobuf (~> 4.31) + sass-embedded (1.102.0-x86_64-linux-gnu) + google-protobuf (~> 4.31) sassc (2.4.0) ffi (~> 1.9) - sassc-rails (2.1.2) - railties (>= 4.0.0) - sassc (>= 2.0) - sprockets (> 3.0) - sprockets-rails - tilt + sassc-embedded (1.80.9) + sass-embedded (~> 1.80) securerandom (0.4.1) selenium-webdriver (4.46.0) base64 (~> 0.2) @@ -582,7 +618,7 @@ GEM memoist3 (~> 1.0.0) win32ole thor (1.5.0) - tilt (2.7.0) + tilt (2.8.0) timecop (0.9.11) timeout (0.6.1) tsort (0.2.0) @@ -638,6 +674,7 @@ DEPENDENCIES capybara-screenshot codeclimate-test-reporter cucumber-rails + dartsass-sprockets (~> 3.2) database_cleaner-active_record debug dotenv-rails @@ -672,7 +709,6 @@ DEPENDENCIES rubocop-performance rubocop-rails-omakase rubocop-rspec - sassc-rails (~> 2.1) selenium-webdriver sentry-rails sentry-ruby diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss index fd5316d0..79b12f65 100644 --- a/app/assets/stylesheets/application.scss +++ b/app/assets/stylesheets/application.scss @@ -8,6 +8,11 @@ // Do NOT use Sprockets require directives (require_tree, require_self) // in this file — they bypass SCSS compilation and break variable sharing. +// Dart Sass deprecated the global color functions; `color.mix` is the +// replacement for `mix`. @use has to precede every rule except variable +// declarations, so it stays at the top even though the @imports are below. +@use "sass:color"; + /** * UC Berkeley color palette based on the official brand guidelines: * https://brand.berkeley.edu/colors/ @@ -91,13 +96,32 @@ $code-color-dark: #ea868f; // invisible on the dark track. Use a solid white knob instead. $form-switch-color-dark: $white; +// Bootstrap inlines a handful of icons (accordion chevron, switch knob, select +// caret) as SVG data URIs, colored by variables that are computed with mix(). +// Dart Sass keeps the fractional channels mix() produces, so those colors +// serialize as `rgb(50%, 57.45%, 73.14%)` where LibSass emitted a hex triplet +// — which drops a bare `%` into a URI, and `%` alone is not a valid escape +// sequence. Browsers pass it through, but escaping `%` first (the list is +// applied in order) keeps the emitted data URIs well-formed. +$escaped-characters: ( + ("%", "%25"), + ("<", "%3c"), + (">", "%3e"), + ("#", "%23"), + ("(", "%28"), + (")", "%29"), +); + @import "bootstrap"; + +// DataTables' Bootstrap 5 theme overrides Bootstrap's own table styles, so it +// has to come after Bootstrap and before our overrides in custom_bootstrap. +@import "datatables/dataTables.bootstrap5"; +@import "datatables/responsive.bootstrap5"; + @import "custom_bootstrap"; @import "font-awesome"; -// NOTE: DataTables CSS is intentionally not imported here. See the comment in -// app/views/layouts/application.html.erb for why it is served from public/. - @each $name, $value in $colors { .bg-#{$name} { background-color: $value; @@ -112,14 +136,14 @@ $form-switch-color-dark: $white; .table-#{$name} { --bs-table-bg: #{$value}; --bs-table-color: #{color-contrast($value)}; - --bs-table-border-color: #{mix(black, $value, 15%)}; + --bs-table-border-color: #{color.mix(black, $value, 15%)}; &, > th, > td { --bs-table-bg: #{$value}; --bs-table-color: #{color-contrast($value)}; - --bs-table-border-color: #{mix(black, $value, 15%)}; + --bs-table-border-color: #{color.mix(black, $value, 15%)}; } } @@ -131,7 +155,7 @@ $form-switch-color-dark: $white; > tr > td { --bs-table-bg: #{$value}; --bs-table-color: #{color-contrast($value)}; - --bs-table-border-color: #{mix(black, $value, 15%)}; + --bs-table-border-color: #{color.mix(black, $value, 15%)}; } } } diff --git a/public/datatables/dataTables.bootstrap5.css b/app/assets/stylesheets/datatables/_dataTables.bootstrap5.scss similarity index 98% rename from public/datatables/dataTables.bootstrap5.css rename to app/assets/stylesheets/datatables/_dataTables.bootstrap5.scss index ed11e6ea..fae42b28 100644 --- a/public/datatables/dataTables.bootstrap5.css +++ b/app/assets/stylesheets/datatables/_dataTables.bootstrap5.scss @@ -1,3 +1,7 @@ +/* Vendored from the datatables.net-bs5 package. Kept as an SCSS partial so it + is inlined into application.css rather than emitted as a separate @import. + Dart Sass passes rgb()/rgba() containing var() through untouched, so the + values below need no wrapping. */ :root { --dt-row-selected: 13, 110, 253; --dt-row-selected-text: 255, 255, 255; diff --git a/public/datatables/responsive.bootstrap5.css b/app/assets/stylesheets/datatables/_responsive.bootstrap5.scss similarity index 100% rename from public/datatables/responsive.bootstrap5.css rename to app/assets/stylesheets/datatables/_responsive.bootstrap5.scss diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index 7824c9ed..c5b63edb 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -8,18 +8,6 @@ <%= csp_meta_tag %> <%= stylesheet_link_tag "application" %> - <%# DataTables CSS is served straight from public/ rather than through the - asset pipeline, on purpose. Importing it from application.scss does NOT - work: Sass treats `@import "foo.css"` as a passthrough and emits a literal - `@import url(datatables/...css)` into the compiled stylesheet, pointing at - a path that is never precompiled -- so it 404s in production, where - config.assets.compile is false. Dropping the .css extension so Sass inlines - it instead fails to compile under sassc-rails. Keep these as plain - tags until the app moves off sassc-rails. Versions must be kept in sync - with the datatables.net-bs5 / datatables.net-responsive-bs5 pins in - config/importmap.rb. %> - - <%= javascript_importmap_tags %> <%= render "layouts/components/color_mode_critical" %> diff --git a/config/initializers/dartsass.rb b/config/initializers/dartsass.rb new file mode 100644 index 00000000..700cd372 --- /dev/null +++ b/config/initializers/dartsass.rb @@ -0,0 +1,18 @@ +# Be sure to restart your server when you modify this file. + +# Configuration for dartsass-sprockets, which compiles app/assets/stylesheets +# with Dart Sass. It keeps sassc-rails' `config.sass` namespace, so the options +# below are the ones sassc-rails understood plus Dart Sass' deprecation +# controls. + +# Don't report deprecations raised inside gem-provided stylesheets (Bootstrap, +# Font Awesome). We can't fix those, and Bootstrap 5.3 alone emits thousands of +# lines of them per compile. +Rails.application.config.sass.quiet_deps = true + +# `quiet_deps` only covers files loaded from the load path, so the `@import` +# lines in application.scss itself still warn. Silence that one deprecation +# everywhere; every other deprecation in our own stylesheets stays visible. +# +# Drop this once Bootstrap 6 ships and the `@import`s can become `@use`. +Rails.application.config.sass.silence_deprecations = [ 'import' ] From 9388a3ae9958391276b8d2f8f34f10a457159a79 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 5 Aug 2026 22:23:21 +0000 Subject: [PATCH 2/3] refactor: load DataTables CSS from npm instead of vendoring it The app carried hand-maintained copies of dataTables.bootstrap5.css and responsive.bootstrap5.css under app/assets/stylesheets. They had already drifted from the JavaScript pinned in config/importmap.rb, and one of them carried a libsass workaround (unquote() around rgb(var(--x))) that Dart Sass does not need. Delete both and @import the stylesheets straight out of node_modules instead, with the two package directories added to the Sass load path. The npm versions are pinned exactly to the versions pinned in config/importmap.rb -- 2.3.1 and 3.0.4 -- so the CSS and the JavaScript stay in lockstep and both move in a single, reviewable bump. This makes node_modules a prerequisite for compiling CSS, so: - the Docker build stage installs nodejs/npm and runs npm ci --omit=dev. It runs after COPY . . so that npm ci (which wipes node_modules first) also clears any node_modules that rode along in the build context; the runtime image still carries no Node. - the rspec, cucumber and a11y workflows set up Node and npm ci - bin/setup runs npm install Realigning to 2.3.1 changes the compiled CSS by 14 tokens: DataTables added .dt-left/.dt-justify header alignment rules and tightened the sort-arrow gutter from 20px to .25rem. Verified against origin/main with the same app code: rspec 619/0, the a11y suite 40/0 with all 40 screenshots pixel-compared (17 byte-identical, the rest differing only in generated timestamps and the slightly narrower sort-arrow gutter), cucumber unchanged at the same 7 pre-existing failures. Production precompiles clean, boots, and serves one stylesheet with DataTables inlined and no stray @import. Co-Authored-By: Claude --- .github/workflows/a11y.yml | 7 + .github/workflows/cucumber.yml | 7 + .github/workflows/rspec.yml | 7 + Dockerfile | 13 +- app/assets/stylesheets/application.scss | 10 +- .../datatables/_dataTables.bootstrap5.scss | 573 ------------------ .../datatables/_responsive.bootstrap5.scss | 203 ------- bin/setup | 4 + config/initializers/dartsass.rb | 10 + package-lock.json | 95 ++- package.json | 9 +- 11 files changed, 124 insertions(+), 814 deletions(-) delete mode 100644 app/assets/stylesheets/datatables/_dataTables.bootstrap5.scss delete mode 100644 app/assets/stylesheets/datatables/_responsive.bootstrap5.scss diff --git a/.github/workflows/a11y.yml b/.github/workflows/a11y.yml index 33bc9d11..e7a0fa6c 100644 --- a/.github/workflows/a11y.yml +++ b/.github/workflows/a11y.yml @@ -34,6 +34,13 @@ jobs: with: ruby-version: .tool-versions bundler-cache: true + - name: Set up Node + uses: actions/setup-node@v4 + with: + node-version: '20' + cache: npm + - name: Install npm packages + run: npm ci --omit=dev - name: Set up Database run: bundle exec rails db:setup - name: Create separate Chrome data directories diff --git a/.github/workflows/cucumber.yml b/.github/workflows/cucumber.yml index 6e511402..0bbd6d09 100644 --- a/.github/workflows/cucumber.yml +++ b/.github/workflows/cucumber.yml @@ -37,6 +37,13 @@ jobs: with: ruby-version: .tool-versions bundler-cache: true + - name: Set up Node + uses: actions/setup-node@v4 + with: + node-version: '20' + cache: npm + - name: Install npm packages + run: npm ci --omit=dev - name: Install Chrome and ChromeDriver uses: browser-actions/setup-chrome@v1 with: diff --git a/.github/workflows/rspec.yml b/.github/workflows/rspec.yml index 78438670..1c1476e4 100644 --- a/.github/workflows/rspec.yml +++ b/.github/workflows/rspec.yml @@ -37,6 +37,13 @@ jobs: with: ruby-version: .tool-versions bundler-cache: true + - name: Set up Node + uses: actions/setup-node@v4 + with: + node-version: '20' + cache: npm + - name: Install npm packages + run: npm ci --omit=dev - name: Setup Database run: | bundle exec rails db:test:prepare diff --git a/Dockerfile b/Dockerfile index 7f3d9f5a..5e32a426 100644 --- a/Dockerfile +++ b/Dockerfile @@ -17,9 +17,12 @@ ENV RAILS_ENV="production" \ # Throw-away build stage to reduce size of final image FROM base as build -# Install packages needed to build gems +# Install packages needed to build gems. nodejs/npm are here for the DataTables +# stylesheets only: application.scss @imports them out of node_modules, so they +# have to be installed before assets:precompile. This is a build-stage-only +# dependency -- the runtime image below carries no Node. RUN apt-get update -qq && \ - apt-get install --no-install-recommends -y build-essential git libvips pkg-config + apt-get install --no-install-recommends -y build-essential git libvips pkg-config nodejs npm # Install application gems COPY Gemfile Gemfile.lock ./ @@ -30,6 +33,12 @@ RUN bundle install && \ # Copy application code COPY . . +# Install the npm packages that supply the DataTables stylesheets. --omit=dev +# skips the documentation tooling the image does not need. This runs after +# COPY so that npm ci (which wipes node_modules first) also clears out any +# node_modules that came along in the build context. +RUN npm ci --omit=dev + # Precompile bootsnap code for faster boot times RUN bundle exec bootsnap precompile app/ lib/ diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss index 79b12f65..5b38bc4b 100644 --- a/app/assets/stylesheets/application.scss +++ b/app/assets/stylesheets/application.scss @@ -114,10 +114,12 @@ $escaped-characters: ( @import "bootstrap"; -// DataTables' Bootstrap 5 theme overrides Bootstrap's own table styles, so it -// has to come after Bootstrap and before our overrides in custom_bootstrap. -@import "datatables/dataTables.bootstrap5"; -@import "datatables/responsive.bootstrap5"; +// DataTables' Bootstrap 5 theme, imported straight out of node_modules (see +// config/initializers/dartsass.rb for the load path). It overrides Bootstrap's +// own table styles, so it has to come after Bootstrap and before our overrides +// in custom_bootstrap. +@import "dataTables.bootstrap5"; +@import "responsive.bootstrap5"; @import "custom_bootstrap"; @import "font-awesome"; diff --git a/app/assets/stylesheets/datatables/_dataTables.bootstrap5.scss b/app/assets/stylesheets/datatables/_dataTables.bootstrap5.scss deleted file mode 100644 index fae42b28..00000000 --- a/app/assets/stylesheets/datatables/_dataTables.bootstrap5.scss +++ /dev/null @@ -1,573 +0,0 @@ -/* Vendored from the datatables.net-bs5 package. Kept as an SCSS partial so it - is inlined into application.css rather than emitted as a separate @import. - Dart Sass passes rgb()/rgba() containing var() through untouched, so the - values below need no wrapping. */ -:root { - --dt-row-selected: 13, 110, 253; - --dt-row-selected-text: 255, 255, 255; - --dt-row-selected-link: 9, 10, 11; - --dt-row-stripe: 0, 0, 0; - --dt-row-hover: 0, 0, 0; - --dt-column-ordering: 0, 0, 0; - --dt-html-background: white; -} -:root.dark { - --dt-html-background: rgb(33, 37, 41); -} - -table.dataTable td.dt-control { - text-align: center; - cursor: pointer; -} -table.dataTable td.dt-control:before { - display: inline-block; - box-sizing: border-box; - content: ""; - border-top: 5px solid transparent; - border-left: 10px solid rgba(0, 0, 0, 0.5); - border-bottom: 5px solid transparent; - border-right: 0px solid transparent; -} -table.dataTable tr.dt-hasChild td.dt-control:before { - border-top: 10px solid rgba(0, 0, 0, 0.5); - border-left: 5px solid transparent; - border-bottom: 0px solid transparent; - border-right: 5px solid transparent; -} -table.dataTable tfoot:empty { - display: none; -} - -html.dark table.dataTable td.dt-control:before, -:root[data-bs-theme=dark] table.dataTable td.dt-control:before, -:root[data-theme=dark] table.dataTable td.dt-control:before { - border-left-color: rgba(255, 255, 255, 0.5); -} -html.dark table.dataTable tr.dt-hasChild td.dt-control:before, -:root[data-bs-theme=dark] table.dataTable tr.dt-hasChild td.dt-control:before, -:root[data-theme=dark] table.dataTable tr.dt-hasChild td.dt-control:before { - border-top-color: rgba(255, 255, 255, 0.5); - border-left-color: transparent; -} - -div.dt-scroll { - width: 100%; -} - -div.dt-scroll-body thead tr, -div.dt-scroll-body tfoot tr { - height: 0; -} -div.dt-scroll-body thead tr th, div.dt-scroll-body thead tr td, -div.dt-scroll-body tfoot tr th, -div.dt-scroll-body tfoot tr td { - height: 0 !important; - padding-top: 0px !important; - padding-bottom: 0px !important; - border-top-width: 0px !important; - border-bottom-width: 0px !important; -} -div.dt-scroll-body thead tr th div.dt-scroll-sizing, div.dt-scroll-body thead tr td div.dt-scroll-sizing, -div.dt-scroll-body tfoot tr th div.dt-scroll-sizing, -div.dt-scroll-body tfoot tr td div.dt-scroll-sizing { - height: 0 !important; - overflow: hidden !important; -} - -table.dataTable thead > tr > th:active, -table.dataTable thead > tr > td:active { - outline: none; -} -table.dataTable thead > tr > th.dt-orderable-asc span.dt-column-order:before, table.dataTable thead > tr > th.dt-ordering-asc span.dt-column-order:before, -table.dataTable thead > tr > td.dt-orderable-asc span.dt-column-order:before, -table.dataTable thead > tr > td.dt-ordering-asc span.dt-column-order:before { - position: absolute; - display: block; - bottom: 50%; - content: "\25B2"; - content: "\25B2"/""; -} -table.dataTable thead > tr > th.dt-orderable-desc span.dt-column-order:after, table.dataTable thead > tr > th.dt-ordering-desc span.dt-column-order:after, -table.dataTable thead > tr > td.dt-orderable-desc span.dt-column-order:after, -table.dataTable thead > tr > td.dt-ordering-desc span.dt-column-order:after { - position: absolute; - display: block; - top: 50%; - content: "\25BC"; - content: "\25BC"/""; -} -table.dataTable thead > tr > th.dt-orderable-asc span.dt-column-order, table.dataTable thead > tr > th.dt-orderable-desc span.dt-column-order, table.dataTable thead > tr > th.dt-ordering-asc span.dt-column-order, table.dataTable thead > tr > th.dt-ordering-desc span.dt-column-order, -table.dataTable thead > tr > td.dt-orderable-asc span.dt-column-order, -table.dataTable thead > tr > td.dt-orderable-desc span.dt-column-order, -table.dataTable thead > tr > td.dt-ordering-asc span.dt-column-order, -table.dataTable thead > tr > td.dt-ordering-desc span.dt-column-order { - position: relative; - width: 12px; - height: 20px; -} -table.dataTable thead > tr > th.dt-orderable-asc span.dt-column-order:before, table.dataTable thead > tr > th.dt-orderable-asc span.dt-column-order:after, table.dataTable thead > tr > th.dt-orderable-desc span.dt-column-order:before, table.dataTable thead > tr > th.dt-orderable-desc span.dt-column-order:after, table.dataTable thead > tr > th.dt-ordering-asc span.dt-column-order:before, table.dataTable thead > tr > th.dt-ordering-asc span.dt-column-order:after, table.dataTable thead > tr > th.dt-ordering-desc span.dt-column-order:before, table.dataTable thead > tr > th.dt-ordering-desc span.dt-column-order:after, -table.dataTable thead > tr > td.dt-orderable-asc span.dt-column-order:before, -table.dataTable thead > tr > td.dt-orderable-asc span.dt-column-order:after, -table.dataTable thead > tr > td.dt-orderable-desc span.dt-column-order:before, -table.dataTable thead > tr > td.dt-orderable-desc span.dt-column-order:after, -table.dataTable thead > tr > td.dt-ordering-asc span.dt-column-order:before, -table.dataTable thead > tr > td.dt-ordering-asc span.dt-column-order:after, -table.dataTable thead > tr > td.dt-ordering-desc span.dt-column-order:before, -table.dataTable thead > tr > td.dt-ordering-desc span.dt-column-order:after { - left: 0; - opacity: 0.125; - line-height: 9px; - font-size: 0.8em; -} -table.dataTable thead > tr > th.dt-orderable-asc, table.dataTable thead > tr > th.dt-orderable-desc, -table.dataTable thead > tr > td.dt-orderable-asc, -table.dataTable thead > tr > td.dt-orderable-desc { - cursor: pointer; -} -table.dataTable thead > tr > th.dt-orderable-asc:hover, table.dataTable thead > tr > th.dt-orderable-desc:hover, -table.dataTable thead > tr > td.dt-orderable-asc:hover, -table.dataTable thead > tr > td.dt-orderable-desc:hover { - outline: 2px solid rgba(0, 0, 0, 0.05); - outline-offset: -2px; -} -table.dataTable thead > tr > th.dt-ordering-asc span.dt-column-order:before, table.dataTable thead > tr > th.dt-ordering-desc span.dt-column-order:after, -table.dataTable thead > tr > td.dt-ordering-asc span.dt-column-order:before, -table.dataTable thead > tr > td.dt-ordering-desc span.dt-column-order:after { - opacity: 0.6; -} -table.dataTable thead > tr > th.sorting_desc_disabled span.dt-column-order:after, table.dataTable thead > tr > th.sorting_asc_disabled span.dt-column-order:before, -table.dataTable thead > tr > td.sorting_desc_disabled span.dt-column-order:after, -table.dataTable thead > tr > td.sorting_asc_disabled span.dt-column-order:before { - display: none; -} -table.dataTable thead > tr > th:active, -table.dataTable thead > tr > td:active { - outline: none; -} - -table.dataTable thead > tr > th div.dt-column-header, -table.dataTable thead > tr > th div.dt-column-footer, -table.dataTable thead > tr > td div.dt-column-header, -table.dataTable thead > tr > td div.dt-column-footer, -table.dataTable tfoot > tr > th div.dt-column-header, -table.dataTable tfoot > tr > th div.dt-column-footer, -table.dataTable tfoot > tr > td div.dt-column-header, -table.dataTable tfoot > tr > td div.dt-column-footer { - display: flex; - justify-content: space-between; - align-items: center; - gap: 4px; -} -table.dataTable thead > tr > th div.dt-column-header span.dt-column-title, -table.dataTable thead > tr > th div.dt-column-footer span.dt-column-title, -table.dataTable thead > tr > td div.dt-column-header span.dt-column-title, -table.dataTable thead > tr > td div.dt-column-footer span.dt-column-title, -table.dataTable tfoot > tr > th div.dt-column-header span.dt-column-title, -table.dataTable tfoot > tr > th div.dt-column-footer span.dt-column-title, -table.dataTable tfoot > tr > td div.dt-column-header span.dt-column-title, -table.dataTable tfoot > tr > td div.dt-column-footer span.dt-column-title { - flex-grow: 1; -} -table.dataTable thead > tr > th div.dt-column-header span.dt-column-title:empty, -table.dataTable thead > tr > th div.dt-column-footer span.dt-column-title:empty, -table.dataTable thead > tr > td div.dt-column-header span.dt-column-title:empty, -table.dataTable thead > tr > td div.dt-column-footer span.dt-column-title:empty, -table.dataTable tfoot > tr > th div.dt-column-header span.dt-column-title:empty, -table.dataTable tfoot > tr > th div.dt-column-footer span.dt-column-title:empty, -table.dataTable tfoot > tr > td div.dt-column-header span.dt-column-title:empty, -table.dataTable tfoot > tr > td div.dt-column-footer span.dt-column-title:empty { - display: none; -} - -div.dt-scroll-body > table.dataTable > thead > tr > th, -div.dt-scroll-body > table.dataTable > thead > tr > td { - overflow: hidden; -} - -:root.dark table.dataTable thead > tr > th.dt-orderable-asc:hover, :root.dark table.dataTable thead > tr > th.dt-orderable-desc:hover, -:root.dark table.dataTable thead > tr > td.dt-orderable-asc:hover, -:root.dark table.dataTable thead > tr > td.dt-orderable-desc:hover, -:root[data-bs-theme=dark] table.dataTable thead > tr > th.dt-orderable-asc:hover, -:root[data-bs-theme=dark] table.dataTable thead > tr > th.dt-orderable-desc:hover, -:root[data-bs-theme=dark] table.dataTable thead > tr > td.dt-orderable-asc:hover, -:root[data-bs-theme=dark] table.dataTable thead > tr > td.dt-orderable-desc:hover { - outline: 2px solid rgba(255, 255, 255, 0.05); -} - -div.dt-processing { - position: absolute; - top: 50%; - left: 50%; - width: 200px; - margin-left: -100px; - margin-top: -22px; - text-align: center; - padding: 2px; - z-index: 10; -} -div.dt-processing > div:last-child { - position: relative; - width: 80px; - height: 15px; - margin: 1em auto; -} -div.dt-processing > div:last-child > div { - position: absolute; - top: 0; - width: 13px; - height: 13px; - border-radius: 50%; - background: rgb(13, 110, 253); - background: rgb(var(--dt-row-selected)); - animation-timing-function: cubic-bezier(0, 1, 1, 0); -} -div.dt-processing > div:last-child > div:nth-child(1) { - left: 8px; - animation: datatables-loader-1 0.6s infinite; -} -div.dt-processing > div:last-child > div:nth-child(2) { - left: 8px; - animation: datatables-loader-2 0.6s infinite; -} -div.dt-processing > div:last-child > div:nth-child(3) { - left: 32px; - animation: datatables-loader-2 0.6s infinite; -} -div.dt-processing > div:last-child > div:nth-child(4) { - left: 56px; - animation: datatables-loader-3 0.6s infinite; -} - -@keyframes datatables-loader-1 { - 0% { - transform: scale(0); - } - 100% { - transform: scale(1); - } -} -@keyframes datatables-loader-3 { - 0% { - transform: scale(1); - } - 100% { - transform: scale(0); - } -} -@keyframes datatables-loader-2 { - 0% { - transform: translate(0, 0); - } - 100% { - transform: translate(24px, 0); - } -} -table.dataTable.nowrap th, table.dataTable.nowrap td { - white-space: nowrap; -} -table.dataTable th, -table.dataTable td { - box-sizing: border-box; -} -table.dataTable th.dt-type-numeric, table.dataTable th.dt-type-date, -table.dataTable td.dt-type-numeric, -table.dataTable td.dt-type-date { - text-align: right; -} -table.dataTable th.dt-type-numeric div.dt-column-header, -table.dataTable th.dt-type-numeric div.dt-column-footer, table.dataTable th.dt-type-date div.dt-column-header, -table.dataTable th.dt-type-date div.dt-column-footer, -table.dataTable td.dt-type-numeric div.dt-column-header, -table.dataTable td.dt-type-numeric div.dt-column-footer, -table.dataTable td.dt-type-date div.dt-column-header, -table.dataTable td.dt-type-date div.dt-column-footer { - flex-direction: row-reverse; -} -table.dataTable th.dt-left, -table.dataTable td.dt-left { - text-align: left; -} -table.dataTable th.dt-center, -table.dataTable td.dt-center { - text-align: center; -} -table.dataTable th.dt-right, -table.dataTable td.dt-right { - text-align: right; -} -table.dataTable th.dt-right div.dt-column-header, -table.dataTable th.dt-right div.dt-column-footer, -table.dataTable td.dt-right div.dt-column-header, -table.dataTable td.dt-right div.dt-column-footer { - flex-direction: row-reverse; -} -table.dataTable th.dt-justify, -table.dataTable td.dt-justify { - text-align: justify; -} -table.dataTable th.dt-nowrap, -table.dataTable td.dt-nowrap { - white-space: nowrap; -} -table.dataTable th.dt-empty, -table.dataTable td.dt-empty { - text-align: center; - vertical-align: top; -} -table.dataTable thead th, -table.dataTable thead td, -table.dataTable tfoot th, -table.dataTable tfoot td { - text-align: left; -} -table.dataTable thead th.dt-head-left, -table.dataTable thead td.dt-head-left, -table.dataTable tfoot th.dt-head-left, -table.dataTable tfoot td.dt-head-left { - text-align: left; -} -table.dataTable thead th.dt-head-center, -table.dataTable thead td.dt-head-center, -table.dataTable tfoot th.dt-head-center, -table.dataTable tfoot td.dt-head-center { - text-align: center; -} -table.dataTable thead th.dt-head-right, -table.dataTable thead td.dt-head-right, -table.dataTable tfoot th.dt-head-right, -table.dataTable tfoot td.dt-head-right { - text-align: right; -} -table.dataTable thead th.dt-head-right div.dt-column-header, -table.dataTable thead th.dt-head-right div.dt-column-footer, -table.dataTable thead td.dt-head-right div.dt-column-header, -table.dataTable thead td.dt-head-right div.dt-column-footer, -table.dataTable tfoot th.dt-head-right div.dt-column-header, -table.dataTable tfoot th.dt-head-right div.dt-column-footer, -table.dataTable tfoot td.dt-head-right div.dt-column-header, -table.dataTable tfoot td.dt-head-right div.dt-column-footer { - flex-direction: row-reverse; -} -table.dataTable thead th.dt-head-justify, -table.dataTable thead td.dt-head-justify, -table.dataTable tfoot th.dt-head-justify, -table.dataTable tfoot td.dt-head-justify { - text-align: justify; -} -table.dataTable thead th.dt-head-nowrap, -table.dataTable thead td.dt-head-nowrap, -table.dataTable tfoot th.dt-head-nowrap, -table.dataTable tfoot td.dt-head-nowrap { - white-space: nowrap; -} -table.dataTable tbody th.dt-body-left, -table.dataTable tbody td.dt-body-left { - text-align: left; -} -table.dataTable tbody th.dt-body-center, -table.dataTable tbody td.dt-body-center { - text-align: center; -} -table.dataTable tbody th.dt-body-right, -table.dataTable tbody td.dt-body-right { - text-align: right; -} -table.dataTable tbody th.dt-body-justify, -table.dataTable tbody td.dt-body-justify { - text-align: justify; -} -table.dataTable tbody th.dt-body-nowrap, -table.dataTable tbody td.dt-body-nowrap { - white-space: nowrap; -} - -/*! Bootstrap 5 integration for DataTables - * - * ©2020 SpryMedia Ltd, all rights reserved. - * License: MIT datatables.net/license/mit - */ -table.table.dataTable { - clear: both; - margin-bottom: 0; - max-width: none; - border-spacing: 0; -} -table.table.dataTable.table-striped > tbody > tr:nth-of-type(2n+1) > * { - box-shadow: none; -} -table.table.dataTable > :not(caption) > * > * { - background-color: var(--bs-table-bg); -} -table.table.dataTable > tbody > tr { - background-color: transparent; -} -table.table.dataTable > tbody > tr.selected > * { - box-shadow: inset 0 0 0 9999px rgb(13, 110, 253); - box-shadow: inset 0 0 0 9999px rgb(var(--dt-row-selected)); - color: rgb(255, 255, 255); - color: rgb(var(--dt-row-selected-text)); -} -table.table.dataTable > tbody > tr.selected a { - color: rgb(9, 10, 11); - color: rgb(var(--dt-row-selected-link)); -} -table.table.dataTable.table-striped > tbody > tr:nth-of-type(2n+1) > * { - box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-stripe), 0.05); -} -table.table.dataTable.table-striped > tbody > tr:nth-of-type(2n+1).selected > * { - box-shadow: inset 0 0 0 9999px rgba(13, 110, 253, 0.95); - box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-selected), 0.95); -} -table.table.dataTable.table-hover > tbody > tr:hover > * { - box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-hover), 0.075); -} -table.table.dataTable.table-hover > tbody > tr.selected:hover > * { - box-shadow: inset 0 0 0 9999px rgba(13, 110, 253, 0.975); - box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-selected), 0.975); -} - -div.dt-container div.dt-layout-start > *:not(:last-child) { - margin-right: 1em; -} -div.dt-container div.dt-layout-end > *:not(:first-child) { - margin-left: 1em; -} -div.dt-container div.dt-layout-full { - width: 100%; -} -div.dt-container div.dt-layout-full > *:only-child { - margin-left: auto; - margin-right: auto; -} -div.dt-container div.dt-layout-table > div { - display: block !important; -} - -@media screen and (max-width: 767px) { - div.dt-container div.dt-layout-start > *:not(:last-child) { - margin-right: 0; - } - div.dt-container div.dt-layout-end > *:not(:first-child) { - margin-left: 0; - } -} -div.dt-container { - position: relative; -} -div.dt-container div.dt-length label { - font-weight: normal; - text-align: left; - white-space: nowrap; -} -div.dt-container div.dt-length select { - width: auto; - display: inline-block; - margin-right: 0.5em; -} -div.dt-container div.dt-search { - text-align: right; -} -div.dt-container div.dt-search label { - font-weight: normal; - white-space: nowrap; - text-align: left; -} -div.dt-container div.dt-search input { - margin-left: 0.5em; - display: inline-block; - width: auto; -} -div.dt-container div.dt-paging { - margin: 0; -} -div.dt-container div.dt-paging ul.pagination { - margin: 2px 0; - flex-wrap: wrap; -} -div.dt-container div.dt-row { - position: relative; -} - -div.dt-scroll-head table.dataTable { - margin-bottom: 0 !important; -} - -div.dt-scroll-body { - border-bottom-color: var(--bs-border-color); - border-bottom-width: var(--bs-border-width); - border-bottom-style: solid; -} -div.dt-scroll-body > table { - border-top: none; - margin-top: 0 !important; - margin-bottom: 0 !important; -} -div.dt-scroll-body > table > tbody > tr:first-child { - border-top-width: 0; -} -div.dt-scroll-body > table > thead > tr { - border-width: 0 !important; -} -div.dt-scroll-body > table > tbody > tr:last-child > * { - border-bottom: none; -} - -div.dt-scroll-foot > .dt-scroll-footInner { - box-sizing: content-box; -} -div.dt-scroll-foot > .dt-scroll-footInner > table { - margin-top: 0 !important; - border-top: none; -} -div.dt-scroll-foot > .dt-scroll-footInner > table > tfoot > tr:first-child { - border-top-width: 0 !important; -} - -@media screen and (max-width: 767px) { - div.dt-container div.dt-length, - div.dt-container div.dt-search, - div.dt-container div.dt-info, - div.dt-container div.dt-paging { - text-align: center; - } - div.dt-container .row { - --bs-gutter-y: 0.5rem; - } - div.dt-container div.dt-paging ul.pagination { - justify-content: center !important; - } -} -table.dataTable.table-sm > thead > tr th.dt-orderable-asc, table.dataTable.table-sm > thead > tr th.dt-orderable-desc, table.dataTable.table-sm > thead > tr th.dt-ordering-asc, table.dataTable.table-sm > thead > tr th.dt-ordering-desc, -table.dataTable.table-sm > thead > tr td.dt-orderable-asc, -table.dataTable.table-sm > thead > tr td.dt-orderable-desc, -table.dataTable.table-sm > thead > tr td.dt-ordering-asc, -table.dataTable.table-sm > thead > tr td.dt-ordering-desc { - padding-right: 20px; -} -table.dataTable.table-sm > thead > tr th.dt-orderable-asc span.dt-column-order, table.dataTable.table-sm > thead > tr th.dt-orderable-desc span.dt-column-order, table.dataTable.table-sm > thead > tr th.dt-ordering-asc span.dt-column-order, table.dataTable.table-sm > thead > tr th.dt-ordering-desc span.dt-column-order, -table.dataTable.table-sm > thead > tr td.dt-orderable-asc span.dt-column-order, -table.dataTable.table-sm > thead > tr td.dt-orderable-desc span.dt-column-order, -table.dataTable.table-sm > thead > tr td.dt-ordering-asc span.dt-column-order, -table.dataTable.table-sm > thead > tr td.dt-ordering-desc span.dt-column-order { - right: 5px; -} - -div.dt-scroll-head table.table-bordered { - border-bottom-width: 0; -} - -div.table-responsive > div.dt-container > div.row { - margin: 0; -} -div.table-responsive > div.dt-container > div.row > div[class^=col-]:first-child { - padding-left: 0; -} -div.table-responsive > div.dt-container > div.row > div[class^=col-]:last-child { - padding-right: 0; -} - -:root[data-bs-theme=dark] { - --dt-row-hover: 255, 255, 255; - --dt-row-stripe: 255, 255, 255; - --dt-column-ordering: 255, 255, 255; -} diff --git a/app/assets/stylesheets/datatables/_responsive.bootstrap5.scss b/app/assets/stylesheets/datatables/_responsive.bootstrap5.scss deleted file mode 100644 index ce6fcb54..00000000 --- a/app/assets/stylesheets/datatables/_responsive.bootstrap5.scss +++ /dev/null @@ -1,203 +0,0 @@ -table.dataTable.dtr-inline.collapsed > tbody > tr > td.child, -table.dataTable.dtr-inline.collapsed > tbody > tr > th.child, -table.dataTable.dtr-inline.collapsed > tbody > tr > td.dataTables_empty { - cursor: default !important; -} -table.dataTable.dtr-inline.collapsed > tbody > tr > td.child:before, -table.dataTable.dtr-inline.collapsed > tbody > tr > th.child:before, -table.dataTable.dtr-inline.collapsed > tbody > tr > td.dataTables_empty:before { - display: none !important; -} -table.dataTable.dtr-inline.collapsed > tbody > tr > td.dtr-control, -table.dataTable.dtr-inline.collapsed > tbody > tr > th.dtr-control { - cursor: pointer; -} -table.dataTable.dtr-inline.collapsed > tbody > tr > td.dtr-control:before, -table.dataTable.dtr-inline.collapsed > tbody > tr > th.dtr-control:before { - margin-right: 0.5em; - display: inline-block; - box-sizing: border-box; - content: ""; - border-top: 5px solid transparent; - border-left: 10px solid rgba(0, 0, 0, 0.5); - border-bottom: 5px solid transparent; - border-right: 0px solid transparent; -} -table.dataTable.dtr-inline.collapsed > tbody > tr > td.dtr-control.arrow-right::before, -table.dataTable.dtr-inline.collapsed > tbody > tr > th.dtr-control.arrow-right::before { - border-top: 5px solid transparent; - border-left: 0px solid transparent; - border-bottom: 5px solid transparent; - border-right: 10px solid rgba(0, 0, 0, 0.5); -} -table.dataTable.dtr-inline.collapsed > tbody > tr.dtr-expanded > td.dtr-control:before, -table.dataTable.dtr-inline.collapsed > tbody > tr.dtr-expanded > th.dtr-control:before { - border-top: 10px solid rgba(0, 0, 0, 0.5); - border-left: 5px solid transparent; - border-bottom: 0px solid transparent; - border-right: 5px solid transparent; -} -table.dataTable.dtr-inline.collapsed.compact > tbody > tr > td.dtr-control, -table.dataTable.dtr-inline.collapsed.compact > tbody > tr > th.dtr-control { - padding-left: 0.333em; -} -table.dataTable.dtr-column > tbody > tr > td.dtr-control, -table.dataTable.dtr-column > tbody > tr > th.dtr-control, -table.dataTable.dtr-column > tbody > tr > td.control, -table.dataTable.dtr-column > tbody > tr > th.control { - cursor: pointer; -} -table.dataTable.dtr-column > tbody > tr > td.dtr-control:before, -table.dataTable.dtr-column > tbody > tr > th.dtr-control:before, -table.dataTable.dtr-column > tbody > tr > td.control:before, -table.dataTable.dtr-column > tbody > tr > th.control:before { - display: inline-block; - box-sizing: border-box; - content: ""; - border-top: 5px solid transparent; - border-left: 10px solid rgba(0, 0, 0, 0.5); - border-bottom: 5px solid transparent; - border-right: 0px solid transparent; -} -table.dataTable.dtr-column > tbody > tr > td.dtr-control.arrow-right::before, -table.dataTable.dtr-column > tbody > tr > th.dtr-control.arrow-right::before, -table.dataTable.dtr-column > tbody > tr > td.control.arrow-right::before, -table.dataTable.dtr-column > tbody > tr > th.control.arrow-right::before { - border-top: 5px solid transparent; - border-left: 0px solid transparent; - border-bottom: 5px solid transparent; - border-right: 10px solid rgba(0, 0, 0, 0.5); -} -table.dataTable.dtr-column > tbody > tr.dtr-expanded td.dtr-control:before, -table.dataTable.dtr-column > tbody > tr.dtr-expanded th.dtr-control:before, -table.dataTable.dtr-column > tbody > tr.dtr-expanded td.control:before, -table.dataTable.dtr-column > tbody > tr.dtr-expanded th.control:before { - border-top: 10px solid rgba(0, 0, 0, 0.5); - border-left: 5px solid transparent; - border-bottom: 0px solid transparent; - border-right: 5px solid transparent; -} -table.dataTable > tbody > tr.child { - padding: 0.5em 1em; -} -table.dataTable > tbody > tr.child:hover { - background: transparent !important; -} -table.dataTable > tbody > tr.child ul.dtr-details { - display: inline-block; - list-style-type: none; - margin: 0; - padding: 0; -} -table.dataTable > tbody > tr.child ul.dtr-details > li { - border-bottom: 1px solid #efefef; - padding: 0.5em 0; -} -table.dataTable > tbody > tr.child ul.dtr-details > li:first-child { - padding-top: 0; -} -table.dataTable > tbody > tr.child ul.dtr-details > li:last-child { - padding-bottom: 0; - border-bottom: none; -} -table.dataTable > tbody > tr.child span.dtr-title { - display: inline-block; - min-width: 75px; - font-weight: bold; -} - -div.dtr-modal { - position: fixed; - box-sizing: border-box; - top: 0; - left: 0; - height: 100%; - width: 100%; - z-index: 100; - padding: 10em 1em; -} -div.dtr-modal div.dtr-modal-display { - position: absolute; - top: 0; - left: 0; - bottom: 0; - right: 0; - width: 50%; - height: fit-content; - max-height: 75%; - overflow: auto; - margin: auto; - z-index: 102; - overflow: auto; - background-color: #f5f5f7; - border: 1px solid black; - border-radius: 0.5em; - box-shadow: 0 12px 30px rgba(0, 0, 0, 0.6); -} -div.dtr-modal div.dtr-modal-content { - position: relative; - padding: 2.5em; -} -div.dtr-modal div.dtr-modal-content h2 { - margin-top: 0; -} -div.dtr-modal div.dtr-modal-close { - position: absolute; - top: 6px; - right: 6px; - width: 22px; - height: 22px; - text-align: center; - border-radius: 3px; - cursor: pointer; - z-index: 12; -} -div.dtr-modal div.dtr-modal-background { - position: fixed; - top: 0; - left: 0; - right: 0; - bottom: 0; - z-index: 101; - background: rgba(0, 0, 0, 0.6); -} - -@media screen and (max-width: 767px) { - div.dtr-modal div.dtr-modal-display { - width: 95%; - } -} -html.dark table.dataTable > tbody > tr > td.dtr-control:before, -html[data-bs-theme=dark] table.dataTable > tbody > tr > td.dtr-control:before { - border-left-color: rgba(255, 255, 255, 0.5) !important; -} -html.dark table.dataTable > tbody > tr > td.dtr-control.arrow-right::before, -html[data-bs-theme=dark] table.dataTable > tbody > tr > td.dtr-control.arrow-right::before { - border-right-color: rgba(255, 255, 255, 0.5) !important; -} -html.dark table.dataTable > tbody > tr.dtr-expanded > td.dtr-control:before, -html.dark table.dataTable > tbody > tr.dtr-expanded > th.dtr-control:before, -html[data-bs-theme=dark] table.dataTable > tbody > tr.dtr-expanded > td.dtr-control:before, -html[data-bs-theme=dark] table.dataTable > tbody > tr.dtr-expanded > th.dtr-control:before { - border-top-color: rgba(255, 255, 255, 0.5) !important; - border-left-color: transparent !important; - border-right-color: transparent !important; -} -html.dark table.dataTable > tbody > tr.child ul.dtr-details > li, -html[data-bs-theme=dark] table.dataTable > tbody > tr.child ul.dtr-details > li { - border-bottom-color: rgb(64, 67, 70); -} -html.dark div.dtr-modal div.dtr-modal-display, -html[data-bs-theme=dark] div.dtr-modal div.dtr-modal-display { - background-color: rgb(33, 37, 41); - border: 1px solid rgba(255, 255, 255, 0.15); -} - -div.dtr-bs-modal table.table tr:first-child td { - border-top: none; -} - -table.dataTable.table-bordered th.dtr-control.dtr-hidden + *, -table.dataTable.table-bordered td.dtr-control.dtr-hidden + * { - border-left-width: 1px; -} diff --git a/bin/setup b/bin/setup index 5e00489e..e5a15a6e 100755 --- a/bin/setup +++ b/bin/setup @@ -17,6 +17,10 @@ FileUtils.chdir APP_ROOT do system! "gem install bundler --conservative" system("bundle check") || system!("bundle install") + # application.scss @imports the DataTables stylesheets out of node_modules, + # so the CSS will not compile without these. + system! "npm install" + # puts "\n== Copying sample files ==" # unless File.exist?("config/database.yml") # FileUtils.cp "config/database.yml.sample", "config/database.yml" diff --git a/config/initializers/dartsass.rb b/config/initializers/dartsass.rb index 700cd372..62cc8459 100644 --- a/config/initializers/dartsass.rb +++ b/config/initializers/dartsass.rb @@ -16,3 +16,13 @@ # # Drop this once Bootstrap 6 ships and the `@import`s can become `@use`. Rails.application.config.sass.silence_deprecations = [ 'import' ] + +# DataTables ships its Bootstrap 5 theme as plain CSS on npm. Put the two +# package directories on the Sass load path so application.scss can @import +# the stylesheets by name and Dart Sass inlines them into application.css, +# rather than the app carrying hand-maintained copies that drift from the +# JavaScript pinned in config/importmap.rb. +Rails.application.config.sass.load_paths += [ + Rails.root.join('node_modules/datatables.net-bs5/css').to_s, + Rails.root.join('node_modules/datatables.net-responsive-bs5/css').to_s +] diff --git a/package-lock.json b/package-lock.json index b0f6fa6d..f9161050 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,10 +8,12 @@ "name": "flextensions", "version": "1.0.0", "license": "BSD-2-Clause", + "dependencies": { + "datatables.net-bs5": "2.3.1", + "datatables.net-responsive-bs5": "3.0.4" + }, "devDependencies": { "@redocly/cli": "^2.40.0", - "datatables.net": "^2.2.2", - "datatables.net-bs5": "^2.2.2", "swagger-cli": "^4.0.4" } }, @@ -310,21 +312,42 @@ "dev": true }, "node_modules/datatables.net": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/datatables.net/-/datatables.net-2.2.2.tgz", - "integrity": "sha512-gfODIKE3gpgbVeZy2QGj2Dq9roO6hy00S+k1knklrqlMyAMrh1wt0Q6ryBUM7gU96U77ysbq8dYhxFdmcC/oPQ==", - "dev": true, + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/datatables.net/-/datatables.net-2.3.1.tgz", + "integrity": "sha512-pRXZuk3oR7P5kTl/CRvopcTihlvmZiY+xSBjbNI6e8MjYtgZQodWEqLUUOrIZeLH8CfxapPR/Bmb0NKAvILP5g==", + "license": "MIT", "dependencies": { "jquery": ">=1.7" } }, "node_modules/datatables.net-bs5": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/datatables.net-bs5/-/datatables.net-bs5-2.2.2.tgz", - "integrity": "sha512-0mAbpUf0EpnIEc0RlN6vSrSk9y/+NuReiwDpjHYY3RfzdvH6Lt0+7Q9OU5RIbYxaFxES/z60thxdrw7IUFnBhw==", - "dev": true, + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/datatables.net-bs5/-/datatables.net-bs5-2.3.1.tgz", + "integrity": "sha512-bUwsW7cIR8EDyiedQv6OKGF5ouxkFjaODmtEd7NRtpoZixAZDScmIrxe9cddjS0EybpC2gYslKs+o03E8tkCEA==", + "license": "MIT", + "dependencies": { + "datatables.net": "2.3.1", + "jquery": ">=1.7" + } + }, + "node_modules/datatables.net-responsive": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/datatables.net-responsive/-/datatables.net-responsive-3.0.4.tgz", + "integrity": "sha512-d1JKaWPRp81xydZrTDqrzGTloXthDtyvwbqiSKYUhDBKWTL4wN5IW5WYr4OBPwu21Ctpi/QgOl7mt9MfyF8KxA==", + "license": "MIT", + "dependencies": { + "datatables.net": "^2", + "jquery": ">=1.7" + } + }, + "node_modules/datatables.net-responsive-bs5": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/datatables.net-responsive-bs5/-/datatables.net-responsive-bs5-3.0.4.tgz", + "integrity": "sha512-/RufNJAFFmXkhfyBmXKfLlpopoKNkeDytMHHbsiBkoTRllJik9rtOON5NuMJOCL8sjwM2c+rUo/rzuXNk/70Qw==", + "license": "MIT", "dependencies": { - "datatables.net": "2.2.2", + "datatables.net-bs5": "^2", + "datatables.net-responsive": "3.0.4", "jquery": ">=1.7" } }, @@ -419,10 +442,10 @@ } }, "node_modules/jquery": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.1.tgz", - "integrity": "sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg==", - "dev": true + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jquery/-/jquery-4.0.0.tgz", + "integrity": "sha512-TXCHVR3Lb6TZdtw1l3RTLf8RBWVGexdxL6AC8/e0xZKEpBflBsjh9/8LXw+dkNFuOyW9B7iB3O1sP7hS0Kiacg==", + "license": "MIT" }, "node_modules/js-yaml": { "version": "4.3.1", @@ -838,21 +861,38 @@ "dev": true }, "datatables.net": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/datatables.net/-/datatables.net-2.2.2.tgz", - "integrity": "sha512-gfODIKE3gpgbVeZy2QGj2Dq9roO6hy00S+k1knklrqlMyAMrh1wt0Q6ryBUM7gU96U77ysbq8dYhxFdmcC/oPQ==", - "dev": true, + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/datatables.net/-/datatables.net-2.3.1.tgz", + "integrity": "sha512-pRXZuk3oR7P5kTl/CRvopcTihlvmZiY+xSBjbNI6e8MjYtgZQodWEqLUUOrIZeLH8CfxapPR/Bmb0NKAvILP5g==", "requires": { "jquery": ">=1.7" } }, "datatables.net-bs5": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/datatables.net-bs5/-/datatables.net-bs5-2.2.2.tgz", - "integrity": "sha512-0mAbpUf0EpnIEc0RlN6vSrSk9y/+NuReiwDpjHYY3RfzdvH6Lt0+7Q9OU5RIbYxaFxES/z60thxdrw7IUFnBhw==", - "dev": true, + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/datatables.net-bs5/-/datatables.net-bs5-2.3.1.tgz", + "integrity": "sha512-bUwsW7cIR8EDyiedQv6OKGF5ouxkFjaODmtEd7NRtpoZixAZDScmIrxe9cddjS0EybpC2gYslKs+o03E8tkCEA==", "requires": { - "datatables.net": "2.2.2", + "datatables.net": "2.3.1", + "jquery": ">=1.7" + } + }, + "datatables.net-responsive": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/datatables.net-responsive/-/datatables.net-responsive-3.0.4.tgz", + "integrity": "sha512-d1JKaWPRp81xydZrTDqrzGTloXthDtyvwbqiSKYUhDBKWTL4wN5IW5WYr4OBPwu21Ctpi/QgOl7mt9MfyF8KxA==", + "requires": { + "datatables.net": "^2", + "jquery": ">=1.7" + } + }, + "datatables.net-responsive-bs5": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/datatables.net-responsive-bs5/-/datatables.net-responsive-bs5-3.0.4.tgz", + "integrity": "sha512-/RufNJAFFmXkhfyBmXKfLlpopoKNkeDytMHHbsiBkoTRllJik9rtOON5NuMJOCL8sjwM2c+rUo/rzuXNk/70Qw==", + "requires": { + "datatables.net-bs5": "^2", + "datatables.net-responsive": "3.0.4", "jquery": ">=1.7" } }, @@ -915,10 +955,9 @@ "dev": true }, "jquery": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.1.tgz", - "integrity": "sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg==", - "dev": true + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jquery/-/jquery-4.0.0.tgz", + "integrity": "sha512-TXCHVR3Lb6TZdtw1l3RTLf8RBWVGexdxL6AC8/e0xZKEpBflBsjh9/8LXw+dkNFuOyW9B7iB3O1sP7hS0Kiacg==" }, "js-yaml": { "version": "4.3.1", diff --git a/package.json b/package.json index 47633087..deac1a64 100644 --- a/package.json +++ b/package.json @@ -11,11 +11,12 @@ }, "author": "CS169l flextensions development team", "license": "BSD-2-Clause", - + "dependencies": { + "datatables.net-bs5": "2.3.1", + "datatables.net-responsive-bs5": "3.0.4" + }, "devDependencies": { "@redocly/cli": "^2.40.0", - "swagger-cli": "^4.0.4", - "datatables.net": "^2.2.2", - "datatables.net-bs5": "^2.2.2" + "swagger-cli": "^4.0.4" } } From 85b5404522d281d6e2da9a5071c368a1b010c2e3 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 5 Aug 2026 21:53:38 +0000 Subject: [PATCH 3/3] refactor: migrate the asset pipeline from Sprockets to Propshaft Follow-on to the dartsass-sprockets migration. Same Dart Sass compiler, different plumbing: Propshaft digests and serves what is on disk and does no transformation, so Dart Sass writes app/assets/builds/application.css and Propshaft takes it from there. sprockets-rails -> propshaft dartsass-sprockets -> dartsass-rails What goes away: app/assets/config/manifest.js, the config.assets.precompile list, and five Sprockets-only environment settings (assets.debug, .digest, .quiet, .compile, .css_compressor). Four things needed handling: 1. Font Awesome webfonts. font-awesome-sass emits font-path() only when Sprockets' Sass helpers are loaded, and otherwise falls back to "#{$fa-font-path}/#{$file}" -- i.e. ../webfonts/..., which 404s. Set $fa-font-path to the gem's logical asset path and let Propshaft rewrite the url() to a digested one. 2. Gem Sass sources leaking into public/assets. Propshaft has a single load path and serves everything on it, and the bootstrap and font-awesome-sass gems put their Sass sources there so application.scss can @import them -- 110 .scss partials were being digested into public/assets. They cannot simply be excluded, because dartsass-rails derives its --load-path list from config.assets.paths and Propshaft's excluded_paths removes entries from that same array. Pass the two directories to Dart Sass explicitly and then take them off Propshaft's path. 3. The CSS is a build artifact now. A checkout without it raises Propshaft::MissingAssetError rather than rendering unstyled, so bin/setup builds it and the three CI workflows that run rspec/cucumber directly get a Build CSS step. Procfile.dev gains a css process, so `make dev` keeps it fresh. 4. Dev output. dartsass-rails defaults to --style=compressed --no-source-map in every environment, where sassc-rails only compressed outside development. Build expanded with source maps in development. Verified: rspec 584/0; the a11y suite 40/0 with all 40 screenshots pixel-compared against the Sprockets build (20 byte-identical, the rest differing only where generated timestamps change column widths); cucumber unchanged at the same 5 pre-existing failures. Production and development boot with CSS, webfonts and all 27 local importmap assets returning 200. The compiled CSS is token-for-token identical to the Sprockets output apart from minifier cosmetics (nested calc(), leading zeros). Co-Authored-By: Claude --- .github/workflows/a11y.yml | 2 + .github/workflows/cucumber.yml | 2 + .github/workflows/rspec.yml | 2 + .gitignore | 4 ++ Gemfile | 19 +++---- Gemfile.lock | 74 ++++++++++--------------- Procfile.dev | 1 + app/assets/builds/.keep | 0 app/assets/config/manifest.js | 9 --- app/assets/stylesheets/application.scss | 7 +++ bin/setup | 6 ++ config/environments/development.rb | 7 --- config/environments/production.rb | 6 -- config/initializers/assets.rb | 22 ++++---- config/initializers/dartsass.rb | 66 ++++++++++++++-------- 15 files changed, 114 insertions(+), 113 deletions(-) create mode 100644 app/assets/builds/.keep delete mode 100644 app/assets/config/manifest.js diff --git a/.github/workflows/a11y.yml b/.github/workflows/a11y.yml index e7a0fa6c..6ffa33e7 100644 --- a/.github/workflows/a11y.yml +++ b/.github/workflows/a11y.yml @@ -43,6 +43,8 @@ jobs: run: npm ci --omit=dev - name: Set up Database run: bundle exec rails db:setup + - name: Build CSS + run: bundle exec rails dartsass:build - name: Create separate Chrome data directories run: | mkdir -p /tmp/chrome-data-rspec diff --git a/.github/workflows/cucumber.yml b/.github/workflows/cucumber.yml index 0bbd6d09..368a7e04 100644 --- a/.github/workflows/cucumber.yml +++ b/.github/workflows/cucumber.yml @@ -50,6 +50,8 @@ jobs: chrome-version: stable - name: Setup Database run: bundle exec rails db:setup + - name: Build CSS + run: bundle exec rails dartsass:build - name: Create Chrome data directory run: mkdir -p /tmp/chrome-data-cucumber - name: Run non-JavaScript Cucumber tests diff --git a/.github/workflows/rspec.yml b/.github/workflows/rspec.yml index 1c1476e4..6b333e9f 100644 --- a/.github/workflows/rspec.yml +++ b/.github/workflows/rspec.yml @@ -47,6 +47,8 @@ jobs: - name: Setup Database run: | bundle exec rails db:test:prepare + - name: Build CSS + run: bundle exec rails dartsass:build - name: Create Chrome data directory run: mkdir -p /tmp/chrome-data-rspec - name: Run RSpec Tests diff --git a/.gitignore b/.gitignore index b8b4a5cd..6567000b 100644 --- a/.gitignore +++ b/.gitignore @@ -66,6 +66,10 @@ node_modules/ /public/packs-test /public/assets +# Dart Sass build output (app/assets/builds/application.css) +/app/assets/builds/* +!/app/assets/builds/.keep + # Ignore yarn files /yarn-error.log yarn-debug.log* diff --git a/Gemfile b/Gemfile index 9808f109..d439468b 100644 --- a/Gemfile +++ b/Gemfile @@ -81,8 +81,9 @@ gem 'hypershield' gem 'good_job', '~> 4.19' #### Frontend related tools -# The original asset pipeline for Rails [https://github.com/rails/sprockets-rails] -gem 'sprockets-rails' +# Rails 8's default asset pipeline: digests and serves what's on disk, and does +# no compiling of its own [https://github.com/rails/propshaft] +gem 'propshaft' # Use JavaScript with ESM import maps [https://github.com/rails/importmap-rails] gem 'importmap-rails' @@ -91,19 +92,15 @@ gem 'importmap-rails' gem 'turbo-rails' gem 'bootstrap', '~> 5.3.8' -# Compiles the SCSS in app/assets/stylesheets with Dart Sass (the only Sass -# implementation still maintained) via Sprockets. Replaces sassc-rails, which -# was deprecated along with its LibSass backend. Everything else about the -# pipeline is unchanged: same stylesheet_link_tag, same manifest.js. -# -# `sassc` itself is still resolved as a transitive dependency (of both -# sassc-embedded and font-awesome-sass), but only as an API shim — LibSass no -# longer compiles anything. +# Compiles app/assets/stylesheets/application.scss to app/assets/builds with +# Dart Sass (the only Sass implementation still maintained), which Propshaft +# then digests and serves. Replaces sassc-rails, deprecated along with its +# LibSass backend. # # Bootstrap 5.3 still uses `@import`, which Dart Sass deprecated; those warnings # are silenced in config/initializers/dartsass.rb. Bootstrap 6 moves to `@use`, # at which point the silencing can go away. -gem 'dartsass-sprockets', '~> 3.2' +gem 'dartsass-rails', '~> 0.5' # Font Awesome for icons gem 'font-awesome-sass' diff --git a/Gemfile.lock b/Gemfile.lock index 29d77f77..42cd43b2 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -85,14 +85,10 @@ GEM uri (>= 0.13.1) addressable (2.9.0) public_suffix (>= 2.0.2, < 8.0) - annotaterb (4.24.0) + annotaterb (4.23.0) activerecord (>= 6.0.0) activesupport (>= 6.0.0) - anonymous_loader (0.1.3) - version_gem (~> 1.1, >= 1.1.14) ast (2.4.3) - auth-sanitizer (0.2.3) - version_gem (~> 1.1, >= 1.1.14) axe-core-api (4.12.0) dumb_delegator axe-core-cucumber (4.12.0) @@ -139,7 +135,7 @@ GEM bigdecimal rexml crass (1.0.7) - csv (3.3.6) + csv (3.3.5) cucumber (11.1.1) base64 (~> 0.2) builder (~> 3.2) @@ -169,12 +165,9 @@ GEM cucumber (>= 7, < 12) railties (>= 6.1, < 9) cucumber-tag-expressions (8.1.0) - dartsass-sprockets (3.2.1) - railties (>= 4.0.0) - sassc-embedded (~> 1.80.1) - sprockets (> 3.0) - sprockets-rails - tilt + dartsass-rails (0.5.1) + railties (>= 6.0.0) + sass-embedded (~> 1.63) database_cleaner-active_record (2.2.2) activerecord (>= 5.a) database_cleaner-core (~> 2.0) @@ -191,9 +184,9 @@ GEM railties (>= 6.1) drb (2.2.3) dumb_delegator (1.1.0) - erb (6.0.7) + erb (6.0.6) erubi (1.13.1) - et-orbi (1.4.1) + et-orbi (1.4.0) tzinfo factory_bot (6.5.6) activesupport (>= 6.1.0) @@ -254,7 +247,7 @@ GEM google-protobuf (4.35.1-x86_64-linux-gnu) bigdecimal rake (~> 13.3) - guard (2.20.2) + guard (2.20.1) formatador (>= 0.2.4) listen (>= 2.7, < 4.0) logger (~> 1.6) @@ -273,7 +266,7 @@ GEM hashdiff (1.2.1) hashie (5.1.0) logger - http-cookie (1.1.6) + http-cookie (1.1.0) domain_name (~> 0.5) httparty (0.24.2) csv @@ -296,7 +289,7 @@ GEM jbuilder (2.15.1) actionview (>= 7.0.0) activesupport (>= 7.0.0) - json (2.21.2) + json (2.21.1) json_schemer (2.5.0) bigdecimal hana (~> 1.3) @@ -342,7 +335,7 @@ GEM net-smtp marcel (1.2.1) matrix (0.4.3) - mcp (0.25.0) + mcp (0.22.0) json_schemer (>= 2.4) memoist3 (1.0.0) method_source (1.1.0) @@ -351,9 +344,9 @@ GEM minitest (6.0.6) drb (~> 2.0) prism (~> 1.5) - msgpack (1.8.4) + msgpack (1.8.3) multi_test (1.1.0) - multi_xml (0.9.1) + multi_xml (0.8.1) bigdecimal (>= 3.1, < 5) nenv (0.3.0) net-http (0.9.1) @@ -384,16 +377,14 @@ GEM notiffany (0.1.3) nenv (~> 0.1) shellany (~> 0.0) - oauth2 (2.0.25) - anonymous_loader (~> 0.1, >= 0.1.3) - auth-sanitizer (~> 0.2, >= 0.2.3) + oauth2 (2.0.18) faraday (>= 0.17.3, < 4.0) jwt (>= 1.0, < 4.0) logger (~> 1.2) multi_xml (~> 0.5) rack (>= 1.2, < 4) - snaky_hash (~> 2.0, >= 2.0.7) - version_gem (~> 1.1, >= 1.1.14) + snaky_hash (~> 2.0, >= 2.0.3) + version_gem (~> 1.1, >= 1.1.9) omniauth (2.1.4) hashie (>= 3.4.6) logger @@ -410,7 +401,7 @@ GEM omniauth (~> 2.0) ostruct (0.6.3) parallel (2.1.0) - parser (3.3.12.0) + parser (3.3.11.1) ast (~> 2.4.1) racc pg (1.6.3) @@ -423,6 +414,10 @@ GEM prettyprint prettyprint (0.2.0) prism (1.9.0) + propshaft (1.3.2) + actionpack (>= 7.0.0) + activesupport (>= 7.0.0) + rack pry (0.16.0) coderay (~> 1.1) method_source (~> 1.0) @@ -486,7 +481,7 @@ GEM rb-fsevent (0.11.2) rb-inotify (0.11.1) ffi (~> 1.0) - rbs (4.1.2) + rbs (4.1.0) logger prism (>= 1.6.0) tsort @@ -574,8 +569,6 @@ GEM google-protobuf (~> 4.31) sassc (2.4.0) ffi (~> 1.9) - sassc-embedded (1.80.9) - sass-embedded (~> 1.80) securerandom (0.4.1) selenium-webdriver (4.46.0) base64 (~> 0.2) @@ -595,17 +588,9 @@ GEM activesupport (>= 7.2) simplecov (1.0.3) simpleidn (0.2.3) - snaky_hash (2.0.7) + snaky_hash (2.0.3) hashie (>= 0.1.0, < 6) - version_gem (~> 1.1, >= 1.1.14) - sprockets (4.2.2) - concurrent-ruby (~> 1.0) - logger - rack (>= 2.2.4, < 4) - sprockets-rails (3.5.2) - actionpack (>= 6.1) - activesupport (>= 6.1) - sprockets (>= 3.0.0) + version_gem (>= 1.1.8, < 3) stimulus-rails (1.3.4) railties (>= 6.0.0) strong_migrations (2.8.0) @@ -618,7 +603,6 @@ GEM memoist3 (~> 1.0.0) win32ole thor (1.5.0) - tilt (2.8.0) timecop (0.9.11) timeout (0.6.1) tsort (0.2.0) @@ -627,14 +611,14 @@ GEM railties (>= 7.1.0) tzinfo (2.0.6) concurrent-ruby (~> 1.0) - tzinfo-data (1.2026.3) + tzinfo-data (1.2026.1) tzinfo (>= 1.0.0) unicode-display_width (3.2.0) unicode-emoji (~> 4.1) unicode-emoji (4.2.0) uri (1.1.1) useragent (0.16.11) - version_gem (1.1.15) + version_gem (1.1.9) web-console (4.3.0) actionview (>= 8.0.0) bindex (>= 0.4.0) @@ -651,7 +635,7 @@ GEM win32ole (1.9.3) xpath (3.2.0) nokogiri (~> 1.8) - zeitwerk (2.8.3) + zeitwerk (2.8.2) PLATFORMS aarch64-linux @@ -674,7 +658,7 @@ DEPENDENCIES capybara-screenshot codeclimate-test-reporter cucumber-rails - dartsass-sprockets (~> 3.2) + dartsass-rails (~> 0.5) database_cleaner-active_record debug dotenv-rails @@ -699,6 +683,7 @@ DEPENDENCIES omniauth-rails_csrf_protection ostruct pg + propshaft puma (>= 6.0) rack_session_access rails (~> 8.1.3) @@ -714,7 +699,6 @@ DEPENDENCIES sentry-ruby shoulda-matchers (~> 8.0) simplecov (~> 1.0.3) - sprockets-rails stimulus-rails strong_migrations timecop diff --git a/Procfile.dev b/Procfile.dev index 9c5377e0..4f961872 100644 --- a/Procfile.dev +++ b/Procfile.dev @@ -1 +1,2 @@ web: bundle exec rails s -p ${PORT:-3000} +css: bundle exec rails dartsass:watch diff --git a/app/assets/builds/.keep b/app/assets/builds/.keep new file mode 100644 index 00000000..e69de29b diff --git a/app/assets/config/manifest.js b/app/assets/config/manifest.js deleted file mode 100644 index ad05f0d7..00000000 --- a/app/assets/config/manifest.js +++ /dev/null @@ -1,9 +0,0 @@ -//= link_tree ../images -// Only the manifest stylesheet is compiled standalone. Do NOT link the whole -// directory: Sprockets would also compile the _*.scss partials on their own, -// where the variables from application.scss are not defined. -//= link application.css -//= link_tree ../../javascript .js -//= link_tree ../../../vendor/javascript .js - -//= link stimulus-loading.js diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss index 5b38bc4b..ffceaace 100644 --- a/app/assets/stylesheets/application.scss +++ b/app/assets/stylesheets/application.scss @@ -112,6 +112,13 @@ $escaped-characters: ( (")", "%29"), ); +// font-awesome-sass emits `font-path(...)` when Sprockets' Sass helpers are +// available and falls back to `#{$fa-font-path}/#{$file}` otherwise. Dart Sass +// runs as a plain CLI here, so the fallback is what we get — point it at the +// logical path of the gem's webfonts (its assets/fonts directory is on the +// Propshaft load path) and let Propshaft rewrite the url() to a digested one. +$fa-font-path: "font-awesome"; + @import "bootstrap"; // DataTables' Bootstrap 5 theme, imported straight out of node_modules (see diff --git a/bin/setup b/bin/setup index e5a15a6e..817e65bf 100755 --- a/bin/setup +++ b/bin/setup @@ -29,6 +29,12 @@ FileUtils.chdir APP_ROOT do puts "\n== Preparing database ==" system! "bin/rails db:prepare" + # application.css is a build artifact now, so a fresh checkout has none and + # the first page render would raise Propshaft::MissingAssetError. `make dev` + # keeps it up to date after this via the css process in Procfile.dev. + puts "\n== Building CSS ==" + system! "bin/rails dartsass:build" + puts "\n== Removing old logs and tempfiles ==" system! "bin/rails log:clear tmp:clear" diff --git a/config/environments/development.rb b/config/environments/development.rb index 1db08070..cd591f66 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -68,14 +68,7 @@ # Highlight code that enqueued background job in logs. config.active_job.verbose_enqueue_logs = true - # Suppress logger output for asset requests. - config.assets.quiet = false - config.assets.debug = true - # TODO: WARNING -- As of April 2026 / Rails 7.2 / sprockets + importmaps - # This must be set to true or JS does not seem to load in development. - config.assets.digest = true config.cache_classes = false - config.assets.compile = true # Raises error for missing translations. # config.i18n.raise_on_missing_translations = true diff --git a/config/environments/production.rb b/config/environments/production.rb index 50628b54..b8cc70ff 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -23,12 +23,6 @@ # Disable serving static files from `public/`, relying on NGINX/Apache to do so instead. # config.public_file_server.enabled = false - # Compress CSS using a preprocessor. - # config.assets.css_compressor = :sass - - # Do not fall back to assets pipeline if a precompiled asset is missed. - config.assets.compile = false - # Enable serving of images, stylesheets, and JavaScripts from an asset server. # config.asset_host = "http://assets.example.com" diff --git a/config/initializers/assets.rb b/config/initializers/assets.rb index be3a10c5..f47ac388 100644 --- a/config/initializers/assets.rb +++ b/config/initializers/assets.rb @@ -1,14 +1,12 @@ # Be sure to restart your server when you modify this file. -# Version of your assets, change this if you want to expire all your assets. -Rails.application.config.assets.version = "1.0" - -# Add additional assets to the asset load path. -# Rails.application.config.assets.paths << Emoji.images_path - -Rails.application.config.assets.precompile += %w(bootstrap.min.js popper.js) - -# Precompile additional assets. -# application.js, application.css, and all non-JS/CSS in the app/assets -# folder are already added. -# Rails.application.config.assets.precompile += %w( admin.js admin.css ) +# Propshaft serves everything it finds on the load path and digests it by +# content, so there is no precompile list to maintain — that was a Sprockets +# concept, as was the manifest.js this app used to carry. +# +# Rails adds every app/assets/* subdirectory to the load path automatically, so +# app/assets/builds (where Dart Sass writes application.css) needs no config. + +# The Sass sources are inputs to the build, not assets to serve; excluding them +# keeps unbuilt .scss out of public/assets. +Rails.application.config.assets.excluded_paths << Rails.root.join('app/assets/stylesheets') diff --git a/config/initializers/dartsass.rb b/config/initializers/dartsass.rb index 62cc8459..b25306b5 100644 --- a/config/initializers/dartsass.rb +++ b/config/initializers/dartsass.rb @@ -1,28 +1,48 @@ # Be sure to restart your server when you modify this file. -# Configuration for dartsass-sprockets, which compiles app/assets/stylesheets -# with Dart Sass. It keeps sassc-rails' `config.sass` namespace, so the options -# below are the ones sassc-rails understood plus Dart Sass' deprecation -# controls. - -# Don't report deprecations raised inside gem-provided stylesheets (Bootstrap, -# Font Awesome). We can't fix those, and Bootstrap 5.3 alone emits thousands of -# lines of them per compile. -Rails.application.config.sass.quiet_deps = true - -# `quiet_deps` only covers files loaded from the load path, so the `@import` -# lines in application.scss itself still warn. Silence that one deprecation -# everywhere; every other deprecation in our own stylesheets stays visible. +# dartsass-rails shells out to the Dart Sass CLI, so its configuration is a +# list of command-line flags rather than a Ruby options hash. # -# Drop this once Bootstrap 6 ships and the `@import`s can become `@use`. -Rails.application.config.sass.silence_deprecations = [ 'import' ] +# --quiet-deps drops deprecations raised inside gem-provided stylesheets +# (Bootstrap, Font Awesome); we can't fix those, and Bootstrap 5.3 alone emits +# thousands of lines of them per build. --silence-deprecation=import covers the +# `@import` lines in application.scss itself, which --quiet-deps does not reach. +# +# Drop the silencing once Bootstrap 6 ships and the `@import`s can become +# `@use`. +Rails.application.config.dartsass.build_options += [ + '--quiet-deps', + '--silence-deprecation=import' +] -# DataTables ships its Bootstrap 5 theme as plain CSS on npm. Put the two -# package directories on the Sass load path so application.scss can @import -# the stylesheets by name and Dart Sass inlines them into application.css, -# rather than the app carrying hand-maintained copies that drift from the -# JavaScript pinned in config/importmap.rb. -Rails.application.config.sass.load_paths += [ - Rails.root.join('node_modules/datatables.net-bs5/css').to_s, - Rails.root.join('node_modules/datatables.net-responsive-bs5/css').to_s +# The gem's default is --style=compressed --no-source-map for every +# environment. Sprockets only compressed outside development, so keep the +# built CSS readable and mapped back to the .scss sources while developing. +if Rails.env.development? + Rails.application.config.dartsass.build_options -= [ '--style=compressed', '--no-source-map' ] + Rails.application.config.dartsass.build_options += [ '--style=expanded', '--embed-sources' ] +end + +# DataTables ships its Bootstrap 5 theme as plain CSS on npm, so application.scss +# imports it out of node_modules. Nothing puts those directories on the Sass +# load path for us. +Rails.application.config.dartsass.build_options += [ + '--load-path', Rails.root.join('node_modules/datatables.net-bs5/css').to_s, + '--load-path', Rails.root.join('node_modules/datatables.net-responsive-bs5/css').to_s ] + +# The bootstrap and font-awesome-sass gems put their Sass sources on the asset +# load path so application.scss can @import them. Propshaft has one load path +# and serves everything on it, so leaving them there digests ~110 .scss +# partials into public/assets. dartsass-rails builds its --load-path list from +# config.assets.paths, so hand Dart Sass the directories directly and then take +# them off Propshaft's path. (font-awesome-sass' assets/fonts directory has to +# stay — that one holds the webfonts the built CSS points at.) +sass_gem_paths = %w[bootstrap font-awesome-sass].filter_map do |name| + spec = Gem.loaded_specs[name] + File.join(spec.gem_dir, 'assets', 'stylesheets') if spec +end + +Rails.application.config.dartsass.build_options += + sass_gem_paths.flat_map { |path| [ '--load-path', path ] } +Rails.application.config.assets.excluded_paths += sass_gem_paths