trek 2.5.0 → 2.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (170) hide show
  1. checksums.yaml +4 -4
  2. data/.node-version +1 -1
  3. data/AGENTS.md +7 -4
  4. data/CHANGELOG.md +37 -0
  5. data/Gemfile +5 -0
  6. data/Gemfile.lock +16 -3
  7. data/Makefile +2 -0
  8. data/app/components/trek/dashboard/tile_component/tile_component.css +9 -0
  9. data/app/components/trek/dashboard/tile_component/tile_component.html.slim +4 -2
  10. data/app/components/trek/dashboard/tile_component.rb +12 -2
  11. data/app/components/trek/form/content_editor_component/content_editor_component.css +5 -0
  12. data/app/components/trek/form/content_editor_component/content_editor_component.html.slim +19 -93
  13. data/app/components/trek/form/content_editor_component/content_editor_component.js +94 -251
  14. data/app/components/trek/form/content_editor_component/extensions/block_view.js +40 -0
  15. data/app/components/trek/form/content_editor_component/extensions/image_block.js +28 -26
  16. data/app/components/trek/form/content_editor_component/extensions/prompts_block.js +15 -26
  17. data/app/components/trek/form/content_editor_component/extensions/table_of_contents.js +1 -3
  18. data/app/components/trek/form/content_editor_component.rb +15 -0
  19. data/app/components/trek/form/image_field_component/image_field_component.css +40 -1
  20. data/app/components/trek/form/image_field_component/image_field_component.html.slim +0 -9
  21. data/app/components/trek/form/image_field_component.rb +36 -9
  22. data/app/content/trek/content/cleaner.rb +53 -0
  23. data/app/content/trek/content/document.rb +2 -2
  24. data/app/content/trek/content/prompts_block.rb +1 -2
  25. data/app/content/trek/content.rb +6 -0
  26. data/app/controllers/concerns/trek/reorderable.rb +24 -6
  27. data/app/controllers/trek/csp_reports_controller.rb +14 -1
  28. data/app/controllers/trek/panels/prompts_controller.rb +1 -1
  29. data/app/controllers/trek/resource_controller.rb +5 -5
  30. data/app/jobs/trek/purge_expired_sessions_job.rb +4 -3
  31. data/app/models/concerns/trek/contentable.rb +15 -1
  32. data/app/models/concerns/trek/users/sign_in_code.rb +70 -0
  33. data/app/models/concerns/trek/users/sign_in_lockout.rb +4 -2
  34. data/app/views/trek/panels/links/_form.html.slim +2 -4
  35. data/app/views/trek/panels/prompts/create.turbo_stream.slim +0 -1
  36. data/config/locales/trek.en.yml +26 -11
  37. data/config/locales/trek.fr.yml +26 -11
  38. data/docs/.vitepress/config.mjs +4 -1
  39. data/docs/background-jobs.md +69 -0
  40. data/docs/content-editor.md +219 -0
  41. data/docs/customize.md +2 -0
  42. data/docs/deploy.md +2 -2
  43. data/docs/getting-started.md +1 -1
  44. data/docs/index.md +1 -1
  45. data/docs/reference/components.md +2 -2
  46. data/docs/reference/concerns/contentable.md +10 -4
  47. data/docs/reference/concerns/formattable.md +1 -1
  48. data/docs/reference/environment-variables.md +1 -0
  49. data/docs/reference/formatters.md +2 -30
  50. data/docs/reference/forms.md +1 -1
  51. data/docs/reference/generators/audit-log.md +2 -2
  52. data/docs/reference/generators/install.md +3 -2
  53. data/docs/reference/generators/public-accounts.md +141 -0
  54. data/docs/reference/generators/security-headers.md +1 -1
  55. data/docs/reference/index.md +2 -1
  56. data/docs/reference/models/user.md +3 -1
  57. data/docs/what-is-trek.md +1 -1
  58. data/lib/generators/trek/audit_log_generator.rb +54 -0
  59. data/lib/generators/trek/install/admin/users_generator.rb +6 -1
  60. data/lib/generators/trek/install/audit_log_generator.rb +9 -45
  61. data/lib/generators/trek/install/good_job_generator.rb +161 -0
  62. data/lib/generators/trek/install/model_specs_generator.rb +3 -3
  63. data/lib/generators/trek/install/ruby_generator.rb +1 -1
  64. data/lib/generators/trek/install/scalingo_generator.rb +7 -45
  65. data/lib/generators/trek/install_generator.rb +15 -0
  66. data/lib/generators/trek/public_accounts_generator.rb +267 -0
  67. data/lib/generators/trek/scalingo_generator.rb +55 -0
  68. data/lib/generators/trek/templates/.gitlab-ci.yml +1 -1
  69. data/lib/generators/trek/templates/controllers/admin/password_renewals_controller.rb +11 -0
  70. data/lib/generators/trek/templates/controllers/admin/user_sessions_controller.rb +1 -1
  71. data/lib/generators/trek/templates/controllers/admin/users_controller.rb +0 -8
  72. data/lib/generators/trek/templates/initializers/content_security_policy.rb +3 -2
  73. data/lib/generators/trek/templates/initializers/good_job.rb.tt +38 -0
  74. data/lib/generators/trek/templates/jobs/mail_delivery_job.rb +6 -0
  75. data/lib/generators/trek/templates/layouts/admin.html.slim +5 -0
  76. data/lib/generators/trek/templates/mailers/admin_mailer.rb +4 -2
  77. data/lib/generators/trek/templates/package.json.tt +1 -1
  78. data/lib/generators/trek/templates/public_accounts/api/authentication.rb.tt +76 -0
  79. data/lib/generators/trek/templates/public_accounts/api/controllers/accounts_controller.rb.tt +22 -0
  80. data/lib/generators/trek/templates/public_accounts/api/controllers/active_sessions_controller.rb.tt +21 -0
  81. data/lib/generators/trek/templates/public_accounts/api/controllers/base_controller.rb.tt +71 -0
  82. data/lib/generators/trek/templates/public_accounts/api/controllers/email_changes_controller.rb.tt +55 -0
  83. data/lib/generators/trek/templates/public_accounts/api/controllers/email_confirmations_controller.rb.tt +19 -0
  84. data/lib/generators/trek/templates/public_accounts/api/controllers/password_resets_controller.rb.tt +28 -0
  85. data/lib/generators/trek/templates/public_accounts/api/controllers/passwords_controller.rb.tt +17 -0
  86. data/lib/generators/trek/templates/public_accounts/api/controllers/registrations_controller.rb.tt +17 -0
  87. data/lib/generators/trek/templates/public_accounts/api/controllers/sessions_controller.rb.tt +45 -0
  88. data/lib/generators/trek/templates/public_accounts/api/controllers/sign_in_codes_controller.rb.tt +15 -0
  89. data/lib/generators/trek/templates/public_accounts/jobs/sign_in_code_job.rb.tt +13 -0
  90. data/lib/generators/trek/templates/public_accounts/locales/accounts.en.yml.tt +205 -0
  91. data/lib/generators/trek/templates/public_accounts/locales/accounts.fr.yml.tt +205 -0
  92. data/lib/generators/trek/templates/public_accounts/mailers/account_mailer.rb.tt +85 -0
  93. data/lib/generators/trek/templates/public_accounts/migrations/create_accounts.rb.tt +46 -0
  94. data/lib/generators/trek/templates/public_accounts/models/account/session.rb.tt +47 -0
  95. data/lib/generators/trek/templates/public_accounts/models/account/sign_in_code.rb.tt +8 -0
  96. data/lib/generators/trek/templates/public_accounts/models/account.rb.tt +73 -0
  97. data/lib/generators/trek/templates/public_accounts/spec/api/accounts_spec.rb.tt +90 -0
  98. data/lib/generators/trek/templates/public_accounts/spec/api/sign_ins_spec.rb.tt +158 -0
  99. data/lib/generators/trek/templates/public_accounts/spec/models/account/sign_in_code_spec.rb.tt +56 -0
  100. data/lib/generators/trek/templates/public_accounts/spec/models/account_spec.rb.tt +109 -0
  101. data/lib/generators/trek/templates/public_accounts/spec/web/accounts_spec.rb.tt +126 -0
  102. data/lib/generators/trek/templates/public_accounts/spec/web/authentication_spec.rb.tt +43 -0
  103. data/lib/generators/trek/templates/public_accounts/spec/web/sign_ins_spec.rb.tt +215 -0
  104. data/lib/generators/trek/templates/public_accounts/views/mailer/email_change.en.html.slim.tt +10 -0
  105. data/lib/generators/trek/templates/public_accounts/views/mailer/email_change.en.text.erb.tt +8 -0
  106. data/lib/generators/trek/templates/public_accounts/views/mailer/email_change.fr.html.slim.tt +13 -0
  107. data/lib/generators/trek/templates/public_accounts/views/mailer/email_change.fr.text.erb.tt +8 -0
  108. data/lib/generators/trek/templates/public_accounts/views/mailer/email_change_code.en.html.slim.tt +9 -0
  109. data/lib/generators/trek/templates/public_accounts/views/mailer/email_change_code.en.text.erb.tt +11 -0
  110. data/lib/generators/trek/templates/public_accounts/views/mailer/email_change_code.fr.html.slim.tt +11 -0
  111. data/lib/generators/trek/templates/public_accounts/views/mailer/email_change_code.fr.text.erb.tt +11 -0
  112. data/lib/generators/trek/templates/public_accounts/views/mailer/email_confirmation.en.html.slim.tt +10 -0
  113. data/lib/generators/trek/templates/public_accounts/views/mailer/email_confirmation.en.text.erb.tt +8 -0
  114. data/lib/generators/trek/templates/public_accounts/views/mailer/email_confirmation.fr.html.slim.tt +10 -0
  115. data/lib/generators/trek/templates/public_accounts/views/mailer/email_confirmation.fr.text.erb.tt +8 -0
  116. data/lib/generators/trek/templates/public_accounts/views/mailer/password_reset.en.html.slim.tt +10 -0
  117. data/lib/generators/trek/templates/public_accounts/views/mailer/password_reset.en.text.erb.tt +8 -0
  118. data/lib/generators/trek/templates/public_accounts/views/mailer/password_reset.fr.html.slim.tt +13 -0
  119. data/lib/generators/trek/templates/public_accounts/views/mailer/password_reset.fr.text.erb.tt +8 -0
  120. data/lib/generators/trek/templates/public_accounts/views/mailer/sign_in_code.en.html.slim.tt +9 -0
  121. data/lib/generators/trek/templates/public_accounts/views/mailer/sign_in_code.en.text.erb.tt +11 -0
  122. data/lib/generators/trek/templates/public_accounts/views/mailer/sign_in_code.fr.html.slim.tt +9 -0
  123. data/lib/generators/trek/templates/public_accounts/views/mailer/sign_in_code.fr.text.erb.tt +11 -0
  124. data/lib/generators/trek/templates/public_accounts/views/mailer/sign_in_locked.en.html.slim.tt +23 -0
  125. data/lib/generators/trek/templates/public_accounts/views/mailer/sign_in_locked.en.text.erb.tt +14 -0
  126. data/lib/generators/trek/templates/public_accounts/views/mailer/sign_in_locked.fr.html.slim.tt +24 -0
  127. data/lib/generators/trek/templates/public_accounts/views/mailer/sign_in_locked.fr.text.erb.tt +14 -0
  128. data/lib/generators/trek/templates/public_accounts/web/authentication.rb.tt +97 -0
  129. data/lib/generators/trek/templates/public_accounts/web/controllers/accounts_controller.rb.tt +33 -0
  130. data/lib/generators/trek/templates/public_accounts/web/controllers/active_sessions_controller.rb.tt +15 -0
  131. data/lib/generators/trek/templates/public_accounts/web/controllers/base_controller.rb.tt +52 -0
  132. data/lib/generators/trek/templates/public_accounts/web/controllers/email_changes_controller.rb.tt +85 -0
  133. data/lib/generators/trek/templates/public_accounts/web/controllers/email_confirmations_controller.rb.tt +19 -0
  134. data/lib/generators/trek/templates/public_accounts/web/controllers/password_resets_controller.rb.tt +41 -0
  135. data/lib/generators/trek/templates/public_accounts/web/controllers/passwords_controller.rb.tt +23 -0
  136. data/lib/generators/trek/templates/public_accounts/web/controllers/registrations_controller.rb.tt +24 -0
  137. data/lib/generators/trek/templates/public_accounts/web/controllers/sessions_controller.rb.tt +78 -0
  138. data/lib/generators/trek/templates/public_accounts/web/controllers/sign_in_codes_controller.rb.tt +27 -0
  139. data/lib/generators/trek/templates/public_accounts/web/views/_flash.html.slim.tt +5 -0
  140. data/lib/generators/trek/templates/public_accounts/web/views/accounts/show.html.slim.tt +52 -0
  141. data/lib/generators/trek/templates/public_accounts/web/views/email_changes/edit.html.slim.tt +17 -0
  142. data/lib/generators/trek/templates/public_accounts/web/views/email_changes/new.html.slim.tt +15 -0
  143. data/lib/generators/trek/templates/public_accounts/web/views/password_resets/edit.html.slim.tt +26 -0
  144. data/lib/generators/trek/templates/public_accounts/web/views/password_resets/new.html.slim.tt +15 -0
  145. data/lib/generators/trek/templates/public_accounts/web/views/passwords/edit.html.slim.tt +25 -0
  146. data/lib/generators/trek/templates/public_accounts/web/views/registrations/new.html.slim.tt +28 -0
  147. data/lib/generators/trek/templates/public_accounts/web/views/sessions/new.html.slim.tt +38 -0
  148. data/lib/generators/trek/templates/public_accounts/web/views/sign_in_codes/new.html.slim.tt +17 -0
  149. data/lib/generators/trek/templates/spec/jobs/mail_delivery_job_spec.rb +11 -0
  150. data/lib/generators/trek/templates/spec/mailers/admin_mailer_spec.rb +10 -0
  151. data/lib/generators/trek/templates/spec/requests/admin/fragments_spec.rb +1 -1
  152. data/lib/generators/trek/templates/spec/requests/admin/good_job_spec.rb +34 -0
  153. data/lib/generators/trek/templates/spec/requests/admin/pages_spec.rb +1 -1
  154. data/lib/generators/trek/templates/spec/requests/admin/panels_spec.rb +16 -0
  155. data/lib/generators/trek/templates/spec/requests/admin/password_renewal_spec.rb +2 -2
  156. data/lib/generators/trek/templates/spec/requests/admin/sign_in_lockout_spec.rb +1 -1
  157. data/lib/generators/trek/templates/spec/requests/admin/user_sessions_spec.rb +2 -2
  158. data/lib/generators/trek/templates/views/admin/users/index.html.slim +1 -1
  159. data/lib/generators/trek/templates/views/admin_mailer/new_sign_in.en.html.slim +1 -1
  160. data/lib/generators/trek/templates/views/admin_mailer/new_sign_in.en.text.erb +1 -1
  161. data/lib/generators/trek/templates/views/admin_mailer/new_sign_in.fr.html.slim +1 -1
  162. data/lib/generators/trek/templates/views/admin_mailer/new_sign_in.fr.text.erb +1 -1
  163. data/lib/trek/csp_reports.rb +22 -4
  164. data/lib/trek/typography_formatter.rb +35 -4
  165. data/lib/trek/version.rb +1 -1
  166. data/package.json +12 -8
  167. data/trek.gemspec +1 -1
  168. data/vitest.config.mjs +24 -0
  169. data/yarn.lock +1004 -288
  170. metadata +90 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 83354e52923bd77162639c74f5e45fcdfbeed812c4b2cbbad7c6b28c987ef8e0
4
- data.tar.gz: 6a9d8038dbc513ecf9e7fdc119f874532e0c422e9aeb9fd6bb158d335542e839
3
+ metadata.gz: d783f048ea7c8129d6b101a8ccb10d0c33234e0d24ef08a57dc33499c88b09e4
4
+ data.tar.gz: 750d8fbdb051715fc9f68dc2891592f36adb79d85c2f890eabeecc142ea15e0b
5
5
  SHA512:
6
- metadata.gz: c9ad87bb1b169ede12d9efce481f385aa2aaf10a76381ccb652ff048846ce7924a0d1db97eb358b0c5e968960d658683b5b4449d4304401cd387e1d9c635c257
7
- data.tar.gz: 4cadf24ff3c2ff45fd419ea97100add550d6e0d6431913b2b07fb94f597eaffb861174648c8f23803056fb87b5ed09ff4426add34aba2be92b6ae772884e0264
6
+ metadata.gz: 3a7a0c78650fc92150bcf59a4923bc192c77759ac027ad30dd8ab3063f3097c83d1b12d397908bddb9e66ff2c3a123a5f4269333026756acd2e1bf15f7990abf
7
+ data.tar.gz: 010e0d3c431c219b0749ab01b98dbf224f4a63fc5d256fd62b7f7503f9a0def8140248eba61917ef5afed1ddfc86472d0ff49eb4918dc95e4edd3893a28f6c5a
data/.node-version CHANGED
@@ -1 +1 @@
1
- 24.22.0
1
+ 24.22.1
data/AGENTS.md CHANGED
@@ -9,10 +9,10 @@ Trek is a customizable CMS for Ruby on Rails, shipped as a **Ruby gem** (`trek`)
9
9
  ## Tech stack
10
10
 
11
11
  - **Ruby** `>= 3.3` (RuboCop targets 3.4) · **Rails** `>= 8.0, < 8.2` (lockfile 8.1.4)
12
- - **Node** 24.22.0 (`.node-version`) · **Yarn** 4.18.1 with its defaults: no install scripts, no package published less than a day ago · **PostgreSQL** 12+ · **libvips** for images
12
+ - **Node** 24.22.1 (`.node-version`) · **Yarn** 4.18.1 with its defaults: no install scripts, no package published less than a day ago · **PostgreSQL** 12+ · **libvips** for images
13
13
  - Backend: `action_policy`, `closure_tree`, `acts_as_list`, `mobility` + `i18n-tasks`, `shrine` (+ blurhash/image_processing/aws-sdk-s3), `view_component` + `slim-rails` + `lookbook`, `kaminari`, `deepl-rb`
14
14
  - Frontend: **esbuild** (`esbuild.config.js`) + **PostCSS** (`postcss.config.js`); Stimulus/Turbo, TipTap, Uppy, Tom Select, SortableJS
15
- - Tests: **RSpec** (`rspec-rails`, capybara, simplecov) · Docs: **VitePress** (`docs/`)
15
+ - Tests: **RSpec** (`rspec-rails`, capybara with Cuprite, simplecov), **Vitest** (browser mode, headless Chrome through Playwright) · Docs: **VitePress** (`docs/`)
16
16
 
17
17
  ## Commands
18
18
 
@@ -21,6 +21,7 @@ bin/setup # bundle install + yarn install (dev setup)
21
21
  bin/console # IRB with trek loaded
22
22
  rake # default: spec + rubocop
23
23
  rake spec # run tests (or: bundle exec rspec)
24
+ yarn test # JavaScript tests (Vitest): the admin's Stimulus controllers, e.g. the content editor's
24
25
  COVERAGE=1 bundle exec rspec # tests + SimpleCov report in coverage/ (fails under the minimum set in spec/spec_helper.rb)
25
26
  make lint # all linters below (make -k lint to run them all despite a failure)
26
27
  make security # brakeman + bundler-audit
@@ -50,9 +51,11 @@ Security: `bundle exec brakeman` · `bundle exec bundle-audit check --update`
50
51
  - `config/locales/` — `trek.en.yml`, `trek.fr.yml`
51
52
  - `spec/` — RSpec suite, two kinds of spec:
52
53
  - **isolated** (`require_relative` the file under test, stub its host): concerns, helpers, simple components
53
- - **Rails** (`require "rails_helper"`): boots `spec/dummy`, a minimal host app on PostgreSQL (`trek_test`, or `DATABASE_URL`). Its models, migrations, Shrine initializer and the whole admin (controllers, views, routes, policies, mailers) are not committed: `spec/dummy/lib/generated_files.rb` runs Trek's own generator steps into `tmp/dummy` on every boot, and the database is rebuilt from them. A new model or migration template must be added to its `STEPS`, or `spec/dummy/generated_files_spec.rb` fails.
54
+ - **Rails** (`require "rails_helper"`): boots `spec/dummy`, a minimal host app on PostgreSQL (`trek_test`, or `DATABASE_URL`). Its models, migrations, Shrine initializer and the whole admin (controllers, views, routes, policies, mailers) are not committed: `spec/dummy/lib/generated_files.rb` runs Trek's own generator steps into `tmp/dummy` on every boot, and the database is rebuilt from them. A new model or migration template must be added to its `STEPS` (and a generator taking arguments, to its `ARGUMENTS`), or `spec/dummy/generated_files_spec.rb` fails.
54
55
  - **Host-app specs**: model, admin request, policy and mailer specs and their [Oaken](https://github.com/kaspth/oaken) seeds are templates (`lib/generators/trek/templates/{spec/models,spec/requests,spec/policies,spec/mailers,db/seeds}/`), installed in host apps by `trek:install:model_specs` and run by Trek's suite from `tmp/dummy` (`spec/dummy/generated_specs_spec.rb`); test data comes from `users.create`, `pages.create` and `seed "cases/…"`, not factories. Write specs for Trek's models there, in host-app style (no `frozen_string_literal`, Ruby 3.3-compatible — no bare `it` block parameter), so host apps get them too.
55
56
  - **Generators** (`spec/trek/generators_spec.rb`): every generator runs on a copy of a real `rails new` app (`spec/support/generators.rb`, built once per run into `tmp/generator_skeleton`), then again to check it changes nothing. Shell-outs are recorded in `shell_commands`, not run. A new generator is picked up automatically; if it needs arguments, an earlier Trek step or a starting file, add it to the spec's `arguments`, `prerequisites` or `setups`.
57
+ - **System** (`spec/system`, `type: :system`): the admin in a headless Chrome (Cuprite), for what needs a real browser: typing, Turbo panels, uploads. `spec/support/system.rb` builds Trek's JS and CSS once per run (`node esbuild.config.js`) and serves them as the admin's `admin.js` and `admin.css`. Keep them few: they take seconds each.
58
+ - `spec/javascript/` — Vitest tests (`yarn test`) of the admin's Stimulus controllers with their libraries, in a headless Chrome (the installed one, or `CHROME_PATH`): the content editor's controller and Tiptap, mounted on its component's markup, driven with the keyboard and the mouse (`userEvent`). Most editor behaviour is tested there: the whole file runs in about five seconds.
56
59
  - Both kinds run in one process, so an isolated spec must restore any global it changes (`I18n`, `Shrine` storages…). `spec_helper` loads Rails and Action View before Trek, as a host app does: several dependencies (ActionPolicy, Slim, ViewComponent) configure themselves from what is already loaded.
57
60
  - `docs/` — VitePress site
58
61
 
@@ -62,7 +65,7 @@ Security: `bundle exec brakeman` · `bundle exec bundle-audit check --update`
62
65
  - **Slim** linted via slim_lint; **JS/CSS** via ESLint + Stylelint + Prettier 2.8.8
63
66
  - **i18n**: keep English + French locales in sync (`config/locales/trek.*.yml`); DeepL auto-translation via `DEEPL_AUTH_KEY`
64
67
  - **Versioning**: version lives in both `lib/trek/version.rb` and `package.json` — keep in sync via `make release`
65
- - **CHANGELOG.md**: Keep-a-Changelog format. **Every MR must add an `[Unreleased]` entry**, except one that only changes specs. Keep entries short: one or two sentences on what changed for host apps, not how it was found or fixed — the MR description holds the details. When the change requires action in the host app on upgrade, end the entry with a bold `**Upgrade note:**` label followed by what to do. Use the same label when the change lets host apps remove or simplify existing patches or custom implementations (e.g. "**Upgrade note:** you can now drop your custom …") — never as an unlabelled trailing sentence.
68
+ - **CHANGELOG.md**: Keep-a-Changelog format. **Every MR must add an `[Unreleased]` entry**, except one that only changes specs or the repository's own tooling (`Makefile`, CI, Renovate config), which host apps never see. Keep entries short: one or two sentences on what changed for host apps, not how it was found or fixed — the MR description holds the details. When the change requires action in the host app on upgrade, end the entry with a bold `**Upgrade note:**` label followed by what to do. Use the same label when the change lets host apps remove or simplify existing patches or custom implementations (e.g. "**Upgrade note:** you can now drop your custom …") — never as an unlabelled trailing sentence.
66
69
  - **Generated model names**: a new model template belonging to another model is nested under it, in `models/<owner>/` (`User::Session`, `User::PreviousPassword`), not prefixed (`UserSession`). Nested in an Active Record model, it keeps the prefixed table name (`user_sessions`) and its owner's `has_many` finds it without `class_name:`. The older `PagePath`, `PageVersion` and `TagCategory` move in Trek 3 (#248).
67
70
  - **Generators' npm packages**: the install generators' `NPM_DEPENDENCIES` (`lib/generators/trek/install/*_generator.rb`) add to host apps the packages needed by the config files they copy from Trek's root (`esbuild.config.js`, `postcss.config.js`, `.eslintrc.json`, `.stylelintrc.json`, `lefthook.yml`). When a package is bumped in Trek's `package.json` — by hand or by Renovate, which doesn't touch the generators — bump its range in the generator in the same MR, or new apps get a version the copied config may not work with (as esbuild 0.15 did). List only packages those configs or the generated scripts use.
68
71
  - **Public code**: Trek ships publicly (gem, npm, docs), so nothing committed may mention a client or project name, nor a client's domain models or business logic — not in code, comments, specs, templates, docs, CHANGELOG or commit messages. Use neutral examples (`Page`, `Shop::Product`, an engine's `Blog::Post`). Client context belongs in GitLab issues and MR descriptions only.
data/CHANGELOG.md CHANGED
@@ -1,5 +1,42 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [2.6.0] - 2026-10-28
4
+
5
+ ### Added
6
+
7
+ - The content editor's link, image and prompt panels give hints under their fields: what to write in an image's alternative text, credit and caption, that a link goes to a page or a URL, and what a prompt's title is for. **Upgrade note:** you can now drop your own `helpers.hint.trek/content_editor/*` translations.
8
+ - The content editor's link panel asks for the link's title, shown when hovering it, as its prompt panel does: Tiptap 3 keeps it on the link, where Tiptap 2 dropped it, and the site renders it.
9
+ - CSP violations reported to AppSignal (`Trek::CspReportsController`) tell what was blocked: they're tagged with the browser's user agent, where it happened (the source file and line) and, for an inline script, its first 40 characters, which the generated policy now asks for with `'report-sample'`. The log line gives the source and sample too. Tags are cut to 255 characters, as AppSignal silently drops longer ones. **Upgrade note:** add `:report_sample` to `policy.script_src` in `config/initializers/content_security_policy.rb` to get the samples.
10
+ - `rails g trek:install:good_job`, a new installer step (also runnable alone) making [GoodJob](https://github.com/bensheldon/good_job) the Active Job backend, as every Trek app was doing by hand: it swaps `solid_queue` for `good_job` in the Gemfile (Solid Cache stays), removes the Solid Queue setup `rails new` leaves behind (`config/queue.yml`, `config/recurring.yml`, `db/queue_schema.rb`, `bin/jobs`, the Puma plugin, the `queue:` database and Kamal's `SOLID_QUEUE_IN_PUMA`/`JOB_CONCURRENCY` variables), and configures the adapter (Active Job' test adapter in `config/environments/test.rb`, so specs can check what is enqueued), `:async` execution in production, the queues, cron and error reporting in `config/initializers/good_job.rb`. The dashboard is mounted at `/admin/good_job` with a menu entry in the admin, guarded by the Trek session: anyone who is not an administrator gets a 404 rather than a redirect. Two specs come with it (dashboard access per role, and the mail delivery job). Because GoodJob discards a failed job, every `deliver_later` email now goes through an installed `MailDeliveryJob` that retries five times with a growing delay, and `config/database.yml` reserves connections for the job threads on top of the web threads (`GOOD_JOB_MAX_THREADS`, which GoodJob defaults to `RAILS_MAX_THREADS`). See [Background jobs](https://trek.etamin.studio/background-jobs). **Upgrade note:** apps installed before this version can run `rails g trek:install:good_job` — it is idempotent and skips what is already in place — then add the dashboard entry to `app/views/layouts/admin.html.slim` (an item with `text: t("admin.jobs")`, `href: "/admin/good_job"`, `allowed_to: Current.user.admin?` in the settings section) and run the migration it generates. Apps already on GoodJob only need the Gemfile left alone and their `config/initializers/good_job.rb` compared with the generated one; apps still on Solid Queue must drop the `queue` database from their deploy target as well.
11
+ - Dashboard tiles (`Trek::Dashboard::TileComponent`) opening in a new tab (`target: "_blank"`) show an external link icon after their text, and a tile's color is the accent of what it holds (`data-accent-color`), which can use `var(--accent-*)`.
12
+ - Public accounts: `rails g trek:public_accounts Customer` gives the site's or the mobile app's users (customers, members…) a model of their own, apart from the admin's `User`, with its own table, sessions and sign-in, so nothing public can reach a staff account. They sign in either with a code emailed to them, which creates the account the first time, or with `--sign-in=password` with a password, the email confirmed and the password reset by a link; a website gets pages and a cookie, `--api` JSON endpoints and a JWT naming a session, so it can be revoked, and `--model-only` neither, for an app whose sign-in is its own. See [Public accounts](https://trek.etamin.studio/reference/generators/public-accounts).
13
+ - `Trek::Users::SignInLockout` sends its email through `sign_in_lockout_mailer` (`"AdminMailer"` by default), and `Trek::PurgeExpiredSessionsJob` takes the session model to purge (`User::Session` by default), for public accounts.
14
+
15
+ ### Changed
16
+
17
+ - Trek requires the `prosemirror` gem 1.0 or later (was 0.7.1), and works with 1.1: rendering content (`formatted_content`) raised `ArgumentError` with it. `Trek::Content::Document#to_html` takes 1.1's `heading_ids: true`.
18
+ - `rails g trek:install` ends by listing the generators it doesn't run, to run as the app needs them: `trek:scaffold`, `trek:taxonomies`, `trek:audit_log`, `trek:public_accounts` and `trek:scalingo`.
19
+ - `trek:install:audit_log` and `trek:install:scalingo` are now `trek:audit_log` and `trek:scalingo`, as `trek:install` doesn't run them: `trek:install:*` is left to its own steps. The former names still work, with a deprecation warning, until Trek 3. **Upgrade note:** update the generator names in your docs and scripts.
20
+ - `trek:install:ruby` now pins **Ruby 4.0.9** (was 4.0.7), and the CI images (repo + generated `.gitlab-ci.yml` template) use the matching `4.0.9` tag. **Upgrade note:** existing apps can set `4.0.9` in `.ruby-version` and bump the `ci-ruby` image tag in `.gitlab-ci.yml`.
21
+ - Asking every user for a new password goes through its own controller, `Admin::PasswordRenewalsController#create` (`POST /admin/password_renewal`), instead of a `require_password_renewal` action of `Admin::UsersController`. **Upgrade note:** copy `app/controllers/admin/password_renewals_controller.rb` from Trek's templates (`lib/generators/trek/templates/controllers/admin/`), add `resource :password_renewal, only: :create` to the `admin` namespace of `config/routes.rb`, remove `post :require_password_renewal, on: :collection` from `resources :users` and the `require_password_renewal` action from `Admin::UsersController`, and point the button of `app/views/admin/users/index.html.slim` to `[:admin, :password_renewal]`.
22
+ - The new sign-in email (`AdminMailer#new_sign_in`) gives the IP address's host name next to it, as the active sessions do, looked up when the email is sent. **Upgrade note:** to get it, copy `new_sign_in` from Trek's `lib/generators/trek/templates/mailers/admin_mailer.rb`, and replace `@ip` with `@address` in `app/views/admin_mailer/new_sign_in.*`.
23
+ - Content is cleaned before it's saved (`Trek::Contentable`): empty paragraphs, empty list items and lists left empty are removed, as rendering already skipped them, so the stored JSON, `content_text` and searches no longer carry them. The document stays one the content editor opens, and node types or attributes the schema doesn't know are kept. The editor's JSON string is now stored as a document, which `Fragment.search` can search. **Upgrade note:** existing content is cleaned the next time it's saved; empty paragraphs authors used for spacing are removed for good. Apps that pruned empty paragraphs before saving can drop that code.
24
+ - The content editor's panels label their fields more explicitly: « Largeur d'affichage », « Légende de l'image », « Lien vers », « Ouvrir ce lien dans… », « Texte du lien », « Texte du bouton », and « Dépasse sur les côtés » for the wider image size (in English: "Display width", "Image caption", "Link to", "Open this link in…", "Link text", "Button text", "Extends past the sides").
25
+ - Trek is now developed and tested against **prosemirror 1.0**, its first stable release, which imports HTML and Markdown about a quarter faster. Trek already accepted it (`>= 0.7.1, < 2`), and doesn't use what 1.0 renames. **Upgrade note:** an app calling `Schema#parse_html`, `#parse_markdown` or `#parse_text`, or `Prosemirror::TextAlign`, renames them `#from_html`, `#from_markdown`, `#from_text` and `Prosemirror::Extensions::TextAlign` when it runs `bundle update prosemirror` (see [its changelog](https://github.com/etaminstudio/prosemirror/blob/main/CHANGELOG.md)).
26
+ - Failed form submissions answer with `:unprocessable_content` (422), Rack's name for it since 3.1, instead of the deprecated `:unprocessable_entity`, in `Trek::ResourceController`, the sign-in controller and the generated specs, which printed a deprecation warning in every run. **Upgrade note:** replace `:unprocessable_entity` with `:unprocessable_content` in the app's own controllers and specs to silence the warning.
27
+ - **Security:** the content editor runs on **Tiptap 3** (was 2), which fixes `mergeAttributes()` turning an own `__proto__` key into DOM attributes ([GHSA-cp6q-959q-f8rh](https://github.com/advisories/GHSA-cp6q-959q-f8rh)). Its floating menu uses Floating UI instead of Tippy. The editor behaves as before: no underline, and no empty paragraph added after a final block. **Upgrade note:** run `yarn up trek`, and update the app's own Tiptap extensions, if any, to Tiptap 3 (see [Upgrade from Tiptap v2](https://tiptap.dev/docs/guides/upgrade-tiptap-v2)).
28
+ - `Trek::Form::ImageFieldComponent` (`f.image_field`) shows two icons over its thumbnail, on hover and focus, opening the original image in a new tab or downloading it, instead of a « See original » button under « Replace » and « Remove », which wrapped onto two lines in French.
29
+
30
+ ### Fixed
31
+
32
+ - `Trek::TypographyFormatter` broke content saved from the content editor: given the editor's JSON string, it formatted it as plain text, and the non-breaking spaces it added before colons made the JSON invalid. It now parses the document, and formats its text nodes. **Upgrade note:** an app that added `:content` to `format_attributes` may hold records whose content no longer parses (`formatted_content` raises `JSON::ParserError`), which the editor can't open either: restore them from a backup.
33
+ - `Trek::TypographyFormatter` no longer adds a space before a colon, semicolon, `!`, `?`, `%`, `€` or `$` inside a word: times (`10:30`), URLs (`https://…?a=1`) and ratios (`3:1`) are left alone, and one space goes before a group of signs (`Quoi ?!`). Apostrophes and the space after `«` now work next to accented letters (`l’été`, `« Été »`). Spaces earlier versions put inside a word (`10 :30`, `https ://`) are removed. **Upgrade note:** formatted titles and content are repaired the next time they're saved.
34
+ - `content_text` ran the content's paragraphs and lines together (`Line oneLine two`), which shows in the excerpts of the admin's fragments index: it now separates blocks with a blank line, and hard breaks with a newline (`Prosemirror::Document#to_text`).
35
+ - Reordering a list in the admin (`Trek::Reorderable`) could leave records out of place: it saved them one at a time while acts_as_list, and closure_tree for the trees ordered with `numeric_order`, renumbered the other records at each save. A list numbered from 1, acts_as_list's default, lost a record on most moves, as the list posts positions from 0. The posted order is now applied as a whole: in an `acts_as_list_no_update` block, from the list's `top_of_list`, or with closure_tree's `prepend_sibling` and `append_sibling` for a tree. Records missing from the list (deleted meanwhile, or left out by `reorderable_objects`) are skipped. **Upgrade note:** you can now drop your own `reorder` actions written to work around it.
36
+ - Adding a call to action from the content editor failed with `NoMethodError: undefined method 'color'`: the prompt panel's response read a `color` that `Trek::ContentEditor::Prompt` never had, and the panel has no field for. Prompts no longer have a color: the editor's block, the panel and `Trek::Content::PromptsBlock` drop it, and the site's link loses its `data-color`. A color already stored in content stays in the JSON, unrendered. **Upgrade note:** an app styling prompts with `[data-color]`, or reading `@color` in its own `PromptsBlock`, can drop that code.
37
+ - **Security:** the content editor showed image and call-to-action blocks by writing their attributes into HTML: an alternative text, caption or button text containing `"` or `<` broke the block, or injected markup into the admin. They are now built as DOM nodes, their texts as text.
38
+ - The content editor's toolbar buttons show the styles and the block where the cursor is right after a click: the bold, italic and heading buttons marked their icon instead of the button until the cursor moved.
39
+
3
40
  ## [2.5.0] - 2026-10-25
4
41
 
5
42
  ### Changed
data/Gemfile CHANGED
@@ -24,8 +24,13 @@ end
24
24
  group :test do
25
25
  gem "bcrypt", "~> 3.1"
26
26
  gem "capybara", "~> 3.38"
27
+ # System specs: a headless Chrome driven through its DevTools protocol, and the server it browses
28
+ gem "cuprite", "~> 0.19"
29
+ # The dummy app's API accounts (trek:public_accounts --api)
30
+ gem "jwt", "~> 3.1"
27
31
  gem "oaken", "~> 1.0"
28
32
  gem "pg", "~> 1.5"
33
+ gem "puma", "~> 8.0"
29
34
  gem "simplecov", "~> 0.22.0"
30
35
  gem "simplecov-cobertura", "~> 3.0"
31
36
  end
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- trek (2.5.0)
4
+ trek (2.6.0)
5
5
  action_policy (~> 0.6)
6
6
  actioncable
7
7
  acts_as_list (~> 1.1)
@@ -22,7 +22,7 @@ PATH
22
22
  mobility (~> 1.3.2)
23
23
  phonelib (~> 0.8)
24
24
  postmark-rails
25
- prosemirror (>= 0.7.1, < 2)
25
+ prosemirror (>= 1.0, < 2)
26
26
  rails (>= 8.0, < 8.2)
27
27
  rails-healthcheck
28
28
  rails-i18n (>= 6.0)
@@ -180,6 +180,9 @@ GEM
180
180
  addressable
181
181
  base64
182
182
  ssrf_filter (~> 1.5)
183
+ cuprite (0.19)
184
+ capybara (~> 3.0)
185
+ ferrum (~> 0.19.1)
183
186
  date (3.6.0)
184
187
  deepl-rb (3.9.0)
185
188
  diff-lcs (1.6.2)
@@ -222,6 +225,11 @@ GEM
222
225
  erb (6.0.7)
223
226
  erubi (1.13.1)
224
227
  fastimage (2.4.1)
228
+ ferrum (0.19.1)
229
+ addressable (~> 2.5)
230
+ base64 (~> 0.2)
231
+ concurrent-ruby (~> 1.1)
232
+ websocket-driver (~> 0.7)
225
233
  ffi (1.17.4-arm64-darwin)
226
234
  ffi (1.17.4-x86_64-darwin)
227
235
  ffi (1.17.4-x86_64-linux-gnu)
@@ -352,11 +360,13 @@ GEM
352
360
  prettyprint
353
361
  prettyprint (0.2.1)
354
362
  prism (1.9.0)
355
- prosemirror (0.7.1)
363
+ prosemirror (1.1.0)
356
364
  literal (~> 1.9)
357
365
  phlex (~> 2.4)
358
366
  zeitwerk (~> 2.6)
359
367
  public_suffix (7.0.5)
368
+ puma (8.0.2)
369
+ nio4r (~> 2.0)
360
370
  racc (1.8.1)
361
371
  rack (3.2.7)
362
372
  rack-session (2.1.2)
@@ -603,8 +613,11 @@ DEPENDENCIES
603
613
  brakeman (~> 7.0)
604
614
  bundler-audit (~> 0.9)
605
615
  capybara (~> 3.38)
616
+ cuprite (~> 0.19)
617
+ jwt (~> 3.1)
606
618
  oaken (~> 1.0)
607
619
  pg (~> 1.5)
620
+ puma (~> 8.0)
608
621
  rake (~> 13.0)
609
622
  reek (~> 6.1)
610
623
  rspec-html-matchers (~> 0.10)
data/Makefile CHANGED
@@ -51,6 +51,8 @@ i18n-check: i18n-normalize
51
51
  git diff --exit-code config/locales
52
52
 
53
53
  release:
54
+ @# Before bumping anything: with a missing or expired token, npm publish fails with a misleading 404
55
+ @npm whoami >/dev/null 2>&1 || { echo "Not signed in to npm, or the token expired: run npm login first."; exit 1; }
54
56
  @read -p "New version (current: $$(sed -n 's/.*VERSION = "\(.*\)"/\1/p' lib/trek/version.rb)): " version; \
55
57
  if [ -z "$$version" ]; then echo "Version cannot be empty"; exit 1; fi && \
56
58
  sed -i '' "s/VERSION = \".*\"/VERSION = \"$$version\"/" lib/trek/version.rb && \
@@ -46,6 +46,15 @@
46
46
  transition: transform 0.2s ease-in-out;
47
47
  }
48
48
 
49
+ /* A tile opening in a new tab: the icon follows the text, on its last line */
50
+ .external {
51
+ --icon-size: 1.4rem;
52
+
53
+ display: inline-block;
54
+ margin-left: 0.6rem;
55
+ vertical-align: -0.1rem;
56
+ }
57
+
49
58
  .root.color-amber {
50
59
  --icon-color: var(--amber-11);
51
60
 
@@ -4,9 +4,11 @@
4
4
  span(class=class_for("title")) = title
5
5
  - if icon
6
6
  = render Trek::IconComponent.new(icon, classnames: class_for("icon"))
7
- span(class=class_for("text")) = formatted_text
7
+ span(class=class_for("text"))
8
+ = formatted_text
9
+ = external_icon
8
10
  - else
9
- div(class=classes)
11
+ div(class=classes data-accent-color=color)
10
12
  - if title
11
13
  span(class=class_for("title")) = title
12
14
  - if icon
@@ -31,12 +31,22 @@ module Trek
31
31
  )
32
32
  end
33
33
 
34
+ # The tile's color is the accent (`data-accent-color`) of what it holds, which can use `var(--accent-*)`
34
35
  def link_data
35
- data.merge(turbo_method: method).compact
36
+ { accent_color: color }.merge(data, turbo_method: method).compact
36
37
  end
37
38
 
38
39
  def link_rel
39
- rel || ("noopener" if target == "_blank")
40
+ rel || ("noopener" if new_tab?)
41
+ end
42
+
43
+ def new_tab?
44
+ target == "_blank"
45
+ end
46
+
47
+ # After the text of a link opening in a new tab
48
+ def external_icon
49
+ render Trek::IconComponent.new("trek/external", classnames: class_for("external"), aria_hidden: true) if new_tab?
40
50
  end
41
51
 
42
52
  def formatted_text
@@ -39,6 +39,11 @@
39
39
  display: none;
40
40
  }
41
41
 
42
+ .floating-menu {
43
+ /* Above the editor's content: Floating UI positions it, with no z-index of its own */
44
+ z-index: 1;
45
+ }
46
+
42
47
  .floating-menu .button {
43
48
  padding: 0.7rem 1.2rem;
44
49
  }
@@ -8,34 +8,22 @@ div(
8
8
  button.bold(
9
9
  class=class_for("button")
10
10
  type="button"
11
- data-action=stimulus_action("toggleBold")
11
+ data=command_data("toggleBold", active: "bold")
12
12
  ) = render Trek::IconComponent.new("trek/bold", title: t(".bold"))
13
13
 
14
14
  button.italic(
15
15
  class=class_for("button")
16
16
  type="button"
17
- data-action=stimulus_action("toggleItalic")
17
+ data=command_data("toggleItalic", active: "italic")
18
18
  ) = render Trek::IconComponent.new("trek/italic", title: t(".italic"))
19
19
 
20
- / button.strike(
21
- / class=class_for("button")
22
- / type="button"
23
- / data-action=stimulus_action("toggleStrike")
24
- / ) strike
25
-
26
- / button.code(
27
- / class=class_for("button")
28
- / type="button"
29
- / data-action=stimulus_action("toggleCode")
30
- / ) code
31
-
32
20
  / The panel opening is handled by the hidden button's
33
21
  openStackedPanelWhenLoaded action — a direct openPanel here would
34
22
  pop the (possibly empty) primary panel alongside the stacked one.
35
23
  button.link(
36
24
  class=class_for("button")
37
25
  type="button"
38
- data-action=stimulus_action("toggleLink")
26
+ data={ action: stimulus_action("toggleLink"), **state_data("link") }
39
27
  ) = render Trek::IconComponent.new("trek/link", title: t(".link"))
40
28
 
41
29
  = link_to "",
@@ -51,7 +39,7 @@ div(
51
39
  button(
52
40
  class=class_for("button")
53
41
  type="button"
54
- data-action=stimulus_action("unsetAllMarks")
42
+ data=command_data("unsetAllMarks")
55
43
  )
56
44
  = render Trek::IconComponent.new(\
57
45
  "trek/eraser",
@@ -61,7 +49,7 @@ div(
61
49
  button(
62
50
  class=class_for("button")
63
51
  type="button"
64
- data-action=stimulus_action("setHardBreak")
52
+ data=command_data("setHardBreak")
65
53
  )
66
54
  = render Trek::IconComponent.new(\
67
55
  "trek/return",
@@ -74,86 +62,36 @@ div(
74
62
  button.paragraph(
75
63
  class=class_for("button")
76
64
  type="button"
77
- data-action=stimulus_action("setParagraph")
65
+ data=command_data("setParagraph", active: "paragraph")
78
66
  )
79
67
  = render Trek::IconComponent.new(\
80
68
  "trek/paragraph",
81
69
  title: t(".paragraph"),
82
70
  )
83
71
 
84
- / button.h1(
85
- / class=class_for("button")
86
- / type="button"
87
- / data-action=stimulus_action("toggleHeading")
88
- / data-level=1
89
- / ) h1
90
-
91
- button.h2(
92
- class=class_for("button")
93
- type="button"
94
- data-action=stimulus_action("toggleHeading")
95
- data-level=2
96
- title=t(".h2")
97
- ) h2
98
-
99
- button.h3(
100
- class=class_for("button")
101
- type="button"
102
- data-action=stimulus_action("toggleHeading")
103
- data-level=3
104
- title=t(".h3")
105
- ) h3
106
-
107
- button.h4(
108
- class=class_for("button")
109
- type="button"
110
- data-action=stimulus_action("toggleHeading")
111
- data-level=4
112
- title=t(".h4")
113
- ) h4
114
-
115
- / button.h5(
116
- / class=class_for("button")
117
- / type="button"
118
- / data-action=stimulus_action("toggleHeading")
119
- / data-level=5
120
- / title=t(".h5")
121
- / ) h5
122
-
123
- / button.h6(
124
- / class=class_for("button")
125
- / type="button"
126
- / data-action=stimulus_action("toggleHeading")
127
- / data-level=6
128
- / title=t(".h6")
129
- / ) h6
72
+ - [2, 3, 4].each do |level|
73
+ button(
74
+ class=class_for("button")
75
+ class="h#{level}"
76
+ type="button"
77
+ data=command_data("toggleHeading", active: "heading", level:)
78
+ title=t(".h#{level}")
79
+ ) = "h#{level}"
130
80
 
131
81
  button.bulletList(
132
82
  class=class_for("button")
133
83
  type="button"
134
- data-action=stimulus_action("toggleBulletList")
84
+ data=command_data("toggleBulletList", active: "bulletList")
135
85
  )
136
86
  = render Trek::IconComponent.new( \
137
87
  "trek/list-bullets",
138
88
  title: t(".bullet_list"),
139
89
  )
140
90
 
141
- / button.orderedList(
142
- / class=class_for("button")
143
- / type="button"
144
- / data-action=stimulus_action("toggleOrderedList")
145
- / ) ordered list
146
-
147
- / button.codeBlock(
148
- / class=class_for("button")
149
- / type="button"
150
- / data-action=stimulus_action("toggleCodeBlock")
151
- / ) code block
152
-
153
91
  button.blockquote(
154
92
  class=class_for("button")
155
93
  type="button"
156
- data-action=stimulus_action("toggleBlockquote")
94
+ data=command_data("toggleBlockquote", active: "blockquote")
157
95
  )
158
96
  = render Trek::IconComponent.new( \
159
97
  "trek/quotation",
@@ -163,37 +101,25 @@ div(
163
101
  button(
164
102
  class=class_for("button")
165
103
  type="button"
166
- data-action=stimulus_action("setHorizontalRule")
104
+ data=command_data("setHorizontalRule")
167
105
  )
168
106
  = render Trek::IconComponent.new( \
169
107
  "trek/hr",
170
108
  title: t(".horizontal_rule"),
171
109
  )
172
110
 
173
- / button(
174
- / class=class_for("button")
175
- / type="button"
176
- / data-action=stimulus_action("clearNodes")
177
- / ) clear nodes
178
-
179
111
  button(
180
112
  class=class_names_for("button", "button-end", map_args: true)
181
113
  type="button"
182
- data-action=stimulus_action("undo")
114
+ data=command_data("undo")
183
115
  ) = render Trek::IconComponent.new("trek/undo", title: t(".undo"))
184
116
 
185
117
  button(
186
118
  class=class_for("button")
187
119
  type="button"
188
- data-action=stimulus_action("redo")
120
+ data=command_data("redo")
189
121
  ) = render Trek::IconComponent.new("trek/redo", title: t(".redo"))
190
122
 
191
- / button(
192
- / class=class_for("button")
193
- / type="button"
194
- / data-action=stimulus_action("insertFrame")
195
- / ) frame
196
-
197
123
  div(
198
124
  class=class_for("content")
199
125
  data=content_data_attributes