From e79a018ceb2d2c51f4dfcd23b078747e744f40d4 Mon Sep 17 00:00:00 2001 From: Tim Hsueh Date: Wed, 2 Sep 2026 10:13:45 +0800 Subject: [PATCH 1/3] =?UTF-8?q?chore:=20=E5=8D=87=E7=B4=9A=20Rails=207.2.2?= =?UTF-8?q?.1=20=E8=87=B3=208.0.5.1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rails 7.2 系列安全修補已於 2026-08-09 結束(見 docs/RAILS_UPGRADE_RESEARCH.md), 依官方建議逐個 minor 升級,本批次先升至 8.0.5.1 並啟用 load_defaults 8.0。 app:update 範本逐檔取捨: - 接受範本:public/ 錯誤頁與 icon、bin/setup、filter_parameter_logging(新增 cvv/cvc)、initializers/assets.rb、test.rb、development.rb 的 Rails 8 預設 (cache_store 改 memory_store、query_log_tags_enabled)、production.rb 的 silence_healthcheck_path、public_file_server 快取標頭、TaggedLogging.logger - 保留自訂:application.rb 的 Taipei 時區與 css_compressor nil workaround、 production.rb 的 assume_ssl/ALLOWED_HOSTS 與 /up host 豁免、assets.compile false、development.rb 的 sprockets assets.quiet/debug/compile、bin/brakeman (不加回 --ensure-latest)、bin/dev(沿用 Procfile.dev + foreman) - 移除範本雜訊:puma.rb 的 solid_queue plugin(本專案未用該 gem)、 production.rb 的 action_mailer default_url_options 佔位主機名、 app:update 誤帶入的三個 ActiveStorage migration(本專案無相關資料表) 驗證:bin/rails test 222 runs 全綠、test:system 13 runs 全綠、 rubocop 0 offenses、production assets:precompile exit 0、無 Rails deprecation。 --- Gemfile | 2 +- Gemfile.lock | 200 +++++++++--------- bin/setup | 13 +- config/application.rb | 2 +- config/environments/development.rb | 32 ++- config/environments/production.rb | 78 +++---- config/environments/test.rb | 24 +-- config/initializers/assets.rb | 5 - .../initializers/filter_parameter_logging.rb | 2 +- config/puma.rb | 8 +- public/400.html | 114 ++++++++++ public/404.html | 179 ++++++++++------ public/406-unsupported-browser.html | 178 ++++++++++------ public/422.html | 179 ++++++++++------ public/500.html | 178 ++++++++++------ public/icon.png | Bin 5599 -> 4166 bytes public/icon.svg | 4 +- 17 files changed, 739 insertions(+), 459 deletions(-) create mode 100644 public/400.html diff --git a/Gemfile b/Gemfile index b76f160..b802cad 100644 --- a/Gemfile +++ b/Gemfile @@ -1,7 +1,7 @@ source "https://rubygems.org" # Bundle edge Rails instead: gem "rails", github: "rails/rails", branch: "main" -gem "rails", "~> 7.2.2", ">= 7.2.2.1" +gem "rails", "~> 8.0.5", ">= 8.0.5.1" # The original asset pipeline for Rails [https://github.com/rails/sprockets-rails] gem "sprockets-rails" # Use postgresql as the database for Active Record diff --git a/Gemfile.lock b/Gemfile.lock index 4958175..bd9da02 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,66 +1,65 @@ GEM remote: https://rubygems.org/ specs: - actioncable (7.2.2.1) - actionpack (= 7.2.2.1) - activesupport (= 7.2.2.1) + actioncable (8.0.5.1) + actionpack (= 8.0.5.1) + activesupport (= 8.0.5.1) nio4r (~> 2.0) websocket-driver (>= 0.6.1) zeitwerk (~> 2.6) - actionmailbox (7.2.2.1) - actionpack (= 7.2.2.1) - activejob (= 7.2.2.1) - activerecord (= 7.2.2.1) - activestorage (= 7.2.2.1) - activesupport (= 7.2.2.1) + actionmailbox (8.0.5.1) + actionpack (= 8.0.5.1) + activejob (= 8.0.5.1) + activerecord (= 8.0.5.1) + activestorage (= 8.0.5.1) + activesupport (= 8.0.5.1) mail (>= 2.8.0) - actionmailer (7.2.2.1) - actionpack (= 7.2.2.1) - actionview (= 7.2.2.1) - activejob (= 7.2.2.1) - activesupport (= 7.2.2.1) + actionmailer (8.0.5.1) + actionpack (= 8.0.5.1) + actionview (= 8.0.5.1) + activejob (= 8.0.5.1) + activesupport (= 8.0.5.1) mail (>= 2.8.0) rails-dom-testing (~> 2.2) - actionpack (7.2.2.1) - actionview (= 7.2.2.1) - activesupport (= 7.2.2.1) + actionpack (8.0.5.1) + actionview (= 8.0.5.1) + activesupport (= 8.0.5.1) nokogiri (>= 1.8.5) - racc - rack (>= 2.2.4, < 3.2) + rack (>= 2.2.4) rack-session (>= 1.0.1) rack-test (>= 0.6.3) rails-dom-testing (~> 2.2) rails-html-sanitizer (~> 1.6) useragent (~> 0.16) - actiontext (7.2.2.1) - actionpack (= 7.2.2.1) - activerecord (= 7.2.2.1) - activestorage (= 7.2.2.1) - activesupport (= 7.2.2.1) + actiontext (8.0.5.1) + actionpack (= 8.0.5.1) + activerecord (= 8.0.5.1) + activestorage (= 8.0.5.1) + activesupport (= 8.0.5.1) globalid (>= 0.6.0) nokogiri (>= 1.8.5) - actionview (7.2.2.1) - activesupport (= 7.2.2.1) + actionview (8.0.5.1) + activesupport (= 8.0.5.1) builder (~> 3.1) erubi (~> 1.11) rails-dom-testing (~> 2.2) rails-html-sanitizer (~> 1.6) - activejob (7.2.2.1) - activesupport (= 7.2.2.1) + activejob (8.0.5.1) + activesupport (= 8.0.5.1) globalid (>= 0.3.6) - activemodel (7.2.2.1) - activesupport (= 7.2.2.1) - activerecord (7.2.2.1) - activemodel (= 7.2.2.1) - activesupport (= 7.2.2.1) + activemodel (8.0.5.1) + activesupport (= 8.0.5.1) + activerecord (8.0.5.1) + activemodel (= 8.0.5.1) + activesupport (= 8.0.5.1) timeout (>= 0.4.0) - activestorage (7.2.2.1) - actionpack (= 7.2.2.1) - activejob (= 7.2.2.1) - activerecord (= 7.2.2.1) - activesupport (= 7.2.2.1) + activestorage (8.0.5.1) + actionpack (= 8.0.5.1) + activejob (= 8.0.5.1) + activerecord (= 8.0.5.1) + activesupport (= 8.0.5.1) marcel (~> 1.0) - activesupport (7.2.2.1) + activesupport (8.0.5.1) base64 benchmark (>= 0.3) bigdecimal @@ -72,13 +71,14 @@ GEM minitest (>= 5.1) securerandom (>= 0.3) tzinfo (~> 2.0, >= 2.0.5) + uri (>= 0.13.1) addressable (2.8.7) public_suffix (>= 2.0.2, < 7.0) ast (2.4.3) base64 (0.3.0) bcrypt (3.1.22) - benchmark (0.4.1) - bigdecimal (3.2.2) + benchmark (0.5.0) + bigdecimal (4.1.2) bindex (0.8.1) bootsnap (1.18.6) msgpack (~> 1.2) @@ -95,15 +95,15 @@ GEM regexp_parser (>= 1.5, < 3.0) xpath (~> 3.2) chunky_png (1.4.0) - concurrent-ruby (1.3.5) - connection_pool (2.5.3) - crass (1.0.6) - date (3.4.1) + concurrent-ruby (1.3.8) + connection_pool (3.0.2) + crass (1.0.7) + date (3.5.1) debug (1.11.0) irb (~> 1.10) reline (>= 0.3.8) drb (2.2.3) - erb (5.0.1) + erb (6.0.7) erubi (1.13.1) ffi (1.17.2-aarch64-linux-gnu) ffi (1.17.2-aarch64-linux-musl) @@ -113,17 +113,18 @@ GEM ffi (1.17.2-x86_64-darwin) ffi (1.17.2-x86_64-linux-gnu) ffi (1.17.2-x86_64-linux-musl) - globalid (1.2.1) + globalid (1.4.0) activesupport (>= 6.1) - i18n (1.14.7) + i18n (1.15.2) concurrent-ruby (~> 1.0) importmap-rails (2.1.0) actionpack (>= 6.0.0) activesupport (>= 6.0.0) railties (>= 6.0.0) - io-console (0.8.0) - irb (1.15.2) + io-console (0.9.2) + irb (1.18.0) pp (>= 0.6.0) + prism (>= 1.3.0) rdoc (>= 4.0.0) reline (>= 0.4.2) jbuilder (2.13.0) @@ -133,107 +134,110 @@ GEM language_server-protocol (3.17.0.5) lint_roller (1.1.0) logger (1.7.0) - loofah (2.24.1) + loofah (2.25.2) crass (~> 1.0.2) nokogiri (>= 1.12.0) - mail (2.8.1) + mail (2.9.1) + logger mini_mime (>= 0.1.1) net-imap net-pop net-smtp - marcel (1.0.4) + marcel (1.2.1) matrix (0.4.3) mini_mime (1.1.5) - minitest (5.25.5) + minitest (5.27.0) msgpack (1.8.0) - net-imap (0.5.9) + net-imap (0.6.6) date net-protocol net-pop (0.1.2) net-protocol - net-protocol (0.2.2) + net-protocol (0.3.0) timeout net-smtp (0.5.1) net-protocol - nio4r (2.7.4) - nokogiri (1.18.8-aarch64-linux-gnu) + nio4r (2.7.5) + nokogiri (1.19.4-aarch64-linux-gnu) racc (~> 1.4) - nokogiri (1.18.8-aarch64-linux-musl) + nokogiri (1.19.4-aarch64-linux-musl) racc (~> 1.4) - nokogiri (1.18.8-arm-linux-gnu) + nokogiri (1.19.4-arm-linux-gnu) racc (~> 1.4) - nokogiri (1.18.8-arm-linux-musl) + nokogiri (1.19.4-arm-linux-musl) racc (~> 1.4) - nokogiri (1.18.8-arm64-darwin) + nokogiri (1.19.4-arm64-darwin) racc (~> 1.4) - nokogiri (1.18.8-x86_64-darwin) + nokogiri (1.19.4-x86_64-darwin) racc (~> 1.4) - nokogiri (1.18.8-x86_64-linux-gnu) + nokogiri (1.19.4-x86_64-linux-gnu) racc (~> 1.4) - nokogiri (1.18.8-x86_64-linux-musl) + nokogiri (1.19.4-x86_64-linux-musl) racc (~> 1.4) parallel (1.27.0) parser (3.3.8.0) ast (~> 2.4.1) racc pg (1.5.9) - pp (0.6.2) + pp (0.6.4) prettyprint prettyprint (0.2.0) prism (1.4.0) - psych (5.2.6) + psych (5.5.0) date stringio public_suffix (6.0.2) puma (6.6.0) nio4r (~> 2.0) racc (1.8.1) - rack (3.1.16) + rack (3.2.7) rack-attack (6.8.0) rack (>= 1.0, < 4) - rack-session (2.1.1) + rack-session (2.1.2) base64 (>= 0.1.0) rack (>= 3.0.0) rack-test (2.2.0) rack (>= 1.3) - rackup (2.2.1) + rackup (2.3.1) rack (>= 3) - rails (7.2.2.1) - actioncable (= 7.2.2.1) - actionmailbox (= 7.2.2.1) - actionmailer (= 7.2.2.1) - actionpack (= 7.2.2.1) - actiontext (= 7.2.2.1) - actionview (= 7.2.2.1) - activejob (= 7.2.2.1) - activemodel (= 7.2.2.1) - activerecord (= 7.2.2.1) - activestorage (= 7.2.2.1) - activesupport (= 7.2.2.1) + rails (8.0.5.1) + actioncable (= 8.0.5.1) + actionmailbox (= 8.0.5.1) + actionmailer (= 8.0.5.1) + actionpack (= 8.0.5.1) + actiontext (= 8.0.5.1) + actionview (= 8.0.5.1) + activejob (= 8.0.5.1) + activemodel (= 8.0.5.1) + activerecord (= 8.0.5.1) + activestorage (= 8.0.5.1) + activesupport (= 8.0.5.1) bundler (>= 1.15.0) - railties (= 7.2.2.1) + railties (= 8.0.5.1) rails-dom-testing (2.3.0) activesupport (>= 5.0.0) minitest nokogiri (>= 1.6) - rails-html-sanitizer (1.6.2) - loofah (~> 2.21) + rails-html-sanitizer (1.7.1) + loofah (~> 2.25, >= 2.25.2) nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0) - railties (7.2.2.1) - actionpack (= 7.2.2.1) - activesupport (= 7.2.2.1) + railties (8.0.5.1) + actionpack (= 8.0.5.1) + activesupport (= 8.0.5.1) irb (~> 1.13) rackup (>= 1.0.0) rake (>= 12.2) thor (~> 1.0, >= 1.2.2) + tsort (>= 0.2) zeitwerk (~> 2.6) rainbow (3.1.1) - rake (13.3.0) - rdoc (6.14.2) + rake (13.4.2) + rdoc (7.2.0) erb psych (>= 4.0.0) + tsort regexp_parser (2.10.0) - reline (0.6.1) + reline (0.7.0) io-console (~> 0.5) rexml (3.4.1) rqrcode (3.2.0) @@ -295,7 +299,7 @@ GEM sprockets (>= 3.0.0) stimulus-rails (1.3.4) railties (>= 6.0.0) - stringio (3.1.7) + stringio (3.2.0) tailwindcss-rails (4.6.0) railties (>= 7.0.0) tailwindcss-ruby (~> 4.0) @@ -306,9 +310,10 @@ GEM tailwindcss-ruby (4.3.3-x86_64-darwin) tailwindcss-ruby (4.3.3-x86_64-linux-gnu) tailwindcss-ruby (4.3.3-x86_64-linux-musl) - thor (1.3.2) + thor (1.5.0) tilt (2.6.1) - timeout (0.4.3) + timeout (0.6.1) + tsort (0.2.0) turbo-rails (2.0.16) actionpack (>= 7.1.0) railties (>= 7.1.0) @@ -317,6 +322,7 @@ GEM unicode-display_width (3.1.4) unicode-emoji (~> 4.0, >= 4.0.4) unicode-emoji (4.0.4) + uri (1.1.1) useragent (0.16.11) web-console (4.2.1) actionview (>= 6.0.0) @@ -324,13 +330,13 @@ GEM bindex (>= 0.4.0) railties (>= 6.0.0) websocket (1.2.11) - websocket-driver (0.8.0) + websocket-driver (0.8.2) base64 websocket-extensions (>= 0.1.0) websocket-extensions (0.1.5) xpath (3.2.0) nokogiri (~> 1.8) - zeitwerk (2.7.3) + zeitwerk (2.8.3) PLATFORMS aarch64-linux @@ -355,7 +361,7 @@ DEPENDENCIES pg (~> 1.1) puma (>= 5.0) rack-attack - rails (~> 7.2.2, >= 7.2.2.1) + rails (~> 8.0.5, >= 8.0.5.1) rqrcode rubocop-rails-omakase sassc-rails diff --git a/bin/setup b/bin/setup index 907b2d9..be3db3c 100755 --- a/bin/setup +++ b/bin/setup @@ -2,7 +2,6 @@ require "fileutils" APP_ROOT = File.expand_path("..", __dir__) -APP_NAME = "venture-ferris" def system!(*args) system(*args, exception: true) @@ -14,7 +13,6 @@ FileUtils.chdir APP_ROOT do # Add necessary setup steps to this file. puts "== Installing dependencies ==" - system! "gem install bundler --conservative" system("bundle check") || system!("bundle install") # puts "\n== Copying sample files ==" @@ -28,10 +26,9 @@ FileUtils.chdir APP_ROOT do puts "\n== Removing old logs and tempfiles ==" system! "bin/rails log:clear tmp:clear" - puts "\n== Restarting application server ==" - system! "bin/rails restart" - - # puts "\n== Configuring puma-dev ==" - # system "ln -nfs #{APP_ROOT} ~/.puma-dev/#{APP_NAME}" - # system "curl -Is https://#{APP_NAME}.test/up | head -n 1" + unless ARGV.include?("--skip-server") + puts "\n== Starting development server ==" + STDOUT.flush # flush the output before exec(2) so that it displays + exec "bin/dev" + end end diff --git a/config/application.rb b/config/application.rb index aaf7ac8..13b2ff5 100644 --- a/config/application.rb +++ b/config/application.rb @@ -9,7 +9,7 @@ module VentureFerris class Application < Rails::Application # Initialize configuration defaults for originally generated Rails version. - config.load_defaults 7.2 + config.load_defaults 8.0 # Please, add to the `ignore` list any other `lib` subdirectories that do # not contain `.rb` files, or that should not be reloaded or eager loaded. diff --git a/config/environments/development.rb b/config/environments/development.rb index 5d5d683..3833711 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -3,9 +3,7 @@ Rails.application.configure do # Settings specified here will take precedence over those in config/application.rb. - # In the development environment your application's code is reloaded any time - # it changes. This slows down response time but is perfect for development - # since you don't have to restart the web server when you make code changes. + # Make code changes take effect immediately without server restart. config.enable_reloading = true # Do not eager load code on boot. @@ -17,50 +15,48 @@ # Enable server timing. config.server_timing = true - # Enable/disable caching. By default caching is disabled. - # Run rails dev:cache to toggle caching. + # Enable/disable Action Controller caching. By default Action Controller caching is disabled. + # Run rails dev:cache to toggle Action Controller caching. if Rails.root.join("tmp/caching-dev.txt").exist? config.action_controller.perform_caching = true config.action_controller.enable_fragment_cache_logging = true - - config.cache_store = :memory_store - config.public_file_server.headers = { "Cache-Control" => "public, max-age=#{2.days.to_i}" } + config.public_file_server.headers = { "cache-control" => "public, max-age=#{2.days.to_i}" } else config.action_controller.perform_caching = false - - config.cache_store = :null_store end + # Change to :null_store to avoid any caching. + config.cache_store = :memory_store + # Store uploaded files on the local file system (see config/storage.yml for options). config.active_storage.service = :local # Don't care if the mailer can't send. config.action_mailer.raise_delivery_errors = false - # Disable caching for Action Mailer templates even if Action Controller - # caching is enabled. + # Make template changes take effect immediately. config.action_mailer.perform_caching = false + # Set localhost to be used by links generated in mailer templates. config.action_mailer.default_url_options = { host: "localhost", port: 3000 } # Print deprecation notices to the Rails logger. config.active_support.deprecation = :log - # Raise exceptions for disallowed deprecations. - config.active_support.disallowed_deprecation = :raise - - # Tell Active Support which deprecation messages to disallow. - config.active_support.disallowed_deprecation_warnings = [] - # Raise an error on page load if there are pending migrations. config.active_record.migration_error = :page_load # Highlight code that triggered database queries in logs. config.active_record.verbose_query_logs = true + # Append comments with runtime information tags to SQL queries in logs. + config.active_record.query_log_tags_enabled = true + # Highlight code that enqueued background job in logs. config.active_job.verbose_enqueue_logs = true + # Sprockets settings. The Rails 8 template omits these because it assumes + # Propshaft; this app still runs the sprockets pipeline (see Gemfile). # Suppress logger output for asset requests. config.assets.quiet = true config.assets.debug = true diff --git a/config/environments/production.rb b/config/environments/production.rb index e3aaecb..8af77dd 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -6,44 +6,29 @@ # Code is not reloaded between requests. config.enable_reloading = false - # Eager load code on boot. This eager loads most of Rails and - # your application in memory, allowing both threaded web servers - # and those relying on copy on write to perform better. - # Rake tasks automatically ignore this option for performance. + # Eager load code on boot for better performance and memory savings (ignored by Rake tasks). config.eager_load = true - # Full error reports are disabled and caching is turned on. + # Full error reports are disabled. config.consider_all_requests_local = false - config.action_controller.perform_caching = true - - # Ensures that a master key has been made available in ENV["RAILS_MASTER_KEY"], config/master.key, or an environment - # key such as config/credentials/production.key. This key is used to decrypt credentials (and other encrypted files). - # config.require_master_key = true - # Disable serving static files from `public/`, relying on NGINX/Apache to do so instead. - # config.public_file_server.enabled = false + # Turn on fragment caching in view templates. + config.action_controller.perform_caching = true - # Compress CSS using a preprocessor. - # config.assets.css_compressor = :sass + # Cache assets for far-future expiry since they are all digest stamped. + config.public_file_server.headers = { "cache-control" => "public, max-age=#{1.year.to_i}" } - # Do not fall back to assets pipeline if a precompiled asset is missed. + # Do not fall back to the asset pipeline if a precompiled asset is missed. + # (Sprockets defaults this to true; the Rails 8 template omits the line + # because it assumes Propshaft, which has no runtime compilation at all.) config.assets.compile = false # Enable serving of images, stylesheets, and JavaScripts from an asset server. # config.asset_host = "http://assets.example.com" - # Specifies the header that your server uses for sending files. - # config.action_dispatch.x_sendfile_header = "X-Sendfile" # for Apache - # config.action_dispatch.x_sendfile_header = "X-Accel-Redirect" # for NGINX - # Store uploaded files on the local file system (see config/storage.yml for options). config.active_storage.service = :local - # Mount Action Cable outside main process or domain. - # config.action_cable.mount_path = nil - # config.action_cable.url = "wss://example.com/cable" - # config.action_cable.allowed_request_origins = [ "http://example.com", /http:\/\/example.*/ ] - # Assume all access to the app is happening through a SSL-terminating reverse proxy. # Can be used together with config.force_ssl for Strict-Transport-Security and secure cookies. # Fly.io's edge proxy terminates TLS and forwards plain HTTP to the app over @@ -58,41 +43,48 @@ # Skip http-to-https redirect for the default health check endpoint. # config.ssl_options = { redirect: { exclude: ->(request) { request.path == "/up" } } } - # Log to STDOUT by default - config.logger = ActiveSupport::Logger.new(STDOUT) - .tap { |logger| logger.formatter = ::Logger::Formatter.new } - .then { |logger| ActiveSupport::TaggedLogging.new(logger) } - - # Prepend all log lines with the following tags. + # Log to STDOUT with the current request id as a default log tag. config.log_tags = [ :request_id ] + config.logger = ActiveSupport::TaggedLogging.logger(STDOUT) - # "info" includes generic and useful information about system operation, but avoids logging too much - # information to avoid inadvertent exposure of personally identifiable information (PII). If you - # want to log everything, set the level to "debug". + # Change to "debug" to log everything (including potentially personally-identifiable information!) config.log_level = ENV.fetch("RAILS_LOG_LEVEL", "info") - # Use a different cache store in production. + # Prevent health checks from clogging up the logs. + config.silence_healthcheck_path = "/up" + + # Don't log any deprecations. + config.active_support.report_deprecations = false + + # Replace the default in-process memory cache store with a durable alternative. # config.cache_store = :mem_cache_store - # Use a real queuing backend for Active Job (and separate queues per environment). + # Replace the default in-process and non-durable queuing backend for Active Job. # config.active_job.queue_adapter = :resque - # config.active_job.queue_name_prefix = "venture_ferris_production" - - # Disable caching for Action Mailer templates even if Action Controller - # caching is enabled. - config.action_mailer.perform_caching = false # Ignore bad email addresses and do not raise email delivery errors. # Set this to true and configure the email server for immediate delivery to raise delivery errors. # config.action_mailer.raise_delivery_errors = false + # Set host to be used by links generated in mailer templates. + # This app ships no mailers (only the generated ApplicationMailer skeleton), + # so the Rails 8 template's `host: "example.com"` placeholder is left off + # rather than committing a hostname the deployment does not actually use. + # config.action_mailer.default_url_options = { host: "example.com" } + + # Specify outgoing SMTP server. Remember to add smtp/* credentials via rails credentials:edit. + # config.action_mailer.smtp_settings = { + # user_name: Rails.application.credentials.dig(:smtp, :user_name), + # password: Rails.application.credentials.dig(:smtp, :password), + # address: "smtp.example.com", + # port: 587, + # authentication: :plain + # } + # Enable locale fallbacks for I18n (makes lookups for any locale fall back to # the I18n.default_locale when a translation cannot be found). config.i18n.fallbacks = true - # Don't log any deprecations. - config.active_support.report_deprecations = false - # Do not dump schema after migrations. config.active_record.dump_schema_after_migration = false diff --git a/config/environments/test.rb b/config/environments/test.rb index 0c616a1..c2095b1 100644 --- a/config/environments/test.rb +++ b/config/environments/test.rb @@ -1,5 +1,3 @@ -require "active_support/core_ext/integer/time" - # The test environment is used exclusively to run your application's # test suite. You never need to work with it otherwise. Remember that # your test database is "scratch space" for the test suite and is wiped @@ -17,12 +15,11 @@ # loading is working properly before deploying your code. config.eager_load = ENV["CI"].present? - # Configure public file server for tests with Cache-Control for performance. - config.public_file_server.headers = { "Cache-Control" => "public, max-age=#{1.hour.to_i}" } + # Configure public file server for tests with cache-control for performance. + config.public_file_server.headers = { "cache-control" => "public, max-age=3600" } - # Show full error reports and disable caching. + # Show full error reports. config.consider_all_requests_local = true - config.action_controller.perform_caching = false config.cache_store = :null_store # Render exception templates for rescuable exceptions and raise for other exceptions. @@ -34,28 +31,17 @@ # Store uploaded files on the local file system in a temporary directory. config.active_storage.service = :test - # Disable caching for Action Mailer templates even if Action Controller - # caching is enabled. - config.action_mailer.perform_caching = false - # Tell Action Mailer not to deliver emails to the real world. # The :test delivery method accumulates sent emails in the # ActionMailer::Base.deliveries array. config.action_mailer.delivery_method = :test - # Unlike controllers, the mailer instance doesn't have any context about the - # incoming request so you'll need to provide the :host parameter yourself. - config.action_mailer.default_url_options = { host: "www.example.com" } + # Set host to be used by links generated in mailer templates. + config.action_mailer.default_url_options = { host: "example.com" } # Print deprecation notices to the stderr. config.active_support.deprecation = :stderr - # Raise exceptions for disallowed deprecations. - config.active_support.disallowed_deprecation = :raise - - # Tell Active Support which deprecation messages to disallow. - config.active_support.disallowed_deprecation_warnings = [] - # Raises error for missing translations. # config.i18n.raise_on_missing_translations = true diff --git a/config/initializers/assets.rb b/config/initializers/assets.rb index bd5bcd2..4873244 100644 --- a/config/initializers/assets.rb +++ b/config/initializers/assets.rb @@ -5,8 +5,3 @@ # Add additional assets to the asset load path. # Rails.application.config.assets.paths << Emoji.images_path - -# 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 ] diff --git a/config/initializers/filter_parameter_logging.rb b/config/initializers/filter_parameter_logging.rb index c010b83..c0b717f 100644 --- a/config/initializers/filter_parameter_logging.rb +++ b/config/initializers/filter_parameter_logging.rb @@ -4,5 +4,5 @@ # Use this to limit dissemination of sensitive information. # See the ActiveSupport::ParameterFilter documentation for supported notations and behaviors. Rails.application.config.filter_parameters += [ - :passw, :email, :secret, :token, :_key, :crypt, :salt, :certificate, :otp, :ssn + :passw, :email, :secret, :token, :_key, :crypt, :salt, :certificate, :otp, :ssn, :cvv, :cvc ] diff --git a/config/puma.rb b/config/puma.rb index 03c166f..787e4ce 100644 --- a/config/puma.rb +++ b/config/puma.rb @@ -1,13 +1,17 @@ # This configuration file will be evaluated by Puma. The top-level methods that # are invoked here are part of Puma's configuration DSL. For more information # about methods provided by the DSL, see https://puma.io/puma/Puma/DSL.html. - +# # Puma starts a configurable number of processes (workers) and each process # serves each request in a thread from an internal thread pool. # +# You can control the number of workers using ENV["WEB_CONCURRENCY"]. You +# should only set this value when you want to run 2 or more workers. The +# default is already 1. +# # The ideal number of threads per worker depends both on how much time the # application spends waiting for IO operations and on how much you wish to -# to prioritize throughput over latency. +# prioritize throughput over latency. # # As a rule of thumb, increasing the number of threads will increase how much # traffic a given process can handle (throughput), but due to CRuby's diff --git a/public/400.html b/public/400.html new file mode 100644 index 0000000..282dbc8 --- /dev/null +++ b/public/400.html @@ -0,0 +1,114 @@ + + + + + + + The server cannot process the request due to a client error (400 Bad Request) + + + + + + + + + + + + + +
+
+ +
+
+

The server cannot process the request due to a client error. Please check the request and try again. If you’re the application owner check the logs for more information.

+
+
+ + + + diff --git a/public/404.html b/public/404.html index 2be3af2..c0670bc 100644 --- a/public/404.html +++ b/public/404.html @@ -1,67 +1,114 @@ - - - - The page you were looking for doesn't exist (404) - - - - - - -
-
-

The page you were looking for doesn't exist.

-

You may have mistyped the address or the page may have moved.

-
-

If you are the application owner check the logs for more information.

-
- + + + + + + + The page you were looking for doesn’t exist (404 Not found) + + + + + + + + + + + + + +
+
+ +
+
+

The page you were looking for doesn’t exist. You may have mistyped the address or the page may have moved. If you’re the application owner check the logs for more information.

+
+
+ + + diff --git a/public/406-unsupported-browser.html b/public/406-unsupported-browser.html index 7cf1e16..9532a9c 100644 --- a/public/406-unsupported-browser.html +++ b/public/406-unsupported-browser.html @@ -1,66 +1,114 @@ - - - - Your browser is not supported (406) - - - - - - -
-
-

Your browser is not supported.

-

Please upgrade your browser to continue.

-
-
- + + + + + + + Your browser is not supported (406 Not Acceptable) + + + + + + + + + + + + + +
+
+ +
+
+

Your browser is not supported.
Please upgrade your browser to continue.

+
+
+ + + diff --git a/public/422.html b/public/422.html index c08eac0..8bcf060 100644 --- a/public/422.html +++ b/public/422.html @@ -1,67 +1,114 @@ - - - - The change you wanted was rejected (422) - - - - - - -
-
-

The change you wanted was rejected.

-

Maybe you tried to change something you didn't have access to.

-
-

If you are the application owner check the logs for more information.

-
- + + + + + + + The change you wanted was rejected (422 Unprocessable Entity) + + + + + + + + + + + + + +
+
+ +
+
+

The change you wanted was rejected. Maybe you tried to change something you didn’t have access to. If you’re the application owner check the logs for more information.

+
+
+ + + diff --git a/public/500.html b/public/500.html index 78a030a..d77718c 100644 --- a/public/500.html +++ b/public/500.html @@ -1,66 +1,114 @@ - - - - We're sorry, but something went wrong (500) - - - - - - -
-
-

We're sorry, but something went wrong.

-
-

If you are the application owner check the logs for more information.

-
- + + + + + + + We’re sorry, but something went wrong (500 Internal Server Error) + + + + + + + + + + + + + +
+
+ +
+
+

We’re sorry, but something went wrong.
If you’re the application owner check the logs for more information.

+
+
+ + + diff --git a/public/icon.png b/public/icon.png index f3b5abcbde91cf6d7a6a26e514eb7e30f476f950..c4c9dbfbbd2f7c1421ffd5727188146213abbcef 100644 GIT binary patch literal 4166 zcmd6qU;WFw?|v@m)Sk^&NvB8tcujdV-r1b=i(NJxn&7{KTb zX$3(M+3TP2o^#KAo{#tIjl&t~(8D-k004kqPglzn0HFG(Q~(I*AKsD#M*g7!XK0T7 zN6P7j>HcT8rZgKl$v!xr806dyN19Bd4C0x_R*I-a?#zsTvb_89cyhuC&T**i|Rc zq5b8M;+{8KvoJ~uj9`u~d_f6`V&3+&ZX9x5pc8s)d175;@pjm(?dapmBcm0&vl9+W zx1ZD2o^nuyUHWj|^A8r>lUorO`wFF;>9XL-Jy!P}UXC{(z!FO%SH~8k`#|9;Q|eue zqWL0^Bp(fg_+Pkm!fDKRSY;+^@BF?AJE zCUWpXPst~hi_~u)SzYBDZroR+Z4xeHIlm_3Yc_9nZ(o_gg!jDgVa=E}Y8uDgem9`b zf=mfJ_@(BXSkW53B)F2s!&?_R4ptb1fYXlF++@vPhd=marQgEGRZS@B4g1Mu?euknL= z67P~tZ?*>-Hmi7GwlisNHHJDku-dSm7g@!=a}9cSL6Pa^w^2?&?$Oi8ibrr>w)xqx zOH_EMU@m05)9kuNR>>4@H%|){U$^yvVQ(YgOlh;5oU_-vivG-p4=LrN-k7D?*?u1u zsWly%tfAzKd6Fb=`eU2un_uaTXmcT#tlOL+aRS=kZZf}A7qT8lvcTx~7j` z*b>=z)mwg7%B2_!D0!1IZ?Nq{^Y$uI4Qx*6T!E2Col&2{k?ImCO=dD~A&9f9diXy^$x{6CwkBimn|1E09 zAMSezYtiL?O6hS37KpvDM?22&d{l)7h-!F)C-d3j8Z`c@($?mfd{R82)H>Qe`h{~G z!I}(2j(|49{LR?w4Jspl_i!(4T{31|dqCOpI52r5NhxYV+cDAu(xp*4iqZ2e-$YP= zoFOPmm|u*7C?S{Fp43y+V;>~@FFR76bCl@pTtyB93vNWy5yf;HKr8^0d7&GVIslYm zo3Tgt@M!`8B6IW&lK{Xk>%zp41G%`(DR&^u z5^pwD4>E6-w<8Kl2DzJ%a@~QDE$(e87lNhy?-Qgep!$b?5f7+&EM7$e>|WrX+=zCb z=!f5P>MxFyy;mIRxjc(H*}mceXw5a*IpC0PEYJ8Y3{JdoIW)@t97{wcUB@u+$FCCO z;s2Qe(d~oJC^`m$7DE-dsha`glrtu&v&93IZadvl_yjp!c89>zo;Krk+d&DEG4?x$ zufC1n+c1XD7dolX1q|7}uelR$`pT0Z)1jun<39$Sn2V5g&|(j~Z!wOddfYiZo7)A< z!dK`aBHOOk+-E_xbWCA3VR-+o$i5eO9`rMI#p_0xQ}rjEpGW;U!&&PKnivOcG(|m9 z!C8?WC6nCXw25WVa*eew)zQ=h45k8jSIPbq&?VE{oG%?4>9rwEeB4&qe#?-y_es4c|7ufw%+H5EY#oCgv!Lzv291#-oNlX~X+Jl5(riC~r z=0M|wMOP)Tt8@hNg&%V@Z9@J|Q#K*hE>sr6@oguas9&6^-=~$*2Gs%h#GF@h)i=Im z^iKk~ipWJg1VrvKS;_2lgs3n1zvNvxb27nGM=NXE!D4C!U`f*K2B@^^&ij9y}DTLB*FI zEnBL6y{jc?JqXWbkIZd7I16hA>(f9T!iwbIxJj~bKPfrO;>%*5nk&Lf?G@c2wvGrY&41$W{7HM9+b@&XY@>NZM5s|EK_Dp zQX60CBuantx>|d#DsaZ*8MW(we|#KTYZ=vNa#d*DJQe6hr~J6{_rI#?wi@s|&O}FR zG$kfPxheXh1?IZ{bDT-CWB4FTvO-k5scW^mi8?iY5Q`f8JcnnCxiy@m@D-%lO;y0pTLhh6i6l@x52j=#^$5_U^os}OFg zzdHbo(QI`%9#o*r8GCW~T3UdV`szO#~)^&X_(VW>o~umY9-ns9-V4lf~j z`QBD~pJ4a#b`*6bJ^3RS5y?RAgF7K5$ll97Y8#WZduZ`j?IEY~H(s^doZg>7-tk*t z4_QE1%%bb^p~4F5SB$t2i1>DBG1cIo;2(xTaj*Y~hlM{tSDHojL-QPg%Mo%6^7FrpB*{ z4G0@T{-77Por4DCMF zB_5Y~Phv%EQ64W8^GS6h?x6xh;w2{z3$rhC;m+;uD&pR74j+i22P5DS-tE8ABvH(U~indEbBUTAAAXfHZg5QpB@TgV9eI<)JrAkOI z8!TSOgfAJiWAXeM&vR4Glh;VxH}WG&V$bVb`a`g}GSpwggti*&)taV1@Ak|{WrV|5 zmNYx)Ans=S{c52qv@+jmGQ&vd6>6yX6IKq9O$3r&0xUTdZ!m1!irzn`SY+F23Rl6# zFRxws&gV-kM1NX(3(gnKpGi0Q)Dxi~#?nyzOR9!en;Ij>YJZVFAL*=R%7y%Mz9hU% zs>+ZB?qRmZ)nISx7wxY)y#cd$iaC~{k0avD>BjyF1q^mNQ1QcwsxiTySe<6C&cC6P zE`vwO9^k-d`9hZ!+r@Jnr+MF*2;2l8WjZ}DrwDUHzSF{WoG zucbSWguA!3KgB3MU%HH`R;XqVv0CcaGq?+;v_A5A2kpmk5V%qZE3yzQ7R5XWhq=eR zyUezH=@V)y>L9T-M-?tW(PQYTRBKZSVb_!$^H-Pn%ea;!vS_?M<~Tm>_rWIW43sPW z=!lY&fWc1g7+r?R)0p8(%zp&vl+FK4HRkns%BW+Up&wK8!lQ2~bja|9bD12WrKn#M zK)Yl9*8$SI7MAwSK$%)dMd>o+1UD<2&aQMhyjS5R{-vV+M;Q4bzl~Z~=4HFj_#2V9 zB)Gfzx3ncy@uzx?yzi}6>d%-?WE}h7v*w)Jr_gBl!2P&F3DX>j_1#--yjpL%<;JMR z*b70Gr)MMIBWDo~#<5F^Q0$VKI;SBIRneuR7)yVsN~A9I@gZTXe)E?iVII+X5h0~H zx^c(fP&4>!*q>fb6dAOC?MI>Cz3kld#J*;uik+Ps49cwm1B4 zZc1|ZxYyTv;{Z!?qS=D)sgRKx^1AYf%;y_V&VgZglfU>d+Ufk5&LV$sKv}Hoj+s; xK3FZRYdhbXT_@RW*ff3@`D1#ps#~H)p+y&j#(J|vk^lW{fF9OJt5(B-_&*Xgn9~3N literal 5599 zcmeHL-D}fO6hCR_taXJlzs3}~RuB=Iujyo=i*=1|1FN%E=zNfMTjru|Q<6v{J{U!C zBEE}?j6I3sz>fzN!6}L_BKjcuASk~1;Dg|U_@d{g?V8mM`~#9U+>>*Ezw>c(PjYWA z4(;!cgge6k5E&d$G5`S-0}!Ik>CV(0Y#1}s-v_gAHhja2=W1?nBAte9D2HG<(+)uj z!5=W4u*{VKMw#{V@^NNs4TClr!FAA%ID-*gc{R%CFKEzG<6gm*9s_uy)oMGW*=nJf zw{(Mau|2FHfXIv6C0@Wk5k)F=3jo1srV-C{pl&k&)4_&JjYrnbJiul}d0^NCSh(#7h=F;3{|>EU>h z6U8_p;^wK6mAB(1b92>5-HxJ~V}@3?G`&Qq-TbJ2(&~-HsH6F#8mFaAG(45eT3VPO zM|(Jd<+;UZs;w>0Qw}0>D%{~r{uo_Fl5_Bo3ABWi zWo^j^_T3dxG6J6fH8X)$a^%TJ#PU!=LxF=#Fd9EvKx_x>q<(KY%+y-08?kN9dXjXK z**Q=yt-FTU*13ouhCdqq-0&;Ke{T3sQU9IdzhV9LhQIpq*P{N)+}|Mh+a-VV=x?R} c>%+pvTcMWshj-umO}|qP?%A)*_KlqT3uEqhU;qFB diff --git a/public/icon.svg b/public/icon.svg index 78307cc..04b34bf 100644 --- a/public/icon.svg +++ b/public/icon.svg @@ -1,3 +1,3 @@ - - + + From 94be6426f010c7fc3df5d197bc796806b940266c Mon Sep 17 00:00:00 2001 From: Tim Hsueh Date: Wed, 2 Sep 2026 10:18:09 +0800 Subject: [PATCH 2/3] =?UTF-8?q?chore:=20=E5=8D=87=E7=B4=9A=20Rails=208.0.5?= =?UTF-8?q?.1=20=E8=87=B3=208.1.3.1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 接續上一批次,升到安全修補支援至 2027-10-10 的 8.1 系列,並啟用 load_defaults 8.1。 app:update 範本逐檔取捨: - 接受範本:public/ 錯誤頁、bin/rubocop 與 bin/setup(新增 --reset)、 content_security_policy 註解、development.rb 的 verbose_redirect_logs、 puma.rb 的 WEB_CONCURRENCY 說明 - 保留自訂:application.rb 的 Taipei 時區與 css_compressor nil workaround、 production.rb 的 assume_ssl/force_ssl(範本又把兩者註解掉)、 ALLOWED_HOSTS 與 /up host 豁免、assets.compile false、development.rb 的 sprockets assets.quiet/debug/compile、bin/brakeman、bin/dev - 不採用:範本新增的 bin/ci 與 config/ci.rb(與既有 GitHub Actions workflow 重複)、puma.rb 的 solid_queue plugin、action_mailer 佔位主機名、 再次誤帶入的 ActiveStorage migration schema.rb 為 8.1 的欄位字母排序一次性重排:已逐行比對確認除 Schema[7.2]→[8.1] 版本標記與 plpgsql→pg_catalog.plpgsql(8.1 改為輸出 schema 限定的擴充名稱)外,內容完全相同,無欄位增刪。 .github/workflows/ci.yml 以 .ruby-version 取版本、未寫死 Rails 版本,無需同步。 驗證:bin/rails -v 為 8.1.3.1、test 222 runs 全綠、test:system 13 runs 全綠、 rubocop 0 offenses、production assets:precompile exit 0、無 deprecation warning。 --- Gemfile | 2 +- Gemfile.lock | 133 +++++++++--------- bin/rubocop | 2 +- bin/setup | 1 + config/application.rb | 2 +- config/environments/development.rb | 3 + config/environments/production.rb | 4 +- .../initializers/content_security_policy.rb | 4 + config/puma.rb | 3 +- db/schema.rb | 92 ++++++------ docs/RAILS_UPGRADE_RESEARCH.md | 38 +++++ public/400.html | 31 +++- public/404.html | 33 ++++- public/406-unsupported-browser.html | 29 +++- public/422.html | 31 +++- public/500.html | 33 ++++- 16 files changed, 299 insertions(+), 142 deletions(-) create mode 100644 docs/RAILS_UPGRADE_RESEARCH.md diff --git a/Gemfile b/Gemfile index b802cad..b18a3f3 100644 --- a/Gemfile +++ b/Gemfile @@ -1,7 +1,7 @@ source "https://rubygems.org" # Bundle edge Rails instead: gem "rails", github: "rails/rails", branch: "main" -gem "rails", "~> 8.0.5", ">= 8.0.5.1" +gem "rails", "~> 8.1.3", ">= 8.1.3.1" # The original asset pipeline for Rails [https://github.com/rails/sprockets-rails] gem "sprockets-rails" # Use postgresql as the database for Active Record diff --git a/Gemfile.lock b/Gemfile.lock index bd9da02..e32c51b 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,29 +1,31 @@ GEM remote: https://rubygems.org/ specs: - actioncable (8.0.5.1) - actionpack (= 8.0.5.1) - activesupport (= 8.0.5.1) + action_text-trix (2.1.19) + railties + actioncable (8.1.3.1) + actionpack (= 8.1.3.1) + activesupport (= 8.1.3.1) nio4r (~> 2.0) websocket-driver (>= 0.6.1) zeitwerk (~> 2.6) - actionmailbox (8.0.5.1) - actionpack (= 8.0.5.1) - activejob (= 8.0.5.1) - activerecord (= 8.0.5.1) - activestorage (= 8.0.5.1) - activesupport (= 8.0.5.1) + actionmailbox (8.1.3.1) + actionpack (= 8.1.3.1) + activejob (= 8.1.3.1) + activerecord (= 8.1.3.1) + activestorage (= 8.1.3.1) + activesupport (= 8.1.3.1) mail (>= 2.8.0) - actionmailer (8.0.5.1) - actionpack (= 8.0.5.1) - actionview (= 8.0.5.1) - activejob (= 8.0.5.1) - activesupport (= 8.0.5.1) + actionmailer (8.1.3.1) + actionpack (= 8.1.3.1) + actionview (= 8.1.3.1) + activejob (= 8.1.3.1) + activesupport (= 8.1.3.1) mail (>= 2.8.0) rails-dom-testing (~> 2.2) - actionpack (8.0.5.1) - actionview (= 8.0.5.1) - activesupport (= 8.0.5.1) + actionpack (8.1.3.1) + actionview (= 8.1.3.1) + activesupport (= 8.1.3.1) nokogiri (>= 1.8.5) rack (>= 2.2.4) rack-session (>= 1.0.1) @@ -31,42 +33,43 @@ GEM rails-dom-testing (~> 2.2) rails-html-sanitizer (~> 1.6) useragent (~> 0.16) - actiontext (8.0.5.1) - actionpack (= 8.0.5.1) - activerecord (= 8.0.5.1) - activestorage (= 8.0.5.1) - activesupport (= 8.0.5.1) + actiontext (8.1.3.1) + action_text-trix (~> 2.1.15) + actionpack (= 8.1.3.1) + activerecord (= 8.1.3.1) + activestorage (= 8.1.3.1) + activesupport (= 8.1.3.1) globalid (>= 0.6.0) nokogiri (>= 1.8.5) - actionview (8.0.5.1) - activesupport (= 8.0.5.1) + actionview (8.1.3.1) + activesupport (= 8.1.3.1) builder (~> 3.1) erubi (~> 1.11) rails-dom-testing (~> 2.2) rails-html-sanitizer (~> 1.6) - activejob (8.0.5.1) - activesupport (= 8.0.5.1) + activejob (8.1.3.1) + activesupport (= 8.1.3.1) globalid (>= 0.3.6) - activemodel (8.0.5.1) - activesupport (= 8.0.5.1) - activerecord (8.0.5.1) - activemodel (= 8.0.5.1) - activesupport (= 8.0.5.1) + activemodel (8.1.3.1) + activesupport (= 8.1.3.1) + activerecord (8.1.3.1) + activemodel (= 8.1.3.1) + activesupport (= 8.1.3.1) timeout (>= 0.4.0) - activestorage (8.0.5.1) - actionpack (= 8.0.5.1) - activejob (= 8.0.5.1) - activerecord (= 8.0.5.1) - activesupport (= 8.0.5.1) + activestorage (8.1.3.1) + actionpack (= 8.1.3.1) + activejob (= 8.1.3.1) + activerecord (= 8.1.3.1) + activesupport (= 8.1.3.1) marcel (~> 1.0) - activesupport (8.0.5.1) + activesupport (8.1.3.1) base64 - benchmark (>= 0.3) bigdecimal concurrent-ruby (~> 1.0, >= 1.3.1) connection_pool (>= 2.2.5) drb i18n (>= 1.6, < 2) + json logger (>= 1.4.2) minitest (>= 5.1) securerandom (>= 0.3) @@ -77,7 +80,6 @@ GEM ast (2.4.3) base64 (0.3.0) bcrypt (3.1.22) - benchmark (0.5.0) bigdecimal (4.1.2) bindex (0.8.1) bootsnap (1.18.6) @@ -146,7 +148,9 @@ GEM marcel (1.2.1) matrix (0.4.3) mini_mime (1.1.5) - minitest (5.27.0) + minitest (6.0.6) + drb (~> 2.0) + prism (~> 1.5) msgpack (1.8.0) net-imap (0.6.6) date @@ -182,10 +186,7 @@ GEM pp (0.6.4) prettyprint prettyprint (0.2.0) - prism (1.4.0) - psych (5.5.0) - date - stringio + prism (1.9.0) public_suffix (6.0.2) puma (6.6.0) nio4r (~> 2.0) @@ -200,20 +201,20 @@ GEM rack (>= 1.3) rackup (2.3.1) rack (>= 3) - rails (8.0.5.1) - actioncable (= 8.0.5.1) - actionmailbox (= 8.0.5.1) - actionmailer (= 8.0.5.1) - actionpack (= 8.0.5.1) - actiontext (= 8.0.5.1) - actionview (= 8.0.5.1) - activejob (= 8.0.5.1) - activemodel (= 8.0.5.1) - activerecord (= 8.0.5.1) - activestorage (= 8.0.5.1) - activesupport (= 8.0.5.1) + rails (8.1.3.1) + actioncable (= 8.1.3.1) + actionmailbox (= 8.1.3.1) + actionmailer (= 8.1.3.1) + actionpack (= 8.1.3.1) + actiontext (= 8.1.3.1) + actionview (= 8.1.3.1) + activejob (= 8.1.3.1) + activemodel (= 8.1.3.1) + activerecord (= 8.1.3.1) + activestorage (= 8.1.3.1) + activesupport (= 8.1.3.1) bundler (>= 1.15.0) - railties (= 8.0.5.1) + railties (= 8.1.3.1) rails-dom-testing (2.3.0) activesupport (>= 5.0.0) minitest @@ -221,9 +222,9 @@ GEM rails-html-sanitizer (1.7.1) loofah (~> 2.25, >= 2.25.2) nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0) - railties (8.0.5.1) - actionpack (= 8.0.5.1) - activesupport (= 8.0.5.1) + railties (8.1.3.1) + actionpack (= 8.1.3.1) + activesupport (= 8.1.3.1) irb (~> 1.13) rackup (>= 1.0.0) rake (>= 12.2) @@ -232,9 +233,14 @@ GEM zeitwerk (~> 2.6) rainbow (3.1.1) rake (13.4.2) - rdoc (7.2.0) + rbs (4.2.0) + logger + prism (>= 1.6.0) + tsort + rdoc (8.0.0) erb - psych (>= 4.0.0) + prism (>= 1.6.0) + rbs (>= 4.0.0) tsort regexp_parser (2.10.0) reline (0.7.0) @@ -299,7 +305,6 @@ GEM sprockets (>= 3.0.0) stimulus-rails (1.3.4) railties (>= 6.0.0) - stringio (3.2.0) tailwindcss-rails (4.6.0) railties (>= 7.0.0) tailwindcss-ruby (~> 4.0) @@ -361,7 +366,7 @@ DEPENDENCIES pg (~> 1.1) puma (>= 5.0) rack-attack - rails (~> 8.0.5, >= 8.0.5.1) + rails (~> 8.1.3, >= 8.1.3.1) rqrcode rubocop-rails-omakase sassc-rails diff --git a/bin/rubocop b/bin/rubocop index 40330c0..5a20504 100755 --- a/bin/rubocop +++ b/bin/rubocop @@ -2,7 +2,7 @@ require "rubygems" require "bundler/setup" -# explicit rubocop config increases performance slightly while avoiding config confusion. +# Explicit RuboCop config increases performance slightly while avoiding config confusion. ARGV.unshift("--config", File.expand_path("../.rubocop.yml", __dir__)) load Gem.bin_path("rubocop", "rubocop") diff --git a/bin/setup b/bin/setup index be3db3c..81be011 100755 --- a/bin/setup +++ b/bin/setup @@ -22,6 +22,7 @@ FileUtils.chdir APP_ROOT do puts "\n== Preparing database ==" system! "bin/rails db:prepare" + system! "bin/rails db:reset" if ARGV.include?("--reset") puts "\n== Removing old logs and tempfiles ==" system! "bin/rails log:clear tmp:clear" diff --git a/config/application.rb b/config/application.rb index 13b2ff5..bf7f89c 100644 --- a/config/application.rb +++ b/config/application.rb @@ -9,7 +9,7 @@ module VentureFerris class Application < Rails::Application # Initialize configuration defaults for originally generated Rails version. - config.load_defaults 8.0 + config.load_defaults 8.1 # Please, add to the `ignore` list any other `lib` subdirectories that do # not contain `.rb` files, or that should not be reloaded or eager loaded. diff --git a/config/environments/development.rb b/config/environments/development.rb index 3833711..cffd7bb 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -55,6 +55,9 @@ # Highlight code that enqueued background job in logs. config.active_job.verbose_enqueue_logs = true + # Highlight code that triggered redirect in logs. + config.action_dispatch.verbose_redirect_logs = true + # Sprockets settings. The Rails 8 template omits these because it assumes # Propshaft; this app still runs the sprockets pipeline (see Gemfile). # Suppress logger output for asset requests. diff --git a/config/environments/production.rb b/config/environments/production.rb index 8af77dd..1050129 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -47,7 +47,7 @@ config.log_tags = [ :request_id ] config.logger = ActiveSupport::TaggedLogging.logger(STDOUT) - # Change to "debug" to log everything (including potentially personally-identifiable information!) + # Change to "debug" to log everything (including potentially personally-identifiable information!). config.log_level = ENV.fetch("RAILS_LOG_LEVEL", "info") # Prevent health checks from clogging up the logs. @@ -72,7 +72,7 @@ # rather than committing a hostname the deployment does not actually use. # config.action_mailer.default_url_options = { host: "example.com" } - # Specify outgoing SMTP server. Remember to add smtp/* credentials via rails credentials:edit. + # Specify outgoing SMTP server. Remember to add smtp/* credentials via bin/rails credentials:edit. # config.action_mailer.smtp_settings = { # user_name: Rails.application.credentials.dig(:smtp, :user_name), # password: Rails.application.credentials.dig(:smtp, :password), diff --git a/config/initializers/content_security_policy.rb b/config/initializers/content_security_policy.rb index b3076b3..d51d713 100644 --- a/config/initializers/content_security_policy.rb +++ b/config/initializers/content_security_policy.rb @@ -20,6 +20,10 @@ # config.content_security_policy_nonce_generator = ->(request) { request.session.id.to_s } # config.content_security_policy_nonce_directives = %w(script-src style-src) # +# # Automatically add `nonce` to `javascript_tag`, `javascript_include_tag`, and `stylesheet_link_tag` +# # if the corresponding directives are specified in `content_security_policy_nonce_directives`. +# # config.content_security_policy_nonce_auto = true +# # # Report violations without enforcing the policy. # # config.content_security_policy_report_only = true # end diff --git a/config/puma.rb b/config/puma.rb index 787e4ce..1c317b4 100644 --- a/config/puma.rb +++ b/config/puma.rb @@ -7,7 +7,8 @@ # # You can control the number of workers using ENV["WEB_CONCURRENCY"]. You # should only set this value when you want to run 2 or more workers. The -# default is already 1. +# default is already 1. You can set it to `auto` to automatically start a worker +# for each available processor. # # The ideal number of threads per worker depends both on how much time the # application spends waiting for IO operations and on how much you wish to diff --git a/db/schema.rb b/db/schema.rb index b9997fe..3a3ce3f 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,31 +10,31 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.2].define(version: 2026_09_01_000000) do +ActiveRecord::Schema[8.1].define(version: 2026_09_01_000000) do # These are extensions that must be enabled in order to support this database - enable_extension "plpgsql" + enable_extension "pg_catalog.plpgsql" create_table "admins", force: :cascade do |t| + t.datetime "created_at", null: false t.string "email", null: false t.string "password_digest", null: false - t.datetime "created_at", null: false - t.datetime "updated_at", null: false t.integer "role", default: 0, null: false + t.datetime "updated_at", null: false t.index ["email"], name: "index_admins_on_email", unique: true end create_table "boss_battles", force: :cascade do |t| - t.bigint "team_id", null: false - t.datetime "started_at" - t.datetime "ended_at" t.integer "attack_count", default: 0, null: false - t.integer "hp", default: 120, null: false + t.integer "bonus_time_seconds", default: 0, null: false t.datetime "created_at", null: false - t.datetime "updated_at", null: false + t.datetime "ended_at" + t.integer "hp", default: 120, null: false t.datetime "last_critical_at" t.bigint "question_id", null: false - t.integer "bonus_time_seconds", default: 0, null: false t.datetime "spotlight_until" + t.datetime "started_at" + t.bigint "team_id", null: false + t.datetime "updated_at", null: false t.index ["question_id"], name: "index_boss_battles_on_question_id" t.index ["team_id", "question_id"], name: "index_boss_battles_on_team_id_and_question_id", unique: true t.index ["team_id"], name: "index_boss_battles_on_team_id" @@ -42,8 +42,8 @@ create_table "boss_readies", force: :cascade do |t| t.bigint "boss_battle_id", null: false - t.bigint "player_id", null: false t.datetime "created_at", null: false + t.bigint "player_id", null: false t.datetime "updated_at", null: false t.index ["boss_battle_id", "player_id"], name: "index_boss_readies_on_battle_and_player", unique: true t.index ["boss_battle_id"], name: "index_boss_readies_on_boss_battle_id" @@ -52,10 +52,10 @@ create_table "boss_skill_uses", force: :cascade do |t| t.bigint "boss_battle_id", null: false - t.bigint "player_id", null: false - t.string "skill", null: false t.datetime "consumed_at" t.datetime "created_at", null: false + t.bigint "player_id", null: false + t.string "skill", null: false t.datetime "updated_at", null: false t.index ["boss_battle_id", "player_id"], name: "index_boss_skill_uses_on_battle_and_player", unique: true t.index ["boss_battle_id"], name: "index_boss_skill_uses_on_boss_battle_id" @@ -63,21 +63,21 @@ end create_table "bosses", force: :cascade do |t| - t.string "sprite", null: false t.datetime "created_at", null: false + t.string "sprite", null: false t.datetime "updated_at", null: false t.index ["sprite"], name: "index_bosses_on_sprite", unique: true end create_table "players", force: :cascade do |t| - t.bigint "team_id", null: false - t.integer "role", null: false + t.datetime "created_at", null: false t.string "email", null: false - t.integer "job" - t.string "name" t.integer "gender", default: 0, null: false + t.integer "job" t.string "mobile" - t.datetime "created_at", null: false + t.string "name" + t.integer "role", null: false + t.bigint "team_id", null: false t.datetime "updated_at", null: false t.index ["team_id", "email"], name: "index_players_on_team_id_and_email", unique: true t.index ["team_id", "job"], name: "index_players_on_team_id_and_job", unique: true, where: "(job IS NOT NULL)" @@ -85,12 +85,12 @@ end create_table "question_attempts", force: :cascade do |t| - t.bigint "team_id", null: false - t.bigint "question_id", null: false - t.datetime "started_at" + t.datetime "created_at", null: false t.datetime "ended_at" t.integer "hint_count", default: 0, null: false - t.datetime "created_at", null: false + t.bigint "question_id", null: false + t.datetime "started_at" + t.bigint "team_id", null: false t.datetime "updated_at", null: false t.index ["question_id"], name: "index_question_attempts_on_question_id" t.index ["team_id", "question_id"], name: "index_question_attempts_on_team_id_and_question_id", unique: true @@ -98,69 +98,69 @@ end create_table "question_hints", force: :cascade do |t| - t.bigint "question_id", null: false - t.integer "position", null: false t.text "content", null: false t.datetime "created_at", null: false + t.integer "position", null: false + t.bigint "question_id", null: false t.datetime "updated_at", null: false t.index ["question_id", "position"], name: "index_question_hints_on_question_id_and_position", unique: true t.index ["question_id"], name: "index_question_hints_on_question_id" end create_table "questions", force: :cascade do |t| - t.integer "number", null: false - t.integer "kind", default: 1, null: false - t.string "title", null: false t.string "answer_digest" - t.text "content" - t.string "level" - t.text "explanation" t.boolean "auto_start", default: false, null: false t.integer "base_score", default: 1000, null: false - t.integer "puzzle_rows" - t.integer "puzzle_cols" t.integer "boss_hp", default: 120, null: false + t.bigint "boss_id", null: false + t.integer "boss_phase" t.integer "boss_time_limit", default: 30, null: false + t.text "content" t.datetime "created_at", null: false + t.text "explanation" + t.integer "kind", default: 1, null: false + t.string "level" + t.integer "number", null: false + t.integer "puzzle_cols" + t.integer "puzzle_rows" + t.string "title", null: false t.datetime "updated_at", null: false - t.bigint "boss_id", null: false - t.integer "boss_phase" t.index ["boss_id"], name: "index_questions_on_boss_id" t.index ["number"], name: "index_questions_on_number", unique: true end create_table "reward_codes", force: :cascade do |t| - t.string "code", null: false - t.boolean "test_mode", default: false, null: false - t.string "player_email" t.datetime "claimed_at" + t.string "code", null: false t.datetime "created_at", null: false + t.string "player_email" + t.boolean "test_mode", default: false, null: false t.datetime "updated_at", null: false t.index ["code"], name: "index_reward_codes_on_code", unique: true t.index ["player_email"], name: "index_reward_codes_on_player_email" end create_table "score_entries", force: :cascade do |t| - t.bigint "team_id", null: false - t.integer "question_score", default: 0, null: false - t.integer "time_score", default: 0, null: false - t.integer "hint_score", default: 0, null: false t.integer "boss_score", default: 0, null: false + t.datetime "created_at", null: false + t.integer "hint_score", default: 0, null: false t.integer "job_score", default: 0, null: false + t.bigint "question_id", null: false + t.integer "question_score", default: 0, null: false + t.bigint "team_id", null: false + t.integer "time_score", default: 0, null: false t.integer "total_score", default: 0, null: false - t.datetime "created_at", null: false t.datetime "updated_at", null: false - t.bigint "question_id", null: false t.index ["question_id"], name: "index_score_entries_on_question_id" t.index ["team_id", "question_id"], name: "index_score_entries_on_team_id_and_question_id", unique: true t.index ["team_id"], name: "index_score_entries_on_team_id" end create_table "teams", force: :cascade do |t| + t.datetime "created_at", null: false + t.string "name" t.string "serial_no", null: false t.boolean "test_mode", default: false, null: false - t.string "name" - t.datetime "created_at", null: false t.datetime "updated_at", null: false t.index ["serial_no"], name: "index_teams_on_serial_no", unique: true end diff --git a/docs/RAILS_UPGRADE_RESEARCH.md b/docs/RAILS_UPGRADE_RESEARCH.md new file mode 100644 index 0000000..2fb00f2 --- /dev/null +++ b/docs/RAILS_UPGRADE_RESEARCH.md @@ -0,0 +1,38 @@ +# Rails 7.2 EOL 與升級路徑查證 + +> 查閱日期:2026-09-02。來源:rubyonrails.org/maintenance、guides.rubyonrails.org/upgrading_ruby_on_rails.html、rubygems.org(皆官方)。 +> 註:原委派的研究 agent 因機器休眠中斷,本文件由主對話直接以官方來源完成查證。 + +## 結論摘要 + +1. **Rails 7.2.x 已完全 EOL**:安全修補止於 2026-08-09(官方 maintenance 頁;與 brakeman `EOLRails` 警告一致)。**必須升級**。 +2. **升級目標選 8.1.x(現行 8.1.3.1)**,不要停在 8.0:8.0.x 的安全修補只到 **2026-11-07**(剩兩個月),8.1.x 的 bug 修補到 2026-10-10、安全修補到 **2027-10-10**。 +3. **Ruby 3.3.5 可直上**:Rails 8.0/8.1 最低要求 Ruby 3.2.0。(Ruby 版本升級可另案,非本次前置。) +4. **官方建議逐個 minor 走**:7.2 → 8.0.5.1(app:update、修 deprecation、測試綠)→ 8.1.3.1(同流程)。 +5. **Sprockets 在 Rails 8 仍是可用的 optional gem**(7.0 起即為 optional dependency);官方升級指南對 8.x 未要求換 Propshaft。是否順勢把 sassc-rails→dartsass(README 既列的未來項)併入本次:**建議併入**——避免「升了框架還掛著 EOL 編譯器」的半吊子狀態,但作為升級後的獨立 commit,失敗可單獨退回。 +6. 官方指南明列的 8.0→8.1 變更:`schema.rb` 欄位改字母排序(會有一次性的大 schema diff,屬預期)。7.2→8.0 的細節在 release notes(實作時逐項對照 `load_defaults 8.0` 的 new_framework_defaults 檔案)。 + +## 版本支援時間表(官方 maintenance 頁) + +| 系列 | bug 修補 | 安全修補 | 狀態 | +|---|---|---|---| +| 8.1.x(8.1.3.1) | 至 2026-10-10 | 至 **2027-10-10** | ✅ 建議目標 | +| 8.0.x(8.0.5.1) | 已結束(2026-05-07) | 至 2026-11-07 | ⚠️ 僅升到此=兩個月後再升 | +| 7.2.x(本專案) | 已結束 | **已結束(2026-08-09)** | ❌ 完全 EOL | +| ≤7.1 | — | — | ❌ 完全 EOL | + +## 本專案受影響面初盤 + +- **Gemfile**:`rails ~> 7.2.2` → `~> 8.1.3`;其餘 gem(pg、puma、importmap/turbo/stimulus、rqrcode、rack-attack、tailwindcss-rails、bcrypt、sprockets-rails、sassc-rails)皆為活躍維護、預期相容(bundle update 時驗證)。 +- **config**:`load_defaults 7.2` → 逐步 8.0 → 8.1;`app:update` 產生的 new_framework_defaults 逐項審閱(本專案自訂點:`assume_ssl`、`ALLOWED_HOSTS`+`/up` 豁免、Taipei 時區、css_compressor nil——最後一項若換 dartsass 可移除)。 +- **schema.rb**:8.1 起欄位字母排序——升級後 `db:migrate` 會產生一次大而無害的 schema diff。 +- **測試安全網**:222 integration+13 system+M0 計分黃金值(升級全程不得改其字面量)。 +- **enum**:已是新語法,無虞。無 ActiveStorage/ActionCable/ActionMailbox 實際使用,升級面小。 +- **資產管線**(若併入換裝):sassc-rails → dartsass-rails(沿用 sprockets);`site.scss`/`boss.scss` 語法為一般 CSS+巢狀,dart-sass 相容性風險低;`config.assets.css_compressor = nil` workaround 屆時可望移除(dart-sass 輸出不再經 libsass 壓縮——實作時驗證)。 + +## 建議實作批次 + +1. **B1:7.2 → 8.0.5.1**(Gemfile、bundle update、app:update 逐檔審閱、deprecation 清零、全測試綠+M0 不變) +2. **B2:8.0 → 8.1.3.1**(同流程+接受 schema 排序 diff) +3. **B3(可選但建議):sassc-rails → dartsass-rails**(獨立 commit,失敗可退) +4. 全程一個 PR、三個 commit;verifier 總驗收(含 production boot、assets:precompile、demo 全鏈 curl)後合併——自動部署上線。 diff --git a/public/400.html b/public/400.html index 282dbc8..640de03 100644 --- a/public/400.html +++ b/public/400.html @@ -35,12 +35,35 @@ font-weight: 400; letter-spacing: -0.0025em; line-height: 1.4; - min-height: 100vh; + min-height: 100dvh; place-items: center; text-rendering: optimizeLegibility; -webkit-text-size-adjust: 100%; } + #error-description { + fill: #d30001; + } + + #error-id { + fill: #f0eff0; + } + + @media (prefers-color-scheme: dark) { + body { + background: #101010; + color: #e0e0e0; + } + + #error-description { + fill: #FF6161; + } + + #error-id { + fill: #2c2c2c; + } + } + a { color: inherit; font-weight: 700; @@ -83,13 +106,11 @@ } main article br { - display: none; @media(min-width: 48em) { display: inline; } - } @@ -102,10 +123,10 @@
- +
-

The server cannot process the request due to a client error. Please check the request and try again. If you’re the application owner check the logs for more information.

+

The server cannot process the request due to a client error. Please check the request and try again. If you're the application owner check the logs for more information.

diff --git a/public/404.html b/public/404.html index c0670bc..d7f0f14 100644 --- a/public/404.html +++ b/public/404.html @@ -4,7 +4,7 @@ - The page you were looking for doesn’t exist (404 Not found) + The page you were looking for doesn't exist (404 Not found) @@ -35,12 +35,35 @@ font-weight: 400; letter-spacing: -0.0025em; line-height: 1.4; - min-height: 100vh; + min-height: 100dvh; place-items: center; text-rendering: optimizeLegibility; -webkit-text-size-adjust: 100%; } + #error-description { + fill: #d30001; + } + + #error-id { + fill: #f0eff0; + } + + @media (prefers-color-scheme: dark) { + body { + background: #101010; + color: #e0e0e0; + } + + #error-description { + fill: #FF6161; + } + + #error-id { + fill: #2c2c2c; + } + } + a { color: inherit; font-weight: 700; @@ -83,13 +106,11 @@ } main article br { - display: none; @media(min-width: 48em) { display: inline; } - } @@ -102,10 +123,10 @@
- +
-

The page you were looking for doesn’t exist. You may have mistyped the address or the page may have moved. If you’re the application owner check the logs for more information.

+

The page you were looking for doesn't exist. You may have mistyped the address or the page may have moved. If you're the application owner check the logs for more information.

diff --git a/public/406-unsupported-browser.html b/public/406-unsupported-browser.html index 9532a9c..43d2811 100644 --- a/public/406-unsupported-browser.html +++ b/public/406-unsupported-browser.html @@ -35,12 +35,35 @@ font-weight: 400; letter-spacing: -0.0025em; line-height: 1.4; - min-height: 100vh; + min-height: 100dvh; place-items: center; text-rendering: optimizeLegibility; -webkit-text-size-adjust: 100%; } + #error-description { + fill: #d30001; + } + + #error-id { + fill: #f0eff0; + } + + @media (prefers-color-scheme: dark) { + body { + background: #101010; + color: #e0e0e0; + } + + #error-description { + fill: #FF6161; + } + + #error-id { + fill: #2c2c2c; + } + } + a { color: inherit; font-weight: 700; @@ -83,13 +106,11 @@ } main article br { - display: none; @media(min-width: 48em) { display: inline; } - } @@ -102,7 +123,7 @@
- +

Your browser is not supported.
Please upgrade your browser to continue.

diff --git a/public/422.html b/public/422.html index 8bcf060..f12fb4a 100644 --- a/public/422.html +++ b/public/422.html @@ -35,12 +35,35 @@ font-weight: 400; letter-spacing: -0.0025em; line-height: 1.4; - min-height: 100vh; + min-height: 100dvh; place-items: center; text-rendering: optimizeLegibility; -webkit-text-size-adjust: 100%; } + #error-description { + fill: #d30001; + } + + #error-id { + fill: #f0eff0; + } + + @media (prefers-color-scheme: dark) { + body { + background: #101010; + color: #e0e0e0; + } + + #error-description { + fill: #FF6161; + } + + #error-id { + fill: #2c2c2c; + } + } + a { color: inherit; font-weight: 700; @@ -83,13 +106,11 @@ } main article br { - display: none; @media(min-width: 48em) { display: inline; } - } @@ -102,10 +123,10 @@
- +
-

The change you wanted was rejected. Maybe you tried to change something you didn’t have access to. If you’re the application owner check the logs for more information.

+

The change you wanted was rejected. Maybe you tried to change something you didn't have access to. If you're the application owner check the logs for more information.

diff --git a/public/500.html b/public/500.html index d77718c..e4eb18a 100644 --- a/public/500.html +++ b/public/500.html @@ -4,7 +4,7 @@ - We’re sorry, but something went wrong (500 Internal Server Error) + We're sorry, but something went wrong (500 Internal Server Error) @@ -35,12 +35,35 @@ font-weight: 400; letter-spacing: -0.0025em; line-height: 1.4; - min-height: 100vh; + min-height: 100dvh; place-items: center; text-rendering: optimizeLegibility; -webkit-text-size-adjust: 100%; } + #error-description { + fill: #d30001; + } + + #error-id { + fill: #f0eff0; + } + + @media (prefers-color-scheme: dark) { + body { + background: #101010; + color: #e0e0e0; + } + + #error-description { + fill: #FF6161; + } + + #error-id { + fill: #2c2c2c; + } + } + a { color: inherit; font-weight: 700; @@ -83,13 +106,11 @@ } main article br { - display: none; @media(min-width: 48em) { display: inline; } - } @@ -102,10 +123,10 @@
- +
-

We’re sorry, but something went wrong.
If you’re the application owner check the logs for more information.

+

We're sorry, but something went wrong.
If you're the application owner check the logs for more information.

From b41d3a07aee59019a0bd993b253b7a8fea83cc78 Mon Sep 17 00:00:00 2001 From: Tim Hsueh Date: Wed, 2 Sep 2026 10:30:06 +0800 Subject: [PATCH 3/3] =?UTF-8?q?feat:=20=E4=BB=A5=20dartsass-rails=20?= =?UTF-8?q?=E5=8F=96=E4=BB=A3=20EOL=20=E7=9A=84=20sassc-rails?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit sassc/sassc-rails(libsass 綁定)上游已停止維護,換成官方 Dart Sass 實作。 沿用既有 sprockets 管線:dart-sass 將 site.scss/boss.scss 編進 app/assets/builds/,再由 sprockets 加 digest 輸出,與 tailwind.css 同路徑。 dartsass:build 已自動掛在 assets:precompile 與 test:prepare 之後,CI 無需調整。 實質不相容只有一處:dart-sass 沒有 sprockets 的 asset-url() helper,且不會 報錯,而是把 asset-url("bg.jpg") 當未知 CSS 函式原樣輸出、背景圖靜默消失。 3 處引用改為 CSS custom property(--site-bg-image/--boss-bg-image),由 layout 與 Boss 頁的 yield :head 以 image_path 注入帶 digest 的網址,兼顧 dart-sass 與資產指紋。兩份 scss 其餘語法 dart-sass 完全相容,無需改寫。 連帶處理: - manifest.js 移除 link_directory ../stylesheets .css,避免 sprockets 自行編 scss 而找不到 sassc - 刪除無任何引用、且 require_tree . 會把 scss 拉進 sprockets 的 app/assets/stylesheets/application.css - Procfile.dev 補上 dartsass:watch - 移除 config.assets.css_compressor = nil workaround(libsass 不再參與;實測 production precompile 後 tailwind.css digest 與移除前相同,確認無壓縮器介入) - Gemfile.lock 的 BUNDLED WITH 對齊實際執行的 bundler 2.7.2,清掉 dartsass:build 子行程造成的 Gem::Platform 重複定義警告 - brakeman.ignore 移除 EOLRails 條目(升級後該警告已消失) - README 技術棧、ARCHITECTURE「CSS 編譯器」節、UI_STYLE_GUIDE、REFACTOR_PLAN 同步改寫;RAILS_UPGRADE_RESEARCH 補上實作附錄 驗證:test 222 runs 全綠、test:system 13 runs 全綠、rubocop 0 offenses、 brakeman exit 0 且 0 ignored、production assets:precompile exit 0、 db:drop→db:prepare→db:seed×2 冪等、dev server root 200 + demo 登入 302, 首頁與 Boss 戰畫面實際目視確認背景圖與版面正常。 --- Gemfile | 4 +- Gemfile.lock | 61 ++++++++++++++++-------- Procfile.dev | 1 + README.md | 4 +- app/assets/config/manifest.js | 1 - app/assets/stylesheets/application.css | 15 ------ app/assets/stylesheets/boss.scss | 9 ++-- app/assets/stylesheets/site.scss | 8 +++- app/views/game/bosses/show.html.erb | 4 ++ app/views/layouts/application.html.erb | 4 ++ config/application.rb | 12 ----- config/brakeman.ignore | 22 +-------- config/initializers/dartsass.rb | 13 +++++ docs/ARCHITECTURE.md | 22 +++++++-- docs/RAILS_UPGRADE_RESEARCH.md | 66 ++++++++++++++++++++++++++ docs/REFACTOR_PLAN.md | 2 +- docs/UI_STYLE_GUIDE.md | 4 +- 17 files changed, 167 insertions(+), 85 deletions(-) delete mode 100644 app/assets/stylesheets/application.css create mode 100644 config/initializers/dartsass.rb diff --git a/Gemfile b/Gemfile index b18a3f3..42b37e7 100644 --- a/Gemfile +++ b/Gemfile @@ -42,8 +42,8 @@ gem "bootsnap", require: false # Use Active Storage variants [https://guides.rubyonrails.org/active_storage_overview.html#transforming-images] # gem "image_processing", "~> 1.2" -# Sass 編譯器 -gem "sassc-rails" +# Sass 編譯器(dart-sass 官方實作;sassc/libsass 已於 2024 停止維護) +gem "dartsass-rails" # TailwindCSS via a standalone CLI executable, integrated with the asset # pipeline (no Node/npm required) [https://github.com/rails/tailwindcss-rails] diff --git a/Gemfile.lock b/Gemfile.lock index e32c51b..f5635a6 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -100,6 +100,9 @@ GEM concurrent-ruby (1.3.8) connection_pool (3.0.2) crass (1.0.7) + dartsass-rails (0.5.1) + railties (>= 6.0.0) + sass-embedded (~> 1.63) date (3.5.1) debug (1.11.0) irb (~> 1.10) @@ -107,16 +110,29 @@ GEM drb (2.2.3) erb (6.0.7) erubi (1.13.1) - ffi (1.17.2-aarch64-linux-gnu) - ffi (1.17.2-aarch64-linux-musl) - ffi (1.17.2-arm-linux-gnu) - ffi (1.17.2-arm-linux-musl) - ffi (1.17.2-arm64-darwin) - ffi (1.17.2-x86_64-darwin) - ffi (1.17.2-x86_64-linux-gnu) - ffi (1.17.2-x86_64-linux-musl) globalid (1.4.0) activesupport (>= 6.1) + google-protobuf (4.36.1) + bigdecimal + rake (~> 13.3) + google-protobuf (4.36.1-aarch64-linux-gnu) + bigdecimal + rake (~> 13.3) + google-protobuf (4.36.1-aarch64-linux-musl) + bigdecimal + rake (~> 13.3) + google-protobuf (4.36.1-arm64-darwin) + bigdecimal + rake (~> 13.3) + google-protobuf (4.36.1-x86_64-darwin) + bigdecimal + rake (~> 13.3) + google-protobuf (4.36.1-x86_64-linux-gnu) + bigdecimal + rake (~> 13.3) + google-protobuf (4.36.1-x86_64-linux-musl) + bigdecimal + rake (~> 13.3) i18n (1.15.2) concurrent-ruby (~> 1.0) importmap-rails (2.1.0) @@ -280,14 +296,22 @@ GEM rubocop-rails (>= 2.30) ruby-progressbar (1.13.0) rubyzip (2.4.1) - 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 + sass-embedded (1.103.1-aarch64-linux-gnu) + google-protobuf (~> 4.31) + sass-embedded (1.103.1-aarch64-linux-musl) + google-protobuf (~> 4.31) + sass-embedded (1.103.1-arm-linux-gnueabihf) + google-protobuf (~> 4.31) + sass-embedded (1.103.1-arm-linux-musleabihf) + google-protobuf (~> 4.31) + sass-embedded (1.103.1-arm64-darwin) + google-protobuf (~> 4.31) + sass-embedded (1.103.1-x86_64-darwin) + google-protobuf (~> 4.31) + sass-embedded (1.103.1-x86_64-linux-gnu) + google-protobuf (~> 4.31) + sass-embedded (1.103.1-x86_64-linux-musl) + google-protobuf (~> 4.31) securerandom (0.4.1) selenium-webdriver (4.34.0) base64 (~> 0.2) @@ -316,7 +340,6 @@ GEM tailwindcss-ruby (4.3.3-x86_64-linux-gnu) tailwindcss-ruby (4.3.3-x86_64-linux-musl) thor (1.5.0) - tilt (2.6.1) timeout (0.6.1) tsort (0.2.0) turbo-rails (2.0.16) @@ -360,6 +383,7 @@ DEPENDENCIES bootsnap brakeman capybara + dartsass-rails debug importmap-rails jbuilder @@ -369,7 +393,6 @@ DEPENDENCIES rails (~> 8.1.3, >= 8.1.3.1) rqrcode rubocop-rails-omakase - sassc-rails selenium-webdriver sprockets-rails stimulus-rails @@ -379,4 +402,4 @@ DEPENDENCIES web-console BUNDLED WITH - 2.5.16 + 2.7.2 diff --git a/Procfile.dev b/Procfile.dev index da151fe..2007e54 100644 --- a/Procfile.dev +++ b/Procfile.dev @@ -1,2 +1,3 @@ web: bin/rails server css: bin/rails tailwindcss:watch +sass: bin/rails dartsass:watch diff --git a/README.md b/README.md index ab57f25..e4e8cfa 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ [▶ Live Demo](https://venture-ferris.curihaosity.xyz) 一個把 2018 年台北市商業處「勇闖摩天輪」團隊實境解謎活動網站,從 2018 年老舊 -的 CodeIgniter 3 專案重構為 Rails 7.2 的技術作品集專案。 +的 CodeIgniter 3 專案重構為 Rails 8.1 的技術作品集專案。 ![遊戲流程展示](docs/images/demo.gif) @@ -36,7 +36,7 @@ 完整論述、架構總覽(Models/ERD/路由)、相較原作的新增功能全文,見 [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md)。 -**技術棧**:Rails 7.2/PostgreSQL/Hotwire(Turbo + Stimulus)/Tailwind CSS v4/部署 Fly.io(東京)+ Cloudflare。 +**技術棧**:Rails 8.1/PostgreSQL/Hotwire(Turbo + Stimulus)/Tailwind CSS v4/Dart Sass/部署 Fly.io(東京)+ Cloudflare。 ## 本機啟動 diff --git a/app/assets/config/manifest.js b/app/assets/config/manifest.js index b06fc42..4028c22 100644 --- a/app/assets/config/manifest.js +++ b/app/assets/config/manifest.js @@ -1,5 +1,4 @@ //= link_tree ../images -//= link_directory ../stylesheets .css //= link_tree ../../javascript .js //= link_tree ../../../vendor/javascript .js //= link_tree ../builds diff --git a/app/assets/stylesheets/application.css b/app/assets/stylesheets/application.css deleted file mode 100644 index 288b9ab..0000000 --- a/app/assets/stylesheets/application.css +++ /dev/null @@ -1,15 +0,0 @@ -/* - * This is a manifest file that'll be compiled into application.css, which will include all the files - * listed below. - * - * Any CSS (and SCSS, if configured) file within this directory, lib/assets/stylesheets, or any plugin's - * vendor/assets/stylesheets directory can be referenced here using a relative path. - * - * You're free to add application-wide styles to this file and they'll appear at the bottom of the - * compiled file so the styles you add here take precedence over styles defined in any other CSS - * files in this directory. Styles in this file should be added after the last require_* statement. - * It is generally better to create a new file per style scope. - * - *= require_tree . - *= require_self - */ diff --git a/app/assets/stylesheets/boss.scss b/app/assets/stylesheets/boss.scss index 37b5495..58c5598 100644 --- a/app/assets/stylesheets/boss.scss +++ b/app/assets/stylesheets/boss.scss @@ -3,8 +3,11 @@ *{ font-family: 'Noto Sans TC'; } /*----- 基本設定 -----*/ -body { - background-image: asset-url("bossBg.png"); +body { + /* dart-sass has no sprockets `asset-url()` helper, so the fingerprinted + URL is injected from the boss page's `yield :head` block as a custom + property. See app/views/game/bosses/show.html.erb. */ + background-image: var(--boss-bg-image); background-repeat: no-repeat; background-attachment: fixed; background-position: center top; @@ -50,7 +53,7 @@ p.text-shadow{ color: white; text-shadow: 1px 0 5px black, 0 1px 5px black; } isolation: isolate; width: 100%; aspect-ratio: 509 / 720; - background-image: asset-url("bossBg.png"); + background-image: var(--boss-bg-image); background-repeat: no-repeat; background-position: center; background-size: cover; diff --git a/app/assets/stylesheets/site.scss b/app/assets/stylesheets/site.scss index 07faff2..aebe141 100644 --- a/app/assets/stylesheets/site.scss +++ b/app/assets/stylesheets/site.scss @@ -3,8 +3,12 @@ *{ font-family: 'Noto Sans TC'; } /*----- 基本設定 -----*/ -body { - background-image: asset-url("bg.jpg"); +body { + /* dart-sass has no sprockets `asset-url()` helper (it would pass the + function through verbatim and the browser could not resolve it), so the + fingerprinted URL is injected from the layout as a custom property. + See app/views/layouts/application.html.erb. */ + background-image: var(--site-bg-image); background-repeat: repeat; background-attachment: fixed; background-position: center top; diff --git a/app/views/game/bosses/show.html.erb b/app/views/game/bosses/show.html.erb index c04d951..cf94a88 100644 --- a/app/views/game/bosses/show.html.erb +++ b/app/views/game/bosses/show.html.erb @@ -8,6 +8,10 @@ stylesheet without editing app/views/layouts/. %> <% content_for :head do %> <%= stylesheet_link_tag "boss" %> + <%# boss.scss is compiled by dart-sass, which has no sprockets `asset-url()` + helper, so the fingerprinted background URL is handed to it here as a + custom property (see app/assets/stylesheets/boss.scss). %> + <% end %>
diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index 6681b32..2b2c946 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -14,6 +14,10 @@ <%= stylesheet_link_tag "tailwind", "data-turbo-track": "reload" %> <%= stylesheet_link_tag "site" %> + <%# site.scss is compiled by dart-sass, which has no sprockets + `asset-url()` helper, so the fingerprinted background URL is handed to + it here as a custom property (see app/assets/stylesheets/site.scss). %> + <%= javascript_importmap_tags %> <%= yield :head %> diff --git a/config/application.rb b/config/application.rb index bf7f89c..943c27d 100644 --- a/config/application.rb +++ b/config/application.rb @@ -26,17 +26,5 @@ class Application < Rails::Application # ActiveRecord still stores UTC as usual. config.time_zone = "Taipei" # config.eager_load_paths << Rails.root.join("extras") - - # sassc-rails defaults `config.assets.css_compressor` to `:sass` outside - # development (see sassc/rails/railtie.rb#setup_compression), which - # routes every compiled CSS asset — including the pre-built, already - # minified `tailwind.css` from tailwindcss-rails — back through the - # libsass compressor. Tailwind v4's output uses modern CSS syntax - # (e.g. `@media (width >= 40rem)` range syntax) that libsass cannot - # parse, raising "unclosed parenthesis in media query expression" in - # test/production. Disable the compressor so built assets are served - # as-is; sassc-rails' own .scss compilation (site.scss/boss.scss) is - # unaffected since that happens at the engine/transformer step, not here. - config.assets.css_compressor = nil end end diff --git a/config/brakeman.ignore b/config/brakeman.ignore index 220996d..9be5989 100644 --- a/config/brakeman.ignore +++ b/config/brakeman.ignore @@ -1,24 +1,4 @@ { - "ignored_warnings": [ - { - "warning_type": "Unmaintained Dependency", - "warning_code": 120, - "fingerprint": "d84924377155b41e094acae7404ec2e521629d86f97b0ff628e3d1b263f8101c", - "check_name": "EOLRails", - "message": "Support for Rails 7.2.2.1 ended on 2026-08-09", - "file": "Gemfile.lock", - "line": 199, - "link": "https://brakemanscanner.org/docs/warning_types/unmaintained_dependency/", - "code": null, - "render_path": null, - "location": null, - "user_input": null, - "confidence": "High", - "cwe_id": [ - 1104 - ], - "note": "Rails 7.2.2.1 EOL is a known, accepted gap for this portfolio rebuild (see README.md's \"CSS 編譯器\" section for the same kind of documented-but-out-of-scope upstream EOL). Bumping the Rails minor/major version is out of scope for this batch of fixes; tracked as a follow-up, not something to silently hide by loosening the Gemfile lock." - } - ], + "ignored_warnings": [], "brakeman_version": "7.0.2" } diff --git a/config/initializers/dartsass.rb b/config/initializers/dartsass.rb new file mode 100644 index 0000000..c89bafa --- /dev/null +++ b/config/initializers/dartsass.rb @@ -0,0 +1,13 @@ +# dartsass-rails compiles each entry point in app/assets/stylesheets/ into +# app/assets/builds/, which sprockets then fingerprints and serves (see +# app/assets/config/manifest.js's `link_tree ../builds`). `dartsass:build` is +# attached to `assets:precompile`, so production builds pick this up +# automatically; `bin/dev` runs `dartsass:watch` alongside the server. +# +# These two entry points are linked directly from views rather than being +# bundled together: "site" from the shared layout, "boss" only from the boss +# battle page (app/views/game/bosses/show.html.erb). +Rails.application.config.dartsass.builds = { + "site.scss" => "site.css", + "boss.scss" => "boss.css" +} diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index cff2fd7..d9d933f 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -175,11 +175,23 @@ Bootstrap 4.1.3(含 Font Awesome)全套 CDN,終點是: `app/assets/tailwind/application.css`(`@import "tailwindcss"`)經 `tailwindcss-rails` 編譯出 `app/assets/builds/tailwind.css`,負責全站絕大部分 -樣式。`sassc-rails`(`libsass` 綁定)現在只剩下編譯上一節提到的兩份少量自訂 -CSS(`site.scss`/`boss.scss`)——拼圖/Boss 疊圖等 Tailwind utility 表達不了 -或不划算表達的功能性定位樣式。`sassc`/`sassc-rails` 上游已宣告 EOL、不再 -維護;**未來待辦**:換成 `dartsass-rails`(官方目前建議的替代方案),屆時 -只需要把兩個 `.scss` 檔案原樣搬過去、調整 Gemfile,不影響任何其他程式碼。 +樣式。`dartsass-rails`(Dart Sass 官方實作)現在只剩下編譯上一節提到的兩份 +少量自訂 CSS(`site.scss`/`boss.scss`)——拼圖/Boss 疊圖等 Tailwind utility +表達不了或不划算表達的功能性定位樣式。原本使用的 `sassc`/`sassc-rails` +(`libsass` 綁定)上游已宣告 EOL,已於 Rails 8 升級時一併換掉。 + +編譯路徑:`config/initializers/dartsass.rb` 定義兩個 entry point,dart-sass +把它們編進 `app/assets/builds/`,再由 sprockets 加上 digest 後輸出(與 +`tailwind.css` 同一條路徑,見 `app/assets/config/manifest.js` 的 +`link_tree ../builds`)。`dartsass:build` 已自動掛在 `assets:precompile` 與 +`test:prepare` 之後,`bin/dev`(`Procfile.dev`)則會另起 `dartsass:watch`。 + +**一個換裝時的取捨要知道**:dart-sass 沒有 sprockets 的 `asset-url()` helper +(會被當成未知的 CSS 函式原樣輸出,瀏覽器解不開)。兩份 scss 原本共 3 處 +`asset-url()` 引用 `bg.jpg`/`bossBg.png`,改成讀 CSS custom property +(`--site-bg-image`/`--boss-bg-image`),由 layout 與 Boss 頁的 `yield :head` +用 `image_path` 注入帶 digest 的網址——這樣才能同時保留 dart-sass 與資產 +指紋。新增背景圖引用時請沿用這個作法,不要寫死路徑。 ## 相較原作的新增功能 diff --git a/docs/RAILS_UPGRADE_RESEARCH.md b/docs/RAILS_UPGRADE_RESEARCH.md index 2fb00f2..96962d1 100644 --- a/docs/RAILS_UPGRADE_RESEARCH.md +++ b/docs/RAILS_UPGRADE_RESEARCH.md @@ -36,3 +36,69 @@ 2. **B2:8.0 → 8.1.3.1**(同流程+接受 schema 排序 diff) 3. **B3(可選但建議):sassc-rails → dartsass-rails**(獨立 commit,失敗可退) 4. 全程一個 PR、三個 commit;verifier 總驗收(含 production boot、assets:precompile、demo 全鏈 curl)後合併——自動部署上線。 + +--- + +## 實作附錄(2026-09-02 完成) + +升級已完成並落地為三個 commit,全程未修改 M0 計分黃金值測試 +(`test/integration/game/scoring_golden_test.rb`,MD5 前後皆為 +`b5f9bf50204b0b7f74d063c26734d602`)。 + +| 批次 | commit | 內容 | +|---|---|---| +| B1 | `chore: 升級 Rails 7.2.2.1 至 8.0.5.1` | Gemfile、app:update 逐檔取捨、`load_defaults 8.0` | +| B2 | `chore: 升級 Rails 8.0.5.1 至 8.1.3.1` | 同流程、`load_defaults 8.1`、schema 字母排序重排 | +| B3 | `feat: 以 dartsass-rails 取代 EOL 的 sassc-rails` | CSS 編譯器換裝、移除 css_compressor workaround | + +### app:update 的通則:範本會覆蓋自訂設定,每一批都要重做一次 + +`app:update` 在 B1 與 B2 都把下列自訂設定整段刪掉或註解掉,兩批都必須手動復原 +(B2 甚至把 `assume_ssl`/`force_ssl` 兩行一起註解掉——若沒發現,production +會退回無 TLS 假設的狀態): + +- `config/application.rb`:Taipei 時區 +- `config/environments/production.rb`:`assume_ssl`/`force_ssl`、 + `ALLOWED_HOSTS` 與 `/up` 的 host authorization 豁免、`assets.compile = false` +- `config/environments/development.rb`:sprockets 的 + `assets.quiet`/`assets.debug`/`assets.compile` +- `bin/brakeman`(範本會把移除過的 `--ensure-latest` 加回來)、 + `bin/dev`(範本改成直接 exec rails server,會廢掉 Procfile.dev) + +**Rails 8 範本假設 Propshaft**,所以凡是 sprockets 專屬的設定(上面 +`assets.*` 那幾項)範本都不會有,升級時要自己補回去,不能當成「範本刪掉了 +表示不需要」。另外每批 `app:update` 都會重新複製三個 ActiveStorage +migration,本專案沒有相關資料表,兩批都需刪除。 + +### 8.1 的 schema.rb 重排 + +除了預期的欄位字母排序外,實際還有兩處差異(已逐行比對確認無欄位增刪): +`ActiveRecord::Schema[7.2]` → `[8.1]`,以及 `enable_extension "plpgsql"` → +`"pg_catalog.plpgsql"`(8.1 改為輸出 schema 限定的擴充名稱)。 + +### dartsass 換裝:唯一的實質不相容是 `asset-url()` + +兩份 scss 的語法 dart-sass 完全吃得下,沒有任何語法錯誤。真正的坑是 +**dart-sass 沒有 sprockets 的 `asset-url()` helper**——它不會報錯,而是把 +`asset-url("bg.jpg")` 當成未知的 CSS 函式原樣輸出,瀏覽器解不開,背景圖靜默 +消失。3 處引用改為 CSS custom property(`--site-bg-image`/`--boss-bg-image`), +由 layout 與 Boss 頁 `yield :head` 用 `image_path` 注入帶 digest 的網址, +兼顧 dart-sass 與資產指紋。 + +連帶處理:`app/assets/config/manifest.js` 移除 `link_directory ../stylesheets .css` +(否則 sprockets 會試圖自己編 scss 而找不到 sassc);刪除沒有任何地方引用、 +且 `require_tree .` 會把 scss 拉進 sprockets 的 `app/assets/stylesheets/application.css`; +`Procfile.dev` 補上 `dartsass:watch`。 + +`config.assets.css_compressor = nil` 的 workaround 已移除並實測:libsass 不再 +參與,production precompile 後 `tailwind.css` 的 digest 與移除前完全相同, +確認沒有壓縮器介入。 + +### 一個與 Rails 無關、但會污染輸出的坑 + +`dartsass:build` 會 `system()` 出一個新的 Ruby 行程,於是 `Gemfile.lock` 的 +`BUNDLED WITH`(原為 2.5.16)與實際執行的 bundler(2.7.2)版本不一致就會在 +每次測試輸出裡噴 8 對 `already initialized constant Gem::Platform::*` 警告。 +把 `BUNDLED WITH` 對齊為 2.7.2 即可清零。注意 `bundle update --bundler` 會直接 +跳到當時最新的 bundler 4.0.19(major 版本,Dockerfile 有 `BUNDLE_DEPLOYMENT=1`, +風險高),不要照單全收,手動寫回實際在跑的版本比較安全。 diff --git a/docs/REFACTOR_PLAN.md b/docs/REFACTOR_PLAN.md index b682de4..7aa4fb6 100644 --- a/docs/REFACTOR_PLAN.md +++ b/docs/REFACTOR_PLAN.md @@ -82,7 +82,7 @@ seeds 明訂中文顯示名(阿北/鄉民/鞋姊/罔美)↔ enum 對映。 - **jQuery 全站保留,走 classic script(不走 importmap ESM**,minified plugin 非 ESM 且 Bootstrap 4 modal 硬依賴 jQuery):jQuery 3.3.1 / jQuery UI 1.12.1 / Bootstrap 4.1.3 **沿用舊站的 CDN `