trek 2.1.0 → 2.3.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 (142) hide show
  1. checksums.yaml +4 -4
  2. data/.slim-lint.yml +0 -3
  3. data/AGENTS.md +8 -1
  4. data/CHANGELOG.md +75 -0
  5. data/Gemfile +4 -0
  6. data/Gemfile.lock +20 -3
  7. data/Makefile +11 -9
  8. data/README.md +1 -1
  9. data/app/assets/icons/trek/audit.svg +1 -0
  10. data/app/assets/icons/trek/chevron-down.svg +1 -0
  11. data/app/assets/icons/trek/chevron-up.svg +1 -0
  12. data/app/assets/icons/trek/external.svg +1 -0
  13. data/app/assets/icons/trek/print.svg +1 -0
  14. data/app/assets/icons/trek/theme-auto.svg +1 -0
  15. data/app/assets/icons/trek/theme-dark.svg +1 -0
  16. data/app/assets/icons/trek/theme-light.svg +1 -0
  17. data/app/assets/stylesheets/trek/base.css +13 -0
  18. data/app/components/trek/button_component/button_component.css +9 -0
  19. data/app/components/trek/button_component.rb +13 -1
  20. data/app/components/trek/dashboard/analytics_component/analytics_component.html.slim +2 -0
  21. data/app/components/trek/dashboard/analytics_component.rb +1 -0
  22. data/app/components/trek/dashboard/chart_component/chart_component.css +192 -0
  23. data/app/components/trek/dashboard/chart_component/chart_component.html.slim +6 -0
  24. data/app/components/trek/dashboard/chart_component/chart_component.js +1 -0
  25. data/app/components/trek/dashboard/chart_component.rb +27 -0
  26. data/app/components/trek/dashboard/tile_component/tile_component.css +40 -0
  27. data/app/components/trek/dashboard/tile_component/tile_component.html.slim +14 -4
  28. data/app/components/trek/dashboard/tile_component.rb +20 -2
  29. data/app/components/trek/dialog_component/dialog_component.html.slim +6 -2
  30. data/app/components/trek/dialog_component/dialog_component.js +62 -2
  31. data/app/components/trek/dropdown_component/dropdown_component.css +66 -0
  32. data/app/components/trek/dropdown_component/dropdown_component.html.slim +31 -0
  33. data/app/components/trek/dropdown_component/dropdown_component.js +114 -0
  34. data/app/components/trek/dropdown_component.rb +29 -0
  35. data/app/components/trek/form/actions_component/actions_component.css +6 -7
  36. data/app/components/trek/form/content_editor_component/content_editor_component.html.slim +10 -12
  37. data/app/components/trek/form/content_editor_component/content_editor_component.js +4 -0
  38. data/app/components/trek/form/group_component/group_component.css +34 -0
  39. data/app/components/trek/header_component/header_component.css +10 -4
  40. data/app/components/trek/header_component/header_component.html.slim +17 -10
  41. data/app/components/trek/header_component/header_component.js +29 -5
  42. data/app/components/trek/header_component.rb +20 -3
  43. data/app/components/trek/layout_component/layout_component.css +20 -0
  44. data/app/components/trek/layout_component/layout_component.html.slim +16 -0
  45. data/app/components/trek/layout_component/layout_component.js +35 -6
  46. data/app/components/trek/menu_component/menu_component.css +6 -0
  47. data/app/components/trek/menu_component.rb +29 -4
  48. data/app/components/trek/toaster/toast_component.rb +1 -1
  49. data/app/controllers/concerns/trek/authorization.rb +12 -0
  50. data/app/formatters/prosemirror_to_html/global_id_to_links_formatter.rb +3 -1
  51. data/app/helpers/trek/page_path_helper.rb +4 -3
  52. data/app/helpers/trek/theme_helper.rb +16 -1
  53. data/app/models/concerns/trek/phonable.rb +15 -3
  54. data/app/models/concerns/trek/sectionable.rb +5 -0
  55. data/app/models/concerns/trek/users/validations.rb +1 -1
  56. data/app/models/prosemirror.rb +5 -2
  57. data/app/uploaders/audio_uploader.rb +11 -0
  58. data/app/uploaders/record_image_uploader.rb +3 -0
  59. data/app/views/trek/panels/images/_form.html.slim +1 -1
  60. data/app/views/trek/panels/images/create.turbo_stream.slim +3 -3
  61. data/app/views/trek/panels/images/new.html.slim +1 -1
  62. data/app/views/trek/panels/links/_form.html.slim +1 -1
  63. data/app/views/trek/panels/links/create.turbo_stream.slim +3 -3
  64. data/app/views/trek/panels/links/new.html.slim +1 -1
  65. data/app/views/trek/panels/prompts/_form.html.slim +1 -1
  66. data/app/views/trek/panels/prompts/create.turbo_stream.slim +3 -3
  67. data/app/views/trek/panels/prompts/new.html.slim +1 -1
  68. data/config/locales/trek.en.yml +3 -2
  69. data/config/locales/trek.fr.yml +1 -0
  70. data/docs/customize.md +18 -2
  71. data/docs/deploy.md +7 -2
  72. data/docs/getting-started.md +1 -1
  73. data/docs/index.md +1 -1
  74. data/docs/reference/components.md +15 -0
  75. data/docs/reference/concerns/phonable.md +27 -1
  76. data/docs/reference/environment-variables.md +2 -0
  77. data/docs/reference/forms.md +2 -0
  78. data/docs/reference/generators/install.md +4 -1
  79. data/docs/reference/icons.md +1 -1
  80. data/docs/reference/models/tag.md +1 -1
  81. data/docs/reference/models/user.md +4 -2
  82. data/docs/reference/uploaders.md +31 -6
  83. data/docs/what-is-trek.md +1 -1
  84. data/lib/generators/trek/install/admin/routes_generator.rb +7 -1
  85. data/lib/generators/trek/install/appsignal_generator.rb +102 -0
  86. data/lib/generators/trek/install/model_specs_generator.rb +64 -0
  87. data/lib/generators/trek/install/postmark_generator.rb +3 -0
  88. data/lib/generators/trek/install/ruby_generator.rb +1 -1
  89. data/lib/generators/trek/install/scalingo_generator.rb +1 -1
  90. data/lib/generators/trek/install/uploaders_generator.rb +2 -0
  91. data/lib/generators/trek/install_generator.rb +5 -0
  92. data/lib/generators/trek/taxonomies_generator.rb +7 -0
  93. data/lib/generators/trek/templates/.gitlab-ci.yml +1 -1
  94. data/lib/generators/trek/templates/config/appsignal.rb.tt +25 -0
  95. data/lib/generators/trek/templates/controllers/admin/uploads_controller.rb +4 -0
  96. data/lib/generators/trek/templates/controllers/admin/users_controller.rb +4 -1
  97. data/lib/generators/trek/templates/controllers/concerns/appsignal_context.rb +37 -0
  98. data/lib/generators/trek/templates/db/seeds/setup/fragments.rb +3 -0
  99. data/lib/generators/trek/templates/db/seeds/setup/users.rb +6 -0
  100. data/lib/generators/trek/templates/db/seeds/test/cases/page_tree.rb +5 -0
  101. data/lib/generators/trek/templates/initializers/appsignal_context.rb +5 -0
  102. data/lib/generators/trek/templates/locales/scaffold/model.en.yml.tt +1 -1
  103. data/lib/generators/trek/templates/migrations/fragments.rb.erb +1 -1
  104. data/lib/generators/trek/templates/migrations/menu_nodes.rb.erb +0 -1
  105. data/lib/generators/trek/templates/migrations/tag_categories.rb.erb +1 -1
  106. data/lib/generators/trek/templates/migrations/taggings.rb.erb +1 -1
  107. data/lib/generators/trek/templates/migrations/users.rb.erb +2 -1
  108. data/lib/generators/trek/templates/models/tag.rb +6 -1
  109. data/lib/generators/trek/templates/policies/admin/tag_policy.rb +1 -1
  110. data/lib/generators/trek/templates/policies/external_link_policy.rb +1 -1
  111. data/lib/generators/trek/templates/spec/fixtures/files/image.png +0 -0
  112. data/lib/generators/trek/templates/spec/fixtures/files/sound.mp3 +0 -0
  113. data/lib/generators/trek/templates/spec/models/page_spec.rb +24 -0
  114. data/lib/generators/trek/templates/spec/models/user_spec.rb +76 -0
  115. data/lib/generators/trek/templates/spec/policies/admin/external_link_policy_spec.rb +17 -0
  116. data/lib/generators/trek/templates/spec/policies/admin/fragment_policy_spec.rb +33 -0
  117. data/lib/generators/trek/templates/spec/policies/admin/page_policy_spec.rb +39 -0
  118. data/lib/generators/trek/templates/spec/policies/admin/preview_policy_spec.rb +9 -0
  119. data/lib/generators/trek/templates/spec/policies/admin/tag_category_policy_spec.rb +31 -0
  120. data/lib/generators/trek/templates/spec/policies/admin/tag_policy_spec.rb +31 -0
  121. data/lib/generators/trek/templates/spec/policies/admin/user_policy_spec.rb +58 -0
  122. data/lib/generators/trek/templates/spec/requests/admin/fragments_spec.rb +50 -0
  123. data/lib/generators/trek/templates/spec/requests/admin/pages_spec.rb +47 -0
  124. data/lib/generators/trek/templates/spec/requests/admin/uploads_spec.rb +22 -0
  125. data/lib/generators/trek/templates/spec/requests/admin/user_password_resets_spec.rb +71 -0
  126. data/lib/generators/trek/templates/spec/requests/admin/user_sessions_spec.rb +45 -0
  127. data/lib/generators/trek/templates/spec/requests/admin/users_spec.rb +99 -0
  128. data/lib/generators/trek/templates/views/admin/user_password_resets/edit.html.slim +1 -1
  129. data/lib/generators/trek/templates/views/admin/user_password_resets/new.html.slim +1 -1
  130. data/lib/generators/trek/templates/views/admin/user_sessions/new.html.slim +2 -2
  131. data/lib/generators/trek/templates/views/admin/users/theme.turbo_stream.slim +2 -0
  132. data/lib/generators/trek/upgrade/v2_generator.rb +3 -0
  133. data/lib/tasks/trek/attachments.rake +18 -0
  134. data/lib/trek/cached_attachments.rb +53 -0
  135. data/lib/trek/saved_attachments_reload.rb +44 -0
  136. data/lib/trek/version.rb +1 -1
  137. data/lib/trek.rb +3 -0
  138. data/package.json +1 -1
  139. data/postcss.config.js +36 -0
  140. data/trek.gemspec +5 -0
  141. metadata +76 -3
  142. data/app/assets/icons/trek/theme.svg +0 -11
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5a2b7ab45c51c036cab94f7ab2f6119b84cfccf501d1133cd4f390e320d40aa6
4
- data.tar.gz: '0803283b1a6283983d174c9797d5ea753f97ffc1ce458e0285ab2ac681257ecf'
3
+ metadata.gz: c42696637827a5c00e5b85274ade2eda636f54cd975af5f781c5f0eb6090e4e9
4
+ data.tar.gz: 1860fefeba9c9a75a1c5caa7e1ec3b9a5907a914636f5c1e65be27ce9a5fe166
5
5
  SHA512:
6
- metadata.gz: 10aea34299dba23456b3f687c6e450996d5473e8d34d9a12200373c2e3f394025e6e5fc2aab4d2dfe00628133113171354780ab3123c6628dc5480e75ed87987
7
- data.tar.gz: 966a6004dc2aaa1b7ff21c4db7c87d00e3eb4054ea3dbaec780c62823b0d297f8736be2d93216dc438820375aa3487063882325fd55cb8f612ec831a9179c830
6
+ metadata.gz: c6da73ed50866c320bb0a16b91d6acb77d2c6eb43ec3d9bc5fdcbd2000560dcc7789929d6a01674cce34f1c53ba4db94da5454d8302d841987d20fa48594c0f4
7
+ data.tar.gz: b5897444ef3f86a8f86f65b82180d20078e2220a5508cc8af5d87c36ff2c05cb43dc9f4b1ee30fea9afb43c3769754dfab4670127909a6a4788db213b276f9e8
data/.slim-lint.yml CHANGED
@@ -8,9 +8,6 @@
8
8
  skip_frontmatter: false
9
9
 
10
10
  linters:
11
- AllCops:
12
- NewCops: disable
13
-
14
11
  CommentControlStatement:
15
12
  enabled: true
16
13
 
data/AGENTS.md CHANGED
@@ -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
+ COVERAGE=1 bundle exec rspec # tests + SimpleCov report in coverage/ (fails under the minimum set in spec/spec_helper.rb)
24
25
  make lint # all linters below
25
26
  make fix # rubocop/eslint/stylelint/prettier autocorrect
26
27
  make release # bump version (version.rb + package.json), stamp CHANGELOG, tag, push gem + npm
@@ -45,7 +46,13 @@ Security: `bundle exec brakeman` · `bundle exec bundle-audit check --update`
45
46
  - `lib/generators/trek/` — the heart of the gem: `install/` (~30 generators), `scaffold_generator.rb`, `admin/`, `templates/`
46
47
  - `lib/trek/` — `engine.rb`, `version.rb`, `i18n_keys_resolver.rb`, `typography_formatter.rb`
47
48
  - `config/locales/` — `trek.en.yml`, `trek.fr.yml`
48
- - `spec/` — RSpec suite · `docs/` — VitePress site
49
+ - `spec/` — RSpec suite, two kinds of spec:
50
+ - **isolated** (`require_relative` the file under test, stub its host): concerns, helpers, simple components
51
+ - **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.
52
+ - **Host-app specs**: model and admin request specs and their [Oaken](https://github.com/kaspth/oaken) seeds are templates (`lib/generators/trek/templates/{spec/models,spec/requests,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.1-compatible — no bare `it` block parameter), so host apps get them too.
53
+ - **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`.
54
+ - 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.
55
+ - `docs/` — VitePress site
49
56
 
50
57
  ## Conventions
51
58
 
data/CHANGELOG.md CHANGED
@@ -1,5 +1,80 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [2.3.0] - 2026-10-01
4
+
5
+ ### Added
6
+
7
+ - More admin request specs from `rails g trek:install:model_specs`, run by Trek's own suite as well: `fragments_spec.rb` (index, new, create, duplicate key, edit, update, destroy), `users_spec.rb` (an admin lists privileged users only, creates a user and sends the invitation, changes a role, deletes a user; an editor is sent back from the users list and other profiles, gets a 403 in JSON, edits their own profile but not their role, switches the admin theme) and `user_password_resets_spec.rb` (the reset request answers known and unknown emails alike and only mails privileged users, reset and invitation links open the form, an invalid link goes back to the request form, a new password signs the user out everywhere, a password under 8 characters is refused). A `db/seeds/setup/fragments.rb` makes `fragments` available to seeds and specs. **Upgrade note:** re-run `rails g trek:install:model_specs` to add them.
8
+ - `Trek::Dashboard::TileComponent` accepts `method:` and `data:`. With `method: :post` (or `:delete`…) the tile issues that request through Turbo, as a `data-turbo-method` link like `Trek::ButtonComponent`'s `to:` links, so a dashboard shortcut can create a record in one click (e.g. a pre-filled draft) rather than only open a form; `data:` adds attributes to the link, such as `turbo_confirm:`. Tiles without them still render a plain GET link. **Upgrade note:** you can now turn a tile pointing to a form into a direct action, and drop buttons kept elsewhere only because a tile couldn't POST.
9
+ - Policy specs for host apps, run by Trek's own suite as well: `rails g trek:install:model_specs` installs `spec/policies/admin/` specs for the page, fragment, user, preview and external link policies (what each role may do, which attributes it may change, which records it sees), and `rails g trek:taxonomies` installs the tag and tag category ones (a tag or category in use can't be deleted). **Upgrade note:** re-run `rails g trek:install:model_specs` (and `rails g trek:taxonomies` if you use tags) to add them.
10
+ - Sound uploads from the admin: `Trek::Form::SoundFieldComponent` (`f.sound_field`) posted to `/admin/upload/audio`, which no generated app routed or handled. `trek:install:uploaders` now installs an `AudioUploader` (MP3, AAC/M4A, Ogg, WAV, WebM, FLAC, up to the field's 200 MB), the generated `Admin::UploadsController` gains an `audio` action, and `trek:install:admin:routes` adds `post "upload/audio", to: "uploads#audio"` to the admin namespace. Attach it with `include AudioUploader::Attachment(:audio)` on a record with an `audio_data` jsonb column. **Upgrade note:** copy `app/uploaders/audio_uploader.rb` from Trek (or re-run `rails g trek:install:uploaders`), add the `audio` action to `app/controllers/admin/uploads_controller.rb` (`set_rack_response AudioUploader.upload_response(:cache, request.env)`), and add `post "upload/audio", to: "uploads#audio"` inside `namespace :admin` in `config/routes.rb`; you can then drop a hand-written audio uploader, action and route that did the same.
11
+ - `rails g trek:install:model_specs` also installs request specs for the admin, run by Trek's own suite as well: `spec/requests/admin/user_sessions_spec.rb` (sign-in form, signing in and out, refusing a wrong password or a user who isn't privileged), `pages_spec.rb` (index, new, create, invalid create, edit, update, destroy as an editor), `uploads_spec.rb` (the image upload endpoint) and the existing `n_plus_one_spec.rb`, with an image fixture in `spec/fixtures/files`. The Oaken users setup gains a `users.password` helper to sign in with. **Upgrade note:** re-run `rails g trek:install:model_specs` to add them, then `bin/rails spec`.
12
+ - `rails g trek:install:model_specs`, a new installer step (also runnable alone) giving host apps specs for the models Trek installs: `spec/models/page_spec.rb` (slugs, full paths through the page tree, path history when a page moves) and `spec/models/user_spec.rb` (validations, roles, `privileged`, `to_s`). Their test data uses [Oaken](https://github.com/kaspth/oaken) seed scripts rather than factories: `db/seeds/setup/users.rb` sets defaults for `users.create`/`users.new` (a unique email, a valid password), and `db/seeds/test/cases/page_tree.rb` lays out a named page tree the Page spec loads with `seed "cases/page_tree"`. The step adds the `oaken` gem (in every group, since `db/seeds.rb` now calls `Oaken.seed`, which also makes these seeds available to `bin/rails db:seed`) and `require "oaken/rspec_setup"` in `spec/rails_helper.rb`, which seeds the database before the suite. The specs check Trek's behaviour inside the app, so a customization that breaks it fails the app's own suite; edit or remove the ones covering behaviour you deliberately changed. Trek's own suite runs the very same files against its dummy app, so they stay green with each Trek release. Oaken needs Ruby 3.2+. **Upgrade note:** run `rails g trek:install:model_specs` to add them (it skips the gem, the `db/seeds.rb` line and the `rails_helper` line when already there, and asks before overwriting an existing file), then `bundle exec rspec spec/models`. `oaken/rspec_setup` truncates every table and runs `db/seeds.rb` before the suite, so make sure your `db/seeds.rb` is fine to run in the test environment.
13
+ - The header's theme button is now a working theme switcher. It used to toggle `light-theme`/`dark-theme` on the page without saving the choice (a `TODO` logged it to the console), so the theme reset on the next page load. It now opens a menu offering **Auto**, **Light** and **Dark**, applies the choice at once and saves it to the user's `theme` through `PATCH /admin/users/theme`. The action answers with a turbo stream that refreshes the header icon. Logged-out pages (login, password reset) now render with `auto-theme` instead of `light-theme`. **Auto** follows the system setting (`prefers-color-scheme`) without JavaScript: a small PostCSS plugin in `postcss.config.js` copies Radix's `.dark-theme` variables into a `@media (prefers-color-scheme: dark)` block scoped to `.auto-theme`. `color-scheme` is now set for each theme too, so native controls and scrollbars follow it. The menu is a new `Trek::DropdownComponent` (a trigger slot plus `with_item` entries, with keyboard navigation), which host apps can reuse. **Upgrade note:** in `app/controllers/admin/users_controller.rb`, replace `head :no_content` in the `theme` action with `respond_to { |format| format.turbo_stream; format.html { head :no_content } }`, and add `app/views/admin/users/theme.turbo_stream.slim` from Trek's generator templates (or re-run `rails g trek:install:admin:users`). Without them the theme is still saved, but the header icon only updates on the next page load. For **Auto** to follow the system setting, copy the `autoDarkTheme` plugin from Trek's `postcss.config.js` into yours and add it to `plugins` right after `postcss-import`; without it, **Auto** renders the light theme.
14
+ - `phonable_attributes` accepts a `strictness:` option — `:possible` (the default, unchanged) or `:valid`. `:possible` only asks Phonelib whether the digits could form a number for the country; `:valid` asks whether they fall in a range the regulator has actually assigned. The gap bites on input errors: with a French default country, `"0012345678"` is possible, so it is accepted and normalized to `"+12345678"` — the stray leading zero read as an international prefix, the number silently rehomed to country code +1, and the original input gone since normalization happens on save. Apps that intend to call or text the numbers they collect want `phonable_attributes :phone, strictness: :valid`.
15
+ - Plain file inputs are now styled: inside a `Trek::Form::GroupComponent` (`f.group`), a standard `f.file_field` gets its native `::file-selector-button` styled like a `Trek::ButtonComponent` with `size: :narrow`, using the accent variables so `data-accent-color` applies as on buttons. The browser's built-in "file chosen" feedback is kept and no JS is involved — useful for one-off uploads (e.g. an .xlsx import) next to Trek's styled fields. The Shrine-backed `image_field`/`sound_field` are unaffected (their file inputs are visually hidden). **Upgrade note:** you can now drop custom `::file-selector-button` styles that mirrored Trek's buttons for plain file inputs.
16
+ - The confirm dialog (`Trek::DialogComponent` wired to `Turbo.config.forms.confirm`) now takes its title, confirm button label and button color per action from the confirming element's data attributes — `data-turbo-confirm-title`, `data-turbo-confirm-button` and `data-turbo-confirm-color` alongside `data-turbo-confirm` — falling back to the current delete wording ("Are you sure?" / "Delete" / tomato) when absent. A non-destructive confirm (a "Validate" flow, a scheduled job trigger) no longer shows a **Delete** button, and one page can host a delete confirm and an action confirm side by side. Works on form submit buttons and on `data-turbo-method` links (the clicked element is remembered, since Turbo only forwards `data-turbo-confirm` to the form it builds for a link). **Upgrade note:** you can now drop `content_for(:dialog)`-style overrides that swapped the whole dialog just to rename the button — pass the data attributes on the confirming button instead.
17
+ - `Trek::MenuComponent` items accept a `target:` option forwarded to the link — e.g. `s.with_item(text: …, href: …, target: "_blank")` for external tools mounted in the admin, like a GoodJob dashboard. A `_blank` item automatically gets `rel="noopener"` and the `trek/external` new-tab icon after its text, as the toast link already did. `Trek::Dashboard::TileComponent` (which already accepted `target:`/`rel:`) now also defaults `rel` to `noopener` when `target` is `_blank`. **Upgrade note:** you can now drop initializer patches that reopened `ItemComponent` to add a `target` option.
18
+ - `Trek::ButtonComponent` accepts a `name:` option, rendered on the plain-button branch (paired with `value:`) so a `type: :submit` button can carry a param — needed when a form has two submit actions with different outcomes (e.g. a "save" button next to a "save then preview" one submitting `then_preview`). **Upgrade note:** you can now drop initializer patches that reopened the component to add a `name` option.
19
+ - `rails g trek:install:appsignal`, a new installer step (also runnable alone) that sets up [AppSignal](https://appsignal.com) error tracking and performance monitoring, until now wired by hand in every app: it adds the `appsignal` gem, injects `APPSIGNAL_PUSH_API_KEY` into the Rails credentials (as the Postmark generator does with its API token — `bundle exec appsignal install` would write the key in plain text and needs a terminal), and writes a `config/appsignal.rb` that reads the key from the credentials, activates the agent in production as soon as a key exists, and ignores the `/healthcheck` action. It also wraps the boot in `config.ru` so a broken initializer or a missing credential is reported, and installs an `AppsignalContext` controller concern that sets the `admin`/`web` namespace and tags transactions with the acting user — included in `ApplicationController` and, through an initializer, in `Trek::BaseController`. `APPSIGNAL_APP_ENV` is added to `.env.example`; the rescued Postmark delivery in `ApplicationMailer` now reports through `Rails.error` rather than only logging, which keeps app code independent of the monitoring vendor. Check an install with `bundle exec appsignal diagnose`. **Upgrade note:** existing apps can run `rails g trek:install:appsignal` — it skips the gem and the credentials when AppSignal is already set up, but review the generated `config/appsignal.rb` against your own (it takes the push API key from the credentials, so drop a plain-text key or an `APPSIGNAL_PUSH_API_KEY` environment variable you no longer need), and remove your hand-written `AppsignalContext` equivalent if you have one.
20
+ - `bin/rails trek:attachments:promote_cached`, a rake task finding the records whose Shrine attachments still point to the temporary `cache` storage — for every model and attachment, whatever the uploader — and promoting those whose cached file still exists (backed by `Trek::CachedAttachments`). It prints one line per attachment with its status: `promoted`, `missing` when the cached file is gone (it has to be uploaded again), or `changed` when the record changed meanwhile; `DRY_RUN=1` only lists them, as `promotable`. Documented in the [uploaders reference](/reference/uploaders).
21
+
22
+ ### Changed
23
+
24
+ - Trek's dummy app now generates and serves the whole admin (authentication, authorization, the admin generator's controllers and layout, routes, the pages, fragments, users, sessions and password-reset panels, mailers), so request and component specs run against the real admin. Line coverage goes from 49% to 73%; the minimum is raised to 72%.
25
+ - Every Trek generator is now run by Trek's own suite (`spec/trek/generators_spec.rb`): on a fresh `rails new` app set up as in the Getting started guide, then a second time to check that re-running it changes nothing (host apps re-run generators when upgrading). Shell-outs (bundle, yarn, migrations, rubocop) are recorded rather than run; the few other gems' generators whose output a later step edits (`rails g model`, `action_policy:install`, `mobility:install`, `rspec:install`) run for real, in-process. `trek:admin:user` has its own spec against the dummy app's database. Line coverage goes from 35% to 49%; the minimum is raised to 49%.
26
+ - `trek:install:ruby` now pins **Ruby 4.0.7** (was 4.0.6), and the CI images (repo + generated `.gitlab-ci.yml` template) use the matching `4.0.7` tag. **Upgrade note:** existing apps can set `4.0.7` in `.ruby-version` and bump the `ci-ruby` image tag in `.gitlab-ci.yml`.
27
+ - Trek's own specs now run in random order (`--seed` replays an order, `--bisect` finds the spec leaking state), and no longer depend on it: `page_path_helper_spec` switched the default locale to French inside `I18n.with_locale`, which left `I18n.locale` set to `:fr` for the specs that ran after it, so `localization_spec` and `i18n_helper_spec` failed under some orders.
28
+ - Trek's own suite can now test code that needs Rails and a database. Specs that `require "rails_helper"` boot `spec/dummy`, a minimal host app on PostgreSQL. Its models, migrations and Shrine initializer are not copied by hand: they are produced on each boot by running Trek's own generator steps (`trek:install:page`, `:user`, `:authentication`, `:fragment`…), so they cannot drift from what `rails g trek:install` gives a real app, and a spec fails when a model or migration template is added without the suite generating it. First `User` and `Page` model specs come with it. Line coverage goes from 9% to 35%, most of it from the app's code now being loaded; the minimum is raised to 34%.
29
+ - Trek's own spec suite runs in CI again, in a `tests` job that had been commented out — so nothing flagged that `spec/helpers/trek/page_path_helper_spec.rb` required a `rails_helper` the repository does not have, which stopped the whole suite from loading. That spec is rewritten in the suite's isolated style (no Rails app, stubbed routes). `COVERAGE=1` turns on SimpleCov, which reports line and branch coverage over every Ruby file in `app/` and `lib/` (generator templates excluded), writes a Cobertura report for GitLab's merge-request diff coverage, and fails the run below a minimum line coverage, currently 9%, to be raised as coverage grows.
30
+ - Trek is now developed and tested against **Shrine 3.10** (was 3.6). The gemspec constraint stays `~> 3.0`, so this changes nothing for host apps by itself — they already resolve 3.10 on Ruby ≥ 3.2 — but Trek's own suite and the generated app in CI no longer run against a version nobody installs. Worth picking up: 3.8 fixes a path traversal outside the storage directory in the `file_system` storage (the development and test storage), 3.7 stops `column` from failing on an empty string, 3.9 fixes `remove_invalid` leaving the attribute set after an invalid file is deassigned and stops the `activerecord` plugin from deleting the original attachment of a duplicated record. Shrine 3.7 dropped Ruby < 3.2, so an app still on Ruby 3.1 keeps resolving 3.6. **Upgrade note:** run `bundle update shrine` (nothing else to change).
31
+
32
+ ### Fixed
33
+
34
+ - A signed-in user opening what their role doesn't allow (an editor on the users panel, or on another user's profile) got a 500 error page: `ActionPolicy::Unauthorized` was raised and handled nowhere. `Trek::Authorization` now rescues it: back to the previous page, or to the dashboard, with a translated alert (`notices.unauthorized`); Turbo Stream and JSON requests get a 403.
35
+ - The new fragment form (and any fragment saved without content) raised `undefined method '[]' for nil`: the generated `fragments` migration created `content` as a jsonb column without the `{}` default its translated `title` has, and Mobility's jsonb backend reads a translation as `column[locale]`. The migration now defaults `content` to `{}`. **Upgrade note:** in apps generated before this version, add a migration with `change_column_default :fragments, :content, from: nil, to: {}` and `up_only { execute "UPDATE fragments SET content = '{}' WHERE content IS NULL" }`.
36
+ - The generated `Admin::ExternalLinkPolicy` scoped external links for non-privileged users with `relation.where(user:)`, a leftover from an example: `external_links` has no `user` column, so applying the scope raised a SQL error. Non-privileged users now get none, as in Trek's other policies. **Upgrade note:** in `app/policies/admin/external_link_policy.rb`, replace `relation.where(user:)` with `relation.none`.
37
+ - With `rails g trek:taxonomies`, asking whether a tag could be deleted (`Admin::TagPolicy#destroy?`) raised `HasManyThroughAssociationPolymorphicSourceError`: it called `Tag#taggables`, a `has_many :taggables, through: :taggings` that Rails can't build over the polymorphic `Tagging#taggable`. The policy now checks `taggings`, and `Tag#taggables` is a method returning the tagged records. **Upgrade note:** in `app/models/tag.rb`, replace `has_many :taggables, through: :taggings` with `def taggables = taggings.includes(:taggable).map(&:taggable)`, and in `app/policies/admin/tag_policy.rb`, replace `record.taggables.empty?` with `record.taggings.empty?`.
38
+ - The docs and README still described [Sorcery](https://github.com/Sorcery/sorcery) as Trek's authentication, and the `User` reference listed its `crypted_password` column. Trek authenticates with Rails' `has_secure_password` and database-backed `UserSession` records; the Getting started guide, the introduction, the home page, the customization guide, the installer and `User` references now say so, and the `User` columns table lists `password_digest` and `last_activity_at`. The README's ClosureTree link, which pointed to Sorcery, now points to ClosureTree.
39
+ - Deleting a page (or a page version) raised `NameError: Missing model class Section for the Page#sections association`: `Trek::Sectionable`, included in the generated `Page` and `PageVersion`, declared `has_many :sections, dependent: :destroy`, but no generator creates a `Section` model. The association is now declared only when the app defines one, so apps with their own sections keep them.
40
+ - Editing a page raised `undefined method 'page_path' for an instance of Trek::HeaderComponent`: the admin header's "See on website" link called `page_path`, which components don't include. The header now includes `Trek::PagePathHelper`, and leaves the link out when the app has no front-office route for the page (a freshly generated app has none), instead of raising.
41
+ - Image uploads from the admin returned 404: `trek:install:admin:routes` wrote `post "upload", to: "uploads#image", namespace: :admin` at the top of the routes (the `namespace:` ended up inside the route string), while image fields post to `/admin/upload/image`. It now writes `post "upload/image", to: "uploads#image"` inside `namespace :admin`. **Upgrade note:** in `config/routes.rb`, replace `post "upload", to: "uploads#image", namespace: :admin` with `post "upload/image", to: "uploads#image"` inside your `namespace :admin do … end` block.
42
+ - Trek's components use `Turbo::FramesHelper`, but the gemspec didn't declare `turbo-rails`: it only worked because `rails new` adds it by default. Trek now depends on `turbo-rails` (>= 1.0) and requires it.
43
+ - `Trek::Users::Validations` only enforced the 8-character password minimum when a user was created: the check for a later change looked at `crypted_password`, a column from Trek's former authentication library that `has_secure_password` users don't have, so an existing user could set a 1-character password (e.g. through the password reset form). It now also applies whenever `password_digest` changes, and still skips saves that leave the password alone. The generated `spec/models/user_spec.rb` covers both cases. **Upgrade note:** nothing to change; to get the new examples in an existing app, re-run `rails g trek:install:model_specs` and accept overwriting `spec/models/user_spec.rb` (or copy the two examples).
44
+ - `rails g trek:upgrade:v2` left two blank lines in `.yarnrc.yml` where it removed the `git.etamin.studio` registry, right before the `npmPreapprovedPackages` block it adds, so running it again changed the file. It now leaves a single one.
45
+ - `rails g trek:scaffold` wrote an invalid `config/locales/models/<model>.en.yml`: the attribute labels ran together on one line (`name: Name description: Description`), so Rails failed to load the translations (`Psych::SyntaxError: mapping values are not allowed in this context`) and every page rendering a translation raised. Each attribute is on its own line again. Trek's CI now generates its test app the way the Getting started guide does (`rails new -j esbuild`) and runs that app's own suite (`bin/rails spec`) after `trek:install` and `trek:scaffold`, which is how this surfaced. **Upgrade note:** in models scaffolded before this version, put each attribute of `config/locales/models/*.en.yml` on its own line.
46
+ - Freshly generated apps got a broken `db/schema.rb`: they resolve `json` 3, whose `JSON.parse` takes its options as keywords only, while ActiveSupport (up to 8.1.3.1, the latest release) passes them positionally. The schema dumper then fails on every jsonb column with a default and writes `# Could not dump table "pages" because of following ArgumentError` in place of `pages`, `page_paths`, `fragments` and `menu_nodes`, so the test database, loaded from `schema.rb`, lacks those tables and the app's specs cannot run. Trek now requires `json` < 3 until Rails supports json 3, and its suite dumps the generated schema to catch a regression. **Upgrade note:** run `bundle update trek` (it moves `json` back to 2.x if you had 3), then `bin/rails db:schema:dump` and check that `db/schema.rb` has no "Could not dump" comment left.
47
+ - Three generated migrations tripped RuboCop in a freshly generated app: `trek_tag_categories` and `trek_taggings` (from `rails g trek:taxonomies`) indented `create_table` by one space, and `trek_menu_nodes` ended with a trailing blank line. The templates are fixed, and Trek's suite now runs RuboCop on every generated migration. **Upgrade note:** apps generated before this version can run `bundle exec rubocop --autocorrect db/migrate` to fix them.
48
+ - `Trek::PagePathHelper#page_path` (and `page_url`) raised `NoMethodError: undefined method 'shop/product_path'` for a page attached to a namespaced pageable: the route name was built from `pageable.class.name.underscore`, which keeps the `/` of the namespace. It now uses `pageable.model_name.singular_route_key`, the key Rails' polymorphic routes use — `shop_product_path` for `Shop::Product`, and `post_path` for an isolated engine's `Blog::Post`. Non-namespaced pageables are unaffected.
49
+ - `Trek::PagePathHelper#page_url` returned a path, not a URL, for a page with a named route (a keyed page such as `about`): that branch always called `#{key}_path` and ignored the `url` suffix the other branches honour. It now calls `about_url`, so mailers, sitemaps and canonical links built with `page_url` get an absolute URL. **Upgrade note:** you can now drop workarounds that made `page_url` absolute for keyed pages (e.g. prefixing `request.base_url`).
50
+ - `trek:install:scalingo` pinned the buildpack with `BUILDPACK_URL=https://github.com/etaminstudio/trek-buildpack#v0.1.0`, and Scalingo resolves a `#ref` suffix as a **branch**: the tag was looked up as `archive/refs/heads/v0.1.0`, which does not exist, and every build failed with `fail to download and extract the custom buildpack: Invalid status code: 404 Not Found`. The generator and the [Deploying to Scalingo](https://trek.etamin.studio/deploy) guide now point at the release archive, `https://github.com/etaminstudio/trek-buildpack/archive/refs/tags/v0.1.0.tar.gz`, which Scalingo downloads as is and which stays pinned to the release. **Upgrade note:** on each Scalingo app, run `scalingo --app <app> env-set BUILDPACK_URL=https://github.com/etaminstudio/trek-buildpack/archive/refs/tags/v0.1.0.tar.gz` — an app still on the `#v0.1.0` form cannot deploy, and nothing surfaces it until the next deploy fails.
51
+ - The English locale gave the content editor's prompt panel its target labels in French ("un nouvel onglet" / "le même onglet"): `admin.content_editor.prompt.targets` had been copied from `trek.fr.yml` rather than translated. An English-locale admin inserting a Turbo-frame prompt now reads "a new tab" / "the same tab", as the link panel right above already did.
52
+ - The generated `trek_users` migration tripped RuboCop's `Rails/NotNullColumn` on `add_column :users, :password_digest, :string, null: false`, so `make lint` (and the `lint` CI job) failed in a freshly generated app — the installer's own closing `make fix` already exited non-zero on it. The column is fine as it stands: the preceding `trek_create_users` migration creates the table empty, so there is nothing to backfill and no default is wanted on a password digest. The line now carries an inline `rubocop:disable Rails/NotNullColumn` with a comment saying why. **Upgrade note:** apps generated before this version can add the same disable comment to `db/migrate/*_trek_users.rb`.
53
+ - The generated `.slim-lint.yml` listed an `AllCops:` key (`NewCops: disable`) among its linters. slim-lint validates linter names since 0.34 ("Add check for invalid keys in config files"), so `make lint` in a freshly generated app died with `No such linter: AllCops. Available: CommentControlStatement, …` before checking a single template; Trek's own lockfile pins 0.33, where the unknown key was ignored, which is why it went unnoticed. The key is gone — it configured nothing (the embedded RuboCop is configured under the `RuboCop` linter's `ignored_cops`). **Upgrade note:** remove the two `AllCops:` / `NewCops: disable` lines from your `.slim-lint.yml` if it still has them.
54
+ - The generated `config/appsignal.rb` now sets `config.log_path = "/tmp"` in production, so the AppSignal agent stops announcing `appsignal: Unable to log to '/app/log'. Logging to '/tmp' instead.` on every boot and every one-off container. Hosts with a read-only filesystem (Scalingo, Heroku and the like) cannot let the agent write its diagnostic log next to the app, and it falls back to `/tmp` by itself — nothing was lost, but the message reads like a misconfiguration. **Upgrade note:** add `config.log_path = "/tmp" if Rails.env.production?` to a `config/appsignal.rb` written before this version, or regenerate it with `rails g trek:install:appsignal`.
55
+ - `Trek::Phonable#normalize_phone` normalized every declared attribute from the value of `phone`: a model declaring `phonable_attributes :phone, :fax` — the concern's own documented example — overwrote the fax with the phone number on every save, and wiped it when the phone was blank. Each attribute is now parsed from its own value. Models declaring a single attribute are unaffected.
56
+ - `Trek::ButtonComponent`'s `disabled` option is now actually rendered — it was declared but ignored by every branch, so a "disabled" button stayed clickable with no visual cue. A disabled button renders `<button disabled>`; a disabled link (`href:` or `to:`) renders an inert anchor — no `href`, no Turbo method — with `aria-disabled="true"`, since `<a>` has no `disabled` attribute. Both get a dimmed, non-interactive style (`opacity: 0.5`, `pointer-events: none`, `cursor: not-allowed`). **Upgrade note:** you can now drop `classnames: "is-disabled"`-style workarounds and their CSS, and pass `disabled:` with a real `href` instead of blanking the href conditionally.
57
+ - The icon of a `Trek::ButtonComponent` no longer shrinks next to a long label: as a flex item it was squeezed (and, with a `min-width`, distorted) when the text ran out of room. It keeps its size, the text wraps or ellipsises as before. **Upgrade note:** you can now drop a `.c-trek--button-icon { flex-shrink: 0 }` override.
58
+ - The login, forgotten-password and password-reset forms labelled their fields with the humanized attribute name ("Email", "Password") whatever the locale. They post to a URL rather than to a record, so `Trek::Form::GroupComponent` had no model to resolve `activerecord.attributes.user.*` against and fell back to the attribute name; the translations shipped in Trek's locales were never looked up. The three generated views now pass `model_name: User.model_name` to `f.group`, which drives the i18n lookup alone and leaves field names (and therefore the params the controllers read) untouched. **Upgrade note:** views generated before this version keep the old labels — add `model_name: User.model_name` to the `f.group` calls in `app/views/admin/user_sessions/new.html.slim` and `app/views/admin/user_password_resets/{new,edit}.html.slim`, or regenerate them.
59
+ - The toast link's new-tab icon rendered as an empty `<svg><use></svg>`: `Trek::Toaster::ToastComponent::LinkComponent` referenced an `external` icon that didn't ship with the gem (it only resolved when the host app provided its own `app/assets/icons/external.svg`). Trek now bundles `trek/external` ([Lucide](https://lucide.dev)'s `external-link`, ISC) and the toast link uses it; the icon is also available to host apps as `Trek::IconComponent.new("trek/external")`. **Upgrade note:** you can now drop an `app/assets/icons/external.svg` that only existed to back the toast link (keep it if your own code renders the bare `external` key).
60
+ - `Trek::MenuComponent` items pointing at a namespaced model doubled the namespace in the path used for the active-state check (`href: [:admin, :missive, Missive::List]` compared against `admin/missive/missive/lists`), so those items never got `is-active`. The namespace segments already covered by the model's collection are now deduplicated, and the item highlights on its own controller. **Upgrade note:** you can now drop `active:` overrides that only worked around this (e.g. `active: "admin/#{model.model_name.collection}"`).
61
+ - `make release` now chains its steps with `&&`, so a failing step halts the run. Previously the steps were separated by `;`: when `rake release` failed (e.g. RubyGems asking for an MFA code), `npm publish` still ran and its output buried the error, leaving the gem unpublished while the tag and npm package were out.
62
+ - `make release` prompts for the RubyGems and npm one-time passwords right before each publish step (passed as `GEM_HOST_OTP_CODE` and `npm publish --otp`), so accounts with MFA no longer fail the push; leave a prompt empty when MFA is off for that registry.
63
+ - A file attached to a record on a numerically ordered closure tree — the generated `Page` and its image — stayed in Shrine's temporary `cache` storage forever when attached on create or together with a parent change. closure_tree reloads the record in its `after_save` callback to reorder siblings, and Shrine's `activerecord` plugin resets the attacher on reload, so the `after_commit` promotion found nothing to promote: the record kept `"storage": "cache"` data, and its derivation URLs broke with "Source file not found" as soon as the cached file was deleted. Updating an existing page's image was unaffected. `Trek::SavedAttachmentsReload`, a Shrine plugin loaded by the generated `RecordImageUploader`, now keeps across a reload the attachers whose file was saved with the record (an unsaved attachment is still discarded), so the file is promoted and the replaced one deleted after commit. **Upgrade note:** add `plugin Trek::SavedAttachmentsReload` under `plugin :activerecord` in `app/uploaders/record_image_uploader.rb` (it covers every model using that uploader, not only Trek's). Records saved before that may still point to cached files — run `bin/rails trek:attachments:promote_cached` to promote those whose file still exists, and have the ones it reports as `missing` uploaded again.
64
+
65
+ ## [2.2.0] - 2026-08-28
66
+
67
+ ### Added
68
+
69
+ - `Trek::Dashboard::ChartComponent`, a dashboard card wrapping [Chartkick](https://chartkick.com) — pass `title`, `data`, a `type` (`:line_chart`, `:bar_chart`, …), optional `info`/`options`, and a `color` (default `:slate`); it renders `public_send(type, data, **options)` inside a coloured card matching the tiles. Chartkick is an **optional** dependency: the component only includes `Chartkick::Helper` when it is loaded, so Trek keeps working without it. `Trek::Dashboard::AnalyticsComponent` gained a `renders_many :charts` slot (alongside the existing `section`) so charts can be dropped straight into the dashboard with `a.with_chart(...)`. **Upgrade note:** to use `ChartComponent`, add `gem "chartkick"` to your app and `import "chartkick/chart.js"` in your admin JS bundle (Trek does not pull Chartkick in for you).
70
+ - `Trek::Dashboard::TileComponent` gained a `title` option (a large heading above the text, coloured per the tile's `color`) and a `wide` option (default `false`) that spans two tile columns. Its `href` is now optional: without one the tile renders as a plain `div` instead of a link, for read-only stat tiles.
71
+ - The layout gains a **stacked panel**: a second panel frame (`trek--layout-stacked-panel`) that opens on top of the primary one. The content editor's link/image/prompt inserters now open there, so an editor hosted _inside_ a panel (e.g. a Sectionable payload edited in a side panel) no longer loses its unsaved content when inserting a link — the primary panel stays put underneath and the inserted element lands back in the live editor. Closing the primary panel also closes the stacked one; _Annuler_ in the stacked panel closes only that one. The stacked panel is 1rem narrower than the primary panel (only while it stacks on an open one). **Upgrade note:** host apps that override the panel views (`app/views/<engine>/panels/{links,images,prompts}/*`) must retarget them to the `trek--layout-stacked-panel` frame and the `stackedPanelCloser` target, as Trek's own views now do.
72
+
73
+ ### Fixed
74
+
75
+ - Internal links inserted through the content editor (stored as `gid://` URLs) rendered as literal `gid://…` hrefs — or were dropped by the sanitizer — in formatted content: `Prosemirror#format_content` now resolves GlobalIDs to page paths before rendering, and the editor's Link extension accepts the `gid` protocol so reopening such a link shows the linked page instead of an empty field.
76
+ - `Trek::Form::ActionsComponent`: the sticky footer styling (sticky positioning, `--accent-1` background, `4rem` vertical padding) now only applies to the `default` aspect. It was set on the root rule, so the `seamless` aspect — used by `Trek::DialogComponent` — was also sticky and padded inside dialogs.
77
+
3
78
  ## [2.1.0] - 2026-08-25
4
79
 
5
80
  ### Added
data/Gemfile CHANGED
@@ -22,6 +22,10 @@ group :development, :test do
22
22
  end
23
23
 
24
24
  group :test do
25
+ gem "bcrypt", "~> 3.1"
25
26
  gem "capybara", "~> 3.38"
27
+ gem "oaken", "~> 1.0"
28
+ gem "pg", "~> 1.5"
26
29
  gem "simplecov", "~> 0.22.0"
30
+ gem "simplecov-cobertura", "~> 3.0"
27
31
  end
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- trek (2.1.0)
4
+ trek (2.3.0)
5
5
  action_policy (~> 0.6)
6
6
  actioncable
7
7
  acts_as_list (~> 1.1)
@@ -14,6 +14,7 @@ PATH
14
14
  http_accept_language (~> 2.1)
15
15
  i18n-tasks (~> 1.0)
16
16
  image_processing (~> 1.8)
17
+ json (< 3)
17
18
  kaminari
18
19
  listen
19
20
  lookbook (>= 2.3.13)
@@ -28,6 +29,7 @@ PATH
28
29
  shrine-blurhash (~> 0.2)
29
30
  slim-rails (~> 4.0)
30
31
  strip_attributes (~> 2.0)
32
+ turbo-rails (>= 1.0)
31
33
  view_component (< 5.0)
32
34
  view_component-form (~> 0.3)
33
35
  zeitwerk (~> 2.6)
@@ -137,6 +139,7 @@ GEM
137
139
  aws-sigv4 (1.12.1)
138
140
  aws-eventstream (~> 1, >= 1.0.2)
139
141
  base64 (0.3.0)
142
+ bcrypt (3.1.22)
140
143
  bigdecimal (4.1.2)
141
144
  blurhash (0.1.8)
142
145
  brakeman (7.1.2)
@@ -169,8 +172,9 @@ GEM
169
172
  deepl-rb (3.6.1)
170
173
  diff-lcs (1.6.2)
171
174
  docile (1.4.1)
172
- down (5.4.2)
175
+ down (5.6.0)
173
176
  addressable (~> 2.8)
177
+ base64 (~> 0.3)
174
178
  drb (2.2.3)
175
179
  dry-configurable (1.3.0)
176
180
  dry-core (~> 1.1)
@@ -302,10 +306,13 @@ GEM
302
306
  racc (~> 1.4)
303
307
  nokogiri (1.19.4-x86_64-darwin)
304
308
  racc (~> 1.4)
309
+ oaken (1.0.0)
305
310
  parallel (1.27.0)
306
311
  parser (3.3.10.0)
307
312
  ast (~> 2.4.1)
308
313
  racc
314
+ pg (1.6.3-arm64-darwin)
315
+ pg (1.6.3-x86_64-darwin)
309
316
  phonelib (0.10.15)
310
317
  postmark (1.25.1)
311
318
  json
@@ -457,7 +464,7 @@ GEM
457
464
  ffi (~> 1.12)
458
465
  logger
459
466
  securerandom (0.4.1)
460
- shrine (3.6.0)
467
+ shrine (3.10.0)
461
468
  content_disposition (~> 1.0)
462
469
  down (~> 5.1)
463
470
  shrine-blurhash (0.2.3)
@@ -467,6 +474,9 @@ GEM
467
474
  docile (~> 1.1)
468
475
  simplecov-html (~> 0.11)
469
476
  simplecov_json_formatter (~> 0.1)
477
+ simplecov-cobertura (3.2.0)
478
+ rexml
479
+ simplecov (~> 0.19)
470
480
  simplecov-html (0.13.2)
471
481
  simplecov_json_formatter (0.1.4)
472
482
  slim (5.2.1)
@@ -491,6 +501,9 @@ GEM
491
501
  tilt (2.6.1)
492
502
  timeout (0.6.1)
493
503
  tsort (0.2.0)
504
+ turbo-rails (2.0.23)
505
+ actionpack (>= 7.1.0)
506
+ railties (>= 7.1.0)
494
507
  tzinfo (2.0.6)
495
508
  concurrent-ruby (~> 1.0)
496
509
  unicode-display_width (3.2.0)
@@ -528,9 +541,12 @@ PLATFORMS
528
541
  x86_64-darwin-24
529
542
 
530
543
  DEPENDENCIES
544
+ bcrypt (~> 3.1)
531
545
  brakeman (~> 7.0)
532
546
  bundler-audit (~> 0.9)
533
547
  capybara (~> 3.38)
548
+ oaken (~> 1.0)
549
+ pg (~> 1.5)
534
550
  rake (~> 13.0)
535
551
  reek (~> 6.1)
536
552
  rspec-html-matchers (~> 0.10)
@@ -541,6 +557,7 @@ DEPENDENCIES
541
557
  rubocop-rails-omakase (~> 1.1)
542
558
  rubocop-rspec (~> 3.5)
543
559
  simplecov (~> 0.22.0)
560
+ simplecov-cobertura (~> 3.0)
544
561
  slim_lint (~> 0.24)
545
562
  trek!
546
563
 
data/Makefile CHANGED
@@ -15,15 +15,17 @@ fix:
15
15
 
16
16
  release:
17
17
  @read -p "New version (current: $$(sed -n 's/.*VERSION = "\(.*\)"/\1/p' lib/trek/version.rb)): " version; \
18
- if [ -z "$$version" ]; then echo "Version cannot be empty"; exit 1; fi; \
19
- sed -i '' "s/VERSION = \".*\"/VERSION = \"$$version\"/" lib/trek/version.rb; \
20
- sed -i '' "s/\"version\": \".*\"/\"version\": \"$$version\"/" package.json; \
21
- awk -v v="$$version" -v d="$$(date +%F)" '!done && /^## \[Unreleased\]/ {print; print ""; print "## [" v "] - " d; done=1; next} {print}' CHANGELOG.md > CHANGELOG.md.tmp && mv CHANGELOG.md.tmp CHANGELOG.md; \
22
- bundle; \
23
- git add -A; \
24
- git commit -m "Version $$version"; \
25
- bundle exec rake release; \
26
- npm publish
18
+ if [ -z "$$version" ]; then echo "Version cannot be empty"; exit 1; fi && \
19
+ sed -i '' "s/VERSION = \".*\"/VERSION = \"$$version\"/" lib/trek/version.rb && \
20
+ sed -i '' "s/\"version\": \".*\"/\"version\": \"$$version\"/" package.json && \
21
+ awk -v v="$$version" -v d="$$(date +%F)" '!done && /^## \[Unreleased\]/ {print; print ""; print "## [" v "] - " d; done=1; next} {print}' CHANGELOG.md > CHANGELOG.md.tmp && mv CHANGELOG.md.tmp CHANGELOG.md && \
22
+ bundle && \
23
+ git add -A && \
24
+ git commit -m "Version $$version" && \
25
+ read -p "RubyGems OTP code (leave empty if MFA is off): " gem_otp && \
26
+ env $${gem_otp:+GEM_HOST_OTP_CODE=$$gem_otp} bundle exec rake release && \
27
+ read -p "npm OTP code (leave empty if MFA is off): " npm_otp && \
28
+ npm publish $${npm_otp:+--otp=$$npm_otp}
27
29
 
28
30
  audit-fix:
29
31
  @out=$$(bundle exec bundle-audit check --update 2>&1); \
data/README.md CHANGED
@@ -16,7 +16,7 @@ Trek is a modern content management system for Ruby on Rails, that is designed t
16
16
  Trek uses these gems under the hood:
17
17
 
18
18
  - [ActionPolicy](https://github.com/palkan/action_policy) for authorization
19
- - [ClosureTree](https://github.com/Sorcery/sorcery) for hierarchies
19
+ - [ClosureTree](https://github.com/ClosureTree/closure_tree) for hierarchies
20
20
  - [Postmark](https://github.com/ActiveCampaign/postmark-rails) for sending emails
21
21
  - [rails-i18n](https://github.com/svenfuchs/rails-i18n), [mobility](https://github.com/shioyama/mobility) and [i18n-tasks](https://github.com/glebm/i18n-tasks) for internationalization
22
22
  - [RSpec](https://github.com/rspec/rspec-rails) for testing
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 18a2 2 0 0 0-4 0"/><path d="m19 11-2.11-6.657a2 2 0 0 0-2.752-1.148l-1.276.61A2 2 0 0 1 12 4H8.5a2 2 0 0 0-1.925 1.456L5 11"/><path d="M2 11h20"/><circle cx="17" cy="18" r="3"/><circle cx="7" cy="18" r="3"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-chevron-down-icon lucide-chevron-down"><path d="m6 9 6 6 6-6"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-chevron-up-icon lucide-chevron-up"><path d="m18 15-6-6-6 6"/></svg>
@@ -0,0 +1 @@
1
+ <svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M15 3h6v6" fill="none" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path><path d="M10 14 21 3" fill="none" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" fill="none" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M6 18H4a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2"/><path d="M6 9V3a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1v6"/><rect x="6" y="14" width="12" height="8" rx="1"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-sun-moon-icon lucide-sun-moon"><path d="M12 2v2"/><path d="M14.837 16.385a6 6 0 1 1-7.223-7.222c.624-.147.97.66.715 1.248a4 4 0 0 0 5.26 5.259c.589-.255 1.396.09 1.248.715"/><path d="M16 12a4 4 0 0 0-4-4"/><path d="m19 5-1.256 1.256"/><path d="M20 12h2"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-moon-icon lucide-moon"><path d="M20.985 12.486a9 9 0 1 1-9.473-9.472c.405-.022.617.46.402.803a6 6 0 0 0 8.268 8.268c.344-.215.825-.004.803.401"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-sun-medium-icon lucide-sun-medium"><circle cx="12" cy="12" r="4"/><path d="M12 3v1"/><path d="M12 20v1"/><path d="M3 12h1"/><path d="M20 12h1"/><path d="m18.364 5.636-.707.707"/><path d="m6.343 17.657-.707.707"/><path d="m5.636 5.636.707.707"/><path d="m17.657 17.657.707.707"/></svg>
@@ -7,6 +7,19 @@ html {
7
7
  font-size: 62.5%; /* for easy rem sizing: 1rem = 10px */
8
8
  }
9
9
 
10
+ /* let native UI (form controls, scrollbars) follow the theme */
11
+ .light-theme {
12
+ color-scheme: light;
13
+ }
14
+
15
+ .dark-theme {
16
+ color-scheme: dark;
17
+ }
18
+
19
+ .auto-theme {
20
+ color-scheme: light dark;
21
+ }
22
+
10
23
  /* allow modules to change the box-sizing */
11
24
  *,
12
25
  *::before,
@@ -35,7 +35,16 @@
35
35
  border-color: var(--accent-7);
36
36
  }
37
37
 
38
+ .root:disabled,
39
+ .root[aria-disabled="true"] {
40
+ cursor: not-allowed;
41
+ opacity: 0.5;
42
+ pointer-events: none;
43
+ }
44
+
45
+ /* Keeps its size next to a long label, which would otherwise squeeze it. */
38
46
  .icon {
47
+ flex-shrink: 0;
39
48
  margin-right: 1.2rem;
40
49
  }
41
50
 
@@ -10,6 +10,7 @@ module Trek
10
10
  option :form_class, optional: true
11
11
  option :href, optional: true
12
12
  option :method, optional: true
13
+ option :name, optional: true
13
14
  option :params, optional: true
14
15
  option :position, type: proc(&:to_sym), optional: true
15
16
  option :rel, optional: true
@@ -50,7 +51,16 @@ module Trek
50
51
  end
51
52
 
52
53
  def call
53
- if link?
54
+ if disabled && (link? || to?)
55
+ # <a> has no disabled attribute: render an inert anchor — no href, no
56
+ # turbo method — flagged for assistive technologies.
57
+ content_tag :a,
58
+ rendered_content,
59
+ class: classes,
60
+ data: data_attributes,
61
+ title: title,
62
+ "aria-disabled": true
63
+ elsif link?
54
64
  link_to rendered_content,
55
65
  href,
56
66
  class: classes,
@@ -72,7 +82,9 @@ module Trek
72
82
  rendered_content,
73
83
  class: classes,
74
84
  data: data_attributes,
85
+ disabled:,
75
86
  form:,
87
+ name:,
76
88
  type:,
77
89
  value:
78
90
  end
@@ -1,2 +1,4 @@
1
1
  div(class=root_class)
2
2
  div(class=class_for("info")) = section
3
+ - charts.each do |chart|
4
+ = chart
@@ -3,5 +3,6 @@
3
3
  module Trek
4
4
  class Dashboard::AnalyticsComponent < Trek::Component
5
5
  renders_one :section, Trek::Dashboard::SectionComponent
6
+ renders_many :charts, Trek::Dashboard::ChartComponent
6
7
  end
7
8
  end