cafe_car 0.1.1 → 0.1.2

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 (126) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +155 -40
  3. data/Rakefile +9 -1
  4. data/app/assets/fonts/Lexend.css +7 -0
  5. data/app/assets/fonts/Lexend.ttf +0 -0
  6. data/app/assets/stylesheets/cafe_car/themes/defaults.css +58 -59
  7. data/app/assets/stylesheets/cafe_car/tooltips.css +20 -0
  8. data/app/assets/stylesheets/cafe_car/utility.css +1 -2
  9. data/app/assets/stylesheets/cafe_car.css +17 -6
  10. data/app/assets/stylesheets/ui/Alert.css +2 -1
  11. data/app/assets/stylesheets/ui/Button.css +6 -6
  12. data/app/assets/stylesheets/ui/Card.css +7 -3
  13. data/app/assets/stylesheets/ui/Chat.css +33 -0
  14. data/app/assets/stylesheets/ui/Close.css +11 -0
  15. data/app/assets/stylesheets/ui/Grid.css +2 -0
  16. data/app/assets/stylesheets/ui/Icon.css +3 -3
  17. data/app/assets/stylesheets/ui/Layout.css +20 -13
  18. data/app/assets/stylesheets/ui/Modal.css +5 -12
  19. data/app/assets/stylesheets/ui/Navigation.css +13 -5
  20. data/app/assets/stylesheets/ui/Page.css +42 -3
  21. data/app/assets/stylesheets/ui/Table.css +27 -56
  22. data/app/assets/stylesheets/ui/components.css +2 -0
  23. data/app/controllers/cafe_car/examples_controller.rb +1 -1
  24. data/app/controllers/cafe_car/sessions_controller.rb +30 -0
  25. data/app/controllers/concerns/cafe_car/authentication.rb +61 -0
  26. data/app/javascript/cafe_car.js +16 -11
  27. data/app/models/cafe_car/session.rb +18 -0
  28. data/app/policies/cafe_car/session_policy.rb +19 -0
  29. data/app/presenters/cafe_car/active_storage/attachment_presenter.rb +5 -4
  30. data/app/presenters/cafe_car/code_presenter.rb +18 -0
  31. data/app/presenters/cafe_car/date_presenter.rb +1 -0
  32. data/app/presenters/cafe_car/date_time_presenter.rb +2 -2
  33. data/app/presenters/cafe_car/enumerable_presenter.rb +1 -1
  34. data/app/presenters/cafe_car/hash_presenter.rb +3 -8
  35. data/app/presenters/cafe_car/presenter.rb +22 -12
  36. data/app/presenters/cafe_car/string_presenter.rb +2 -2
  37. data/app/ui/cafe_car/ui/button.rb +2 -1
  38. data/app/ui/cafe_car/ui/card.rb +18 -0
  39. data/app/ui/cafe_car/ui/grid.rb +30 -0
  40. data/app/ui/cafe_car/ui/layout.rb +7 -0
  41. data/app/ui/cafe_car/ui/page.rb +5 -1
  42. data/app/views/application/_body.html.haml +2 -1
  43. data/app/views/application/_controls.html.haml +1 -0
  44. data/app/views/application/_debug.html.haml +9 -2
  45. data/app/views/application/_errors.html.haml +4 -8
  46. data/app/views/application/_grid.html.haml +1 -1
  47. data/app/views/application/_grid_item.html.haml +1 -1
  48. data/app/views/application/_head.html.haml +1 -0
  49. data/app/views/application/_index.html.haml +6 -2
  50. data/app/views/application/_index_actions.html.haml +3 -3
  51. data/app/views/application/_navigation.html.haml +7 -0
  52. data/app/views/application/_navigation_links.html.haml +1 -1
  53. data/app/views/application/_notes.html.haml +1 -0
  54. data/app/views/application/_popup.html.haml +7 -0
  55. data/app/views/cafe_car/application/edit.html.haml +1 -1
  56. data/app/views/cafe_car/application/edit.turbo_stream.haml +3 -5
  57. data/app/views/cafe_car/application/index.html.haml +3 -0
  58. data/app/views/cafe_car/application/new.turbo_stream.haml +5 -6
  59. data/app/views/cafe_car/application/show.html.haml +2 -2
  60. data/app/views/cafe_car/examples/ui/_chat.html.haml +3 -0
  61. data/app/views/cafe_car/examples/ui/_info_circle.html.haml +1 -1
  62. data/app/views/cafe_car/examples/ui/_modal.html.haml +1 -1
  63. data/app/views/passwords_mailer/reset.html.haml +5 -0
  64. data/app/views/passwords_mailer/reset.text.erb +4 -0
  65. data/app/views/ui/_card.html.haml +6 -11
  66. data/app/views/ui/_field.html.haml +1 -7
  67. data/app/views/ui/_modal_close.html.haml +1 -2
  68. data/app/views/ui/_page.html.haml +6 -12
  69. data/config/brakeman.ignore +3 -3
  70. data/config/locales/en.yml +10 -2
  71. data/config/routes.rb +5 -1
  72. data/db/migrate/20251005220017_create_slugs.rb +2 -2
  73. data/lib/cafe_car/active_record.rb +1 -1
  74. data/lib/cafe_car/application_responder.rb +6 -0
  75. data/lib/cafe_car/attributes.rb +1 -1
  76. data/lib/cafe_car/auto_resolver.rb +1 -1
  77. data/lib/cafe_car/component.rb +102 -39
  78. data/lib/cafe_car/context.rb +5 -4
  79. data/lib/cafe_car/controller/filtering.rb +9 -1
  80. data/lib/cafe_car/controller.rb +52 -13
  81. data/lib/cafe_car/core_ext/array.rb +13 -0
  82. data/lib/cafe_car/core_ext/hash.rb +15 -0
  83. data/lib/cafe_car/core_ext/module.rb +15 -0
  84. data/lib/cafe_car/core_ext.rb +0 -2
  85. data/lib/cafe_car/current.rb +4 -1
  86. data/lib/cafe_car/engine.rb +9 -2
  87. data/lib/cafe_car/field_builder.rb +1 -1
  88. data/lib/cafe_car/field_info.rb +14 -12
  89. data/lib/cafe_car/fields.rb +7 -0
  90. data/lib/cafe_car/filter/field_info.rb +1 -1
  91. data/lib/cafe_car/filter/form_builder.rb +2 -2
  92. data/lib/cafe_car/filter_builder.rb +1 -1
  93. data/lib/cafe_car/form_builder.rb +1 -1
  94. data/lib/cafe_car/generators.rb +1 -1
  95. data/lib/cafe_car/helpers.rb +37 -10
  96. data/lib/cafe_car/href_builder.rb +35 -9
  97. data/lib/cafe_car/input_builder.rb +1 -1
  98. data/lib/cafe_car/link_builder.rb +14 -11
  99. data/lib/cafe_car/model.rb +2 -2
  100. data/lib/cafe_car/navigation.rb +10 -10
  101. data/lib/cafe_car/option_helpers.rb +11 -5
  102. data/lib/cafe_car/param_parser.rb +10 -6
  103. data/lib/cafe_car/policy.rb +2 -2
  104. data/lib/cafe_car/query_builder.rb +3 -3
  105. data/lib/cafe_car/resolver.rb +5 -1
  106. data/lib/cafe_car/routing.rb +1 -1
  107. data/lib/cafe_car/table/builder.rb +3 -2
  108. data/lib/cafe_car/table/head_builder.rb +2 -2
  109. data/lib/cafe_car/table/label_builder.rb +1 -1
  110. data/lib/cafe_car/table/row_builder.rb +5 -7
  111. data/lib/cafe_car/table_builder.rb +3 -3
  112. data/lib/cafe_car/ui.rb +2 -0
  113. data/lib/cafe_car/version.rb +1 -1
  114. data/lib/cafe_car/visitors.rb +2 -2
  115. data/lib/cafe_car.rb +25 -0
  116. data/lib/generators/cafe_car/controller/templates/controller.rb.tt +1 -1
  117. data/lib/generators/cafe_car/install/install_generator.rb +0 -1
  118. data/lib/generators/cafe_car/sessions/USAGE +17 -0
  119. data/lib/generators/cafe_car/sessions/sessions_generator.rb +29 -0
  120. data/lib/generators/cafe_car/sessions/templates/create_sessions.rb.tt +12 -0
  121. data/lib/tasks/holdco_tasks.rake +532 -0
  122. data/lib/tasks/templates/tasks_header.md +37 -0
  123. metadata +76 -48
  124. data/app/models/cafe_car/slug.rb +0 -3
  125. data/app/views/ui/_grid.html.haml +0 -17
  126. data/app/views/ui/_layout_menu.html.haml +0 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f8d0f3cafde61c20c26c71051639e723c59aa9a9c1e6d3e8ad33b3a7d97aec8d
4
- data.tar.gz: 315d84333c1c9a7bfe90b4ea7e58911cc5c3da3012f54ce942075f12863098ef
3
+ metadata.gz: fdfdd44f9f4f564e6fe25b17a694b0dd6dce990efdc30990d0cef474a98cc718
4
+ data.tar.gz: e483b2317f3a39b3b7c57b9c44bd20e3e766376f4889551bdc8ed199b8e6db52
5
5
  SHA512:
6
- metadata.gz: 390d873729793c6dd8ec53988a0d6fe68eefa31bebd5df7999a7faf1ab16d46d50cd14f35afe6d671e56c774d0a2366526fbe9f4f631139a85e076e1da789e46
7
- data.tar.gz: 4dc107cbc55e13033fbb5b7b9e55c50672ffa4ebe6eb8de23c6a523af0026ff4465207519e1bd250eb1958d50b379a70c4e308cd2ae8cbf6cb86b4a071aa6c3e
6
+ metadata.gz: daba4f7613586a3ff4f74e952800d3738965afafb81fbf1bfe82eec4b3ae5363c99d7aa879c1d3be438ff1ecdbf18d37d071307072487a1d2fb434dbfa3a6b62
7
+ data.tar.gz: '051968fadd32602c6b8884443200f030aeae9bcecb731c602161b854f0222e8197b65d568eadce9e1c45175cc66b64fab899d74f5df367df856f7c39e29bc8eb'
data/README.md CHANGED
@@ -1,24 +1,36 @@
1
1
  # CafeCar
2
2
 
3
- CafeCar is a Rails engine that extends the MVC "view" layer to provide automatic CRUD UI generation with sensible defaults. Its philosophy is rooted in the idea that Rails should render _something_ that represents the CRUD operations of your models by default. These defaults can then be expanded or overridden on either an application-wide or model-specific basis.
3
+ [![CI](https://img.shields.io/github/actions/workflow/status/craft-concept/cafe_car/ci.yml?branch=main&label=CI)](https://github.com/craft-concept/cafe_car/actions/workflows/ci.yml)
4
+ [![Gem Version](https://img.shields.io/gem/v/cafe_car)](https://rubygems.org/gems/cafe_car)
5
+ [![License: MIT](https://img.shields.io/badge/license-MIT-green)](https://opensource.org/licenses/MIT)
6
+
7
+ CafeCar is a Rails engine that extends the MVC "view" layer to provide automatic
8
+ CRUD UI generation with sensible defaults. Its philosophy is rooted in the idea
9
+ that Rails should render _something_ that represents the CRUD operations of your
10
+ models by default. These defaults can then be expanded or overridden on either
11
+ an application-wide or model-specific basis.
4
12
 
5
13
  **Perfect for**: Admin panels, internal tools, and rapid prototyping.
6
14
 
7
15
  ## Features
8
16
 
9
- - 🚀 **Auto-generated CRUD interfaces** - One line of code generates complete index, show, new, edit views
10
- - 🎨 **Component-based UI system** - Flexible, composable components for building interfaces
11
- - 🔐 **Built-in authorization** - Pundit integration for attribute-level permissions
17
+ - 🚀 **Auto-generated CRUD interfaces** - One line of code generates complete
18
+ index, show, new, edit views
19
+ - 🎨 **Component-based UI system** - Flexible, composable components for
20
+ building interfaces
21
+ - 🔐 **Built-in authorization** - Pundit integration for attribute-level
22
+ permissions
12
23
  - 📊 **Smart presenters** - Automatic type-aware display of your data
13
- - 🔍 **Advanced filtering** - Range queries, comparison operators, and association filters
24
+ - 🔍 **Advanced filtering** - Range queries, comparison operators, and
25
+ association filters
14
26
  - 📄 **Pagination & sorting** - Kaminari integration with sortable columns
15
27
  - ⚡ **Hotwire ready** - Turbo Streams support out of the box
16
28
  - 📝 **Intelligent forms** - Auto-generated forms with smart field detection
17
29
 
18
30
  ## Prerequisites
19
31
 
20
- - Rails 7.0+
21
- - Ruby 3.0+
32
+ - Ruby 3.3+ (developed and tested against 3.3.5)
33
+ - Rails 8.0+ (developed and tested against Rails 8.1)
22
34
 
23
35
  ## Installation
24
36
 
@@ -41,23 +53,28 @@ $ rails generate cafe_car:install
41
53
  ```
42
54
 
43
55
  This will:
44
- - Add required dependencies (bcrypt, paper_trail, factory_bot_rails, faker, rouge)
56
+
57
+ - Add required gems (cnc, bcrypt, paper_trail, factory_bot_rails, faker, rouge)
58
+ plus development tools (hotwire-livereload, better_errors, binding_of_caller,
59
+ chrome_devtools_rails, i18n-debug)
45
60
  - Mount the CafeCar engine at `/` under the `:admin` namespace
46
61
  - Create `app/policies/application_policy.rb`
47
62
  - Add `CafeCar::Controller` to your `ApplicationController`
48
- - Set up JavaScript imports for Trix and ActionText
63
+ - Set up JavaScript imports for CafeCar, Trix, and ActionText
49
64
 
50
65
  ## Getting Started
51
66
 
52
67
  ### Quick Start: Generate a Complete Resource
53
68
 
54
- The fastest way to get started is to generate a complete resource (model + controller + policy):
69
+ The fastest way to get started is to generate a complete resource (model +
70
+ controller + policy):
55
71
 
56
72
  ```bash
57
73
  $ rails generate cafe_car:resource Product name:string price:decimal description:text
58
74
  ```
59
75
 
60
76
  This creates:
77
+
61
78
  - Migration and model (`app/models/product.rb`)
62
79
  - Controller with CRUD actions (`app/controllers/products_controller.rb`)
63
80
  - Policy with permission methods (`app/policies/product_policy.rb`)
@@ -79,11 +96,12 @@ You can also add CafeCar to existing resources:
79
96
 
80
97
  ```ruby
81
98
  class ProductsController < ApplicationController
82
- recline_in_the_cafe_car
99
+ cafe_car
83
100
  end
84
101
  ```
85
102
 
86
103
  That single line provides:
104
+
87
105
  - All 7 RESTful actions (index, show, new, create, edit, update, destroy)
88
106
  - Automatic authorization via Pundit
89
107
  - Filtering and sorting
@@ -113,17 +131,19 @@ The policy controls both authorization and which attributes can be edited.
113
131
 
114
132
  ### Controllers
115
133
 
116
- The `CafeCar::Controller` module provides automatic CRUD functionality with the `recline_in_the_cafe_car` class method.
134
+ The `CafeCar::Controller` module provides automatic CRUD functionality with the
135
+ `cafe_car` class method.
117
136
 
118
137
  ```ruby
119
138
  class Admin::ClientsController < ApplicationController
120
- recline_in_the_cafe_car
139
+ cafe_car
121
140
  end
122
141
  ```
123
142
 
124
143
  **What you get:**
125
144
 
126
- - **RESTful actions**: `index`, `show`, `new`, `edit`, `create`, `update`, `destroy`
145
+ - **RESTful actions**: `index`, `show`, `new`, `edit`, `create`, `update`,
146
+ `destroy`
127
147
  - **Authorization**: Automatic `authorize!` before each action
128
148
  - **Smart defaults**: Model detection from controller name
129
149
  - **Callbacks**: Lifecycle hooks for `render`, `update`, `create`, `destroy`
@@ -132,9 +152,9 @@ end
132
152
  **Limiting actions:**
133
153
 
134
154
  ```ruby
135
- recline_in_the_cafe_car only: [:index, :show]
155
+ cafe_car only: [:index, :show]
136
156
  # or
137
- recline_in_the_cafe_car except: [:destroy]
157
+ cafe_car except: [:destroy]
138
158
  ```
139
159
 
140
160
  **Custom model:**
@@ -142,7 +162,7 @@ recline_in_the_cafe_car except: [:destroy]
142
162
  ```ruby
143
163
  class Admin::ClientsController < ApplicationController
144
164
  model Company # Use Company model instead of Client
145
- recline_in_the_cafe_car
165
+ cafe_car
146
166
  end
147
167
  ```
148
168
 
@@ -150,8 +170,8 @@ end
150
170
 
151
171
  ```ruby
152
172
  class ProductsController < ApplicationController
153
- recline_in_the_cafe_car
154
-
173
+ cafe_car
174
+
155
175
  set_callback :create, :after do |controller|
156
176
  NotificationMailer.product_created(controller.object).deliver_later
157
177
  end
@@ -160,7 +180,8 @@ end
160
180
 
161
181
  ### Policies
162
182
 
163
- CafeCar extends Pundit with attribute-level permissions and auto-detection of displayable fields.
183
+ CafeCar extends Pundit with attribute-level permissions and auto-detection of
184
+ displayable fields.
164
185
 
165
186
  ```ruby
166
187
  class ClientPolicy < ApplicationPolicy
@@ -185,9 +206,11 @@ end
185
206
  **Key methods:**
186
207
 
187
208
  - `permitted_attributes` - Attributes that can be edited via forms
188
- - `displayable_attributes` - Attributes shown in views (auto-detected from columns + associations)
209
+ - `displayable_attributes` - Attributes shown in views (auto-detected from
210
+ columns + associations)
189
211
  - `displayable_associations` - Associations that can be displayed
190
- - `filtered_attribute?(attr)` - Check if attribute should be hidden (uses Rails parameter filters)
212
+ - `filtered_attribute?(attr)` - Check if attribute should be hidden (uses Rails
213
+ parameter filters)
191
214
 
192
215
  **Scope pattern:**
193
216
 
@@ -203,7 +226,8 @@ end
203
226
 
204
227
  ### Presenters
205
228
 
206
- Presenters convert model objects into view-ready representations with automatic type detection.
229
+ Presenters convert model objects into view-ready representations with automatic
230
+ type detection.
207
231
 
208
232
  **Automatic usage** (in views):
209
233
 
@@ -212,6 +236,7 @@ Presenters convert model objects into view-ready representations with automatic
212
236
  ```
213
237
 
214
238
  This automatically:
239
+
215
240
  1. Finds the appropriate presenter for the object type
216
241
  2. Checks policy permissions
217
242
  3. Renders displayable attributes
@@ -227,14 +252,14 @@ class ProductPresenter < CafeCar::Presenter
227
252
  show :description
228
253
  show :category
229
254
  show :created_at
230
-
255
+
231
256
  # Custom display method
232
257
  def preview
233
258
  "#{name} - #{format_currency(price)}"
234
259
  end
235
-
260
+
236
261
  private
237
-
262
+
238
263
  def format_currency(amount)
239
264
  "$#{amount}"
240
265
  end
@@ -349,7 +374,7 @@ The form builder automatically detects field types:
349
374
  <%= f.field(:price).label %>
350
375
  <%= f.field(:price).input class: "currency" %>
351
376
  <%= f.field(:price).hint "In USD" %>
352
- <%= f.field(:price).errors %>
377
+ <%= f.field(:price).error %>
353
378
  <% end %>
354
379
  ```
355
380
 
@@ -397,17 +422,19 @@ end
397
422
  ```
398
423
 
399
424
  The model gets:
425
+
400
426
  - `sorted(*keys)` - Parse and apply sort parameters
401
- - `normalized_sort_key()` - Convert sort keys to Arel format
427
+ - `normalize_sort_key(key)` - Internal helper that converts a sort key to Arel
428
+ order format
402
429
 
403
430
  **Custom filters in controllers:**
404
431
 
405
432
  ```ruby
406
433
  class ProductsController < ApplicationController
407
- recline_in_the_cafe_car
408
-
434
+ cafe_car
435
+
409
436
  private
410
-
437
+
411
438
  def find_objects
412
439
  @objects = model.where(active: true)
413
440
  .query(filter_params)
@@ -431,16 +458,17 @@ app/views/
431
458
  _form.html.haml # Override form partial
432
459
  ```
433
460
 
434
- CafeCar's default views are in `app/views/cafe_car/application/` and serve as templates.
461
+ CafeCar's default views are in `app/views/cafe_car/application/` and serve as
462
+ templates.
435
463
 
436
464
  ### Custom Responders
437
465
 
438
466
  ```ruby
439
467
  class ProductsController < ApplicationController
440
- recline_in_the_cafe_car
441
-
468
+ cafe_car
469
+
442
470
  private
443
-
471
+
444
472
  def create
445
473
  super
446
474
  respond_with object, location: custom_path
@@ -477,7 +505,78 @@ CafeCar::Current.user_agent # User agent string
477
505
  CafeCar::Current.ip_address # IP address
478
506
  ```
479
507
 
480
- Set in controllers via `set_current_attributes` (automatically called by `recline_in_the_cafe_car`).
508
+ Set in controllers via `set_current_attributes` (automatically called by
509
+ `cafe_car`).
510
+
511
+ ## Sessions & Authentication
512
+
513
+ Sessions are **opt-in**. CafeCar works for plain CRUD with no login at all: when
514
+ a policy denies access and no sessions infrastructure is present, the request
515
+ gets a plain **403 Forbidden** instead of redirecting to a login page that
516
+ doesn't exist. Authorization (Pundit policies) is always on; *authentication*
517
+ (knowing who the user is) is the part you turn on when you want it.
518
+
519
+ ### Enabling sessions
520
+
521
+ 1. **Run the generator** to add the `sessions` table:
522
+
523
+ ```bash
524
+ $ rails generate cafe_car:sessions
525
+ $ rails db:migrate
526
+ ```
527
+
528
+ The `CafeCar::Session` model and `SessionPolicy` ship with the engine, so the
529
+ generator only creates the migration (columns: `user`, `ip_address`,
530
+ `user_agent`).
531
+
532
+ 2. **Expose the routes.** Mounting the engine already provides them. To expose
533
+ login at the top level without mounting, add to `config/routes.rb`:
534
+
535
+ ```ruby
536
+ resource :session, only: %i[new create destroy], controller: "cafe_car/sessions"
537
+ ```
538
+
539
+ This gives you `new_session_path` (login form) and `session_path` (create via
540
+ `POST`, log out via `DELETE`).
541
+
542
+ 3. **Prepare your user model.** It needs `has_secure_password` and an `email`:
543
+
544
+ ```ruby
545
+ class User < ApplicationRecord
546
+ has_secure_password
547
+ has_many :sessions, dependent: :destroy, class_name: "CafeCar::Session"
548
+ end
549
+ ```
550
+
551
+ 4. **Different user model name?** Set it in an initializer (resolved lazily):
552
+
553
+ ```ruby
554
+ # config/initializers/cafe_car.rb
555
+ CafeCar.user_class_name = "Account"
556
+ ```
557
+
558
+ Once sessions are available, an authorization failure for a signed-out visitor
559
+ redirects to the login form (remembering where they were headed) instead of
560
+ returning 403.
561
+
562
+ ### Helpers
563
+
564
+ These are available in controllers and views:
565
+
566
+ - `authenticated?` - truthy when someone is logged in
567
+ - `current_user` - the logged-in user (or `nil`)
568
+ - `current_session` - the current `CafeCar::Session`
569
+
570
+ ```erb
571
+ <% if authenticated? %>
572
+ Signed in as <%= current_user.email %>
573
+ <% else %>
574
+ <%= link_to "Log in", new_session_path %>
575
+ <% end %>
576
+ ```
577
+
578
+ Logging in (`POST /session` with `session[:email]`/`session[:password]`) sets a
579
+ signed, http-only cookie; logging out (`DELETE /session`) clears it.
481
580
 
482
581
  ## Generators
483
582
 
@@ -514,10 +613,22 @@ $ rails generate cafe_car:notes
514
613
  ```
515
614
 
516
615
  Creates:
616
+
517
617
  - Migration for notes table
518
618
  - `Note` model
519
619
  - `Notable` concern for trackable models
520
620
 
621
+ ### Sessions Generator
622
+
623
+ Enable opt-in login/logout (see [Sessions & Authentication](#sessions--authentication)):
624
+
625
+ ```bash
626
+ $ rails generate cafe_car:sessions
627
+ ```
628
+
629
+ Creates the `sessions` table migration. The `CafeCar::Session` model and
630
+ `SessionPolicy` already ship with the engine.
631
+
521
632
  ## Configuration
522
633
 
523
634
  ### Custom Form Builder
@@ -539,7 +650,7 @@ class ApplicationPresenter < CafeCar::Presenter
539
650
  # Application-wide presenter customizations
540
651
  end
541
652
 
542
- # app/presenters/product_presenter.rb
653
+ # app/presenters/product_presenter.rb
543
654
  class ProductPresenter < ApplicationPresenter
544
655
  show :name
545
656
  show :price
@@ -568,7 +679,7 @@ class ProductsControllerTest < ActionDispatch::IntegrationTest
568
679
  get products_url
569
680
  assert_response :success
570
681
  end
571
-
682
+
572
683
  test "create with valid attributes" do
573
684
  assert_difference "Product.count", 1 do
574
685
  post products_url, params: { product: { name: "Widget" } }
@@ -580,8 +691,12 @@ end
580
691
 
581
692
  ## Contributing
582
693
 
583
- Contributions are welcome! Please feel free to submit a Pull Request.
694
+ Contributions are welcome! Please read [CONTRIBUTING.md](CONTRIBUTING.md) for
695
+ development setup, how to run the tests (`bundle exec rake`), and PR expectations.
696
+ By participating you agree to the [Code of Conduct](CODE_OF_CONDUCT.md). To report a
697
+ security issue, see [SECURITY.md](SECURITY.md).
584
698
 
585
699
  ## License
586
700
 
587
- The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
701
+ The gem is available as open source under the terms of the
702
+ [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile CHANGED
@@ -10,7 +10,15 @@ RuboCop::RakeTask.new
10
10
 
11
11
  task :brakeman do
12
12
  require "brakeman"
13
- Brakeman.run app_path: ".", print_report: true
13
+ Brakeman.run app_path: ".", print_report: true, pager: false
14
+ end
15
+
16
+ task :test do
17
+ Rails::TestUnit::Runner.run_from_rake("test")
14
18
  end
15
19
 
16
20
  task default: %i[rubocop test brakeman]
21
+
22
+ # Holdco backlog machinery (tasks:new, tasks:index, tasks:claim, tasks:done, rake task).
23
+ # Loads every .rake under lib/tasks/ so the gem's own tasks and holdco_tasks both run.
24
+ Dir.glob(File.expand_path("lib/tasks/*.rake", __dir__)).sort.each { |f| load f }
@@ -0,0 +1,7 @@
1
+ @font-face {
2
+ font-family: 'Lexend';
3
+ font-style: normal;
4
+ font-weight: 100 900;
5
+ src: url('./Lexend.ttf') format('truetype');
6
+ /* size-adjust: 122%; */
7
+ }
Binary file
@@ -1,61 +1,60 @@
1
1
  :root {
2
- color-scheme: light dark;
3
- accent-color: var(--accent);
4
- scroll-behavior: smooth;
5
-
6
- --blue: blue;
7
- --green: green;
8
- --orange: orange;
9
- --red: red;
10
-
11
- --white: white;
12
- --gray: #eee;
13
- --black: black;
14
-
15
- --accent: var(--blue);
16
- --primary: var(--accent);
17
- --secondary: var(--accent);
18
- --tertiary: var(--accent);
19
-
20
- --success: var(--green);
21
- --warning: var(--orange);
22
- --danger: var(--red);
23
- --error: var(--red);
24
- --info: var(--blue);
25
-
26
- --link: var(--accent);
27
- --card: var(--white);
28
- --button: var(--gray);
29
- --menu: var(--card);
30
-
31
- --shadow: rgb(50 50 93 / .25);
32
- --backdrop: rgb(0 0 0 / .5);
33
-
34
- --hover: rgb(50 50 93 / .03);
35
- --outline: var(--accent);
36
- --invalid: color-mix(in lab, var(--error) 3%, var(--input));
37
- --selection: color-mix(in lab, var(--accent) 25%, transparent);
38
-
39
- --font-family: ui-sans-serif, -apple-system, menu, sans-serif;
40
- --font: 16px var(--font-family);
41
-
42
- --box-shadow: var(--shadow) 0 2px 5px -1px,
43
- var(--shadow) 0 1px 3px -1px;
44
- --inset-shadow: inset var(--shadow) 0 1px 3px -1px;
45
-
46
- --gap: 1rem;
47
- --indent: 0;
48
- --half-gap: calc(var(--gap) / 2);
49
- --third-gap: calc(var(--gap) / 3);
50
- --gap2: calc(var(--gap) * 2);
51
- --radius: 12px;
52
- --menu-radius: calc(var(--radius) / 2);
53
- --control-radius: calc(var(--radius) / 3);
54
-
55
- --max-width: 720px;
56
- --page-width: 1024px;
57
- --aside-width: 20vw;
58
- --modal-width: 720px;
59
-
60
- --duration: 300ms;
2
+ color-scheme: light dark;
3
+ accent-color: var(--accent);
4
+ scroll-behavior: smooth;
5
+
6
+ --blue: blue;
7
+ --green: green;
8
+ --orange: orange;
9
+ --red: red;
10
+
11
+ --white: white;
12
+ --gray: #eee;
13
+ --black: black;
14
+
15
+ --accent: var(--blue);
16
+ --primary: var(--accent);
17
+ --secondary: var(--accent);
18
+ --tertiary: var(--accent);
19
+
20
+ --success: var(--green);
21
+ --warning: var(--orange);
22
+ --danger: var(--red);
23
+ --error: var(--red);
24
+ --info: var(--blue);
25
+
26
+ --link: var(--accent);
27
+ --card: var(--white);
28
+ --button: var(--gray);
29
+ --menu: var(--card);
30
+
31
+ --shadow: rgb(50 50 93 / 0.25);
32
+ --backdrop: rgb(0 0 0 / 0.5);
33
+
34
+ --hover: rgb(50 50 93 / 0.03);
35
+ --outline: var(--accent);
36
+ --invalid: color-mix(in lab, var(--error) 3%, var(--input));
37
+ --selection: color-mix(in lab, var(--accent) 25%, transparent);
38
+
39
+ --font-family: ui-sans-serif, -apple-system, menu, sans-serif;
40
+ --font: 16px var(--font-family);
41
+
42
+ --box-shadow: var(--shadow) 0 2px 5px -1px, var(--shadow) 0 1px 3px -1px;
43
+ --inset-shadow: inset var(--shadow) 0 1px 3px -1px;
44
+
45
+ --gap: 1rem;
46
+ --indent: 0;
47
+ --half-gap: calc(var(--gap) / 2);
48
+ --third-gap: calc(var(--gap) / 3);
49
+ --gap2: calc(var(--gap) * 2);
50
+ --radius: 12px;
51
+ --menu-radius: calc(var(--radius) / 2);
52
+ --control-radius: calc(var(--radius) / 3);
53
+
54
+ --max-width: 720px;
55
+ --page-width: 1024px;
56
+ --aside-width: 20vw;
57
+ --modal-width: 720px;
58
+
59
+ --duration: 100ms;
61
60
  }
@@ -0,0 +1,20 @@
1
+ [data-tip]:not(:has([data-tip]:hover)):hover {
2
+ &::before {
3
+ content: attr(data-tip, attr(title));
4
+ position: fixed;
5
+ display: block;
6
+
7
+ position-anchor: auto;
8
+ position-area: top;
9
+ position-try-fallbacks: flip-block;
10
+
11
+ z-index: 99;
12
+ background: var(--tooltip, rgb(0 0 0 / 0.8));
13
+ font-size: 0.8rem;
14
+ color: var(--white, white);
15
+ padding: 0.25rem 0.5rem;
16
+ max-width: 80%;
17
+ white-space: wrap;
18
+ border-radius: var(--control-radius);
19
+ }
20
+ }
@@ -7,9 +7,8 @@
7
7
  }
8
8
 
9
9
  .remove {
10
- /*animation: fade-out 100ms;*/
11
10
  opacity: 0;
12
- transition: opacity 0.1s;
11
+ transition: all var(--duration);
13
12
  pointer-events: none;
14
13
  }
15
14
 
@@ -7,8 +7,10 @@
7
7
  @import url(cafe_car/trix.css);
8
8
 
9
9
  @import url(cafe_car/pagination.css);
10
+ @import url(cafe_car/tooltips.css);
10
11
  @import url(cafe_car/utility.css);
11
12
  @import url(ui/components.css);
13
+ @import url(Lexend.css);
12
14
  @import url(cafe_car/themes/warm.css);
13
15
  @import url(cafe_car/themes/warm-dark.css) (prefers-color-scheme: dark);
14
16
 
@@ -16,7 +18,7 @@
16
18
  @import url(cafe_car/code/base16-dark.css) (prefers-color-scheme: dark);
17
19
 
18
20
  :root {
19
- --half-gap: calc(var(--gap) / 2);
21
+ --font-family: Lexend, ui-sans-serif, -apple-system, menu, sans-serif;
20
22
  }
21
23
 
22
24
  :focus {
@@ -33,7 +35,7 @@ a {
33
35
  }
34
36
 
35
37
  &:active {
36
- filter: brightness(.95);
38
+ filter: brightness(0.95);
37
39
  }
38
40
  }
39
41
 
@@ -53,20 +55,29 @@ body {
53
55
  background-blend-mode: soft-light; */
54
56
  font: var(--font);
55
57
  margin: 0;
56
- min-height: 100vh;
58
+ min-height: 100dvh;
59
+ display: flex;
60
+ flex-flow: column;
61
+
62
+ > * {
63
+ flex: 1;
64
+ }
57
65
  }
58
66
 
59
- h1, h2, h4, h5, h6, p {
67
+ h1,
68
+ h2,
69
+ h4,
70
+ h5,
71
+ h6,
72
+ p {
60
73
  margin-left: var(--indent);
61
74
  }
62
75
 
63
76
  time {
64
77
  text-decoration: dotted underline;
65
78
  text-decoration-color: var(--dim);
66
- cursor: default;
67
79
  }
68
80
 
69
-
70
81
  html[data-turbo-visit-direction="forward"]::view-transition-group(root),
71
82
  html[data-turbo-visit-direction="back"]::view-transition-group(root) {
72
83
  animation: none;
@@ -15,7 +15,8 @@
15
15
  --color: var(--error);
16
16
  }
17
17
 
18
- .Alert-info {
18
+ .Alert-info,
19
+ .Alert-notice {
19
20
  --color: var(--info);
20
21
  }
21
22