vident-view_component 1.0.0.beta2 → 1.0.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +32 -1
- data/README.md +171 -17
- data/lib/vident/view_component/base.rb +9 -1
- metadata +3 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: cd06405de6503622f0cfe065630cc6c87d5d71f2a370e71b50fa8b73c28fb9ea
|
|
4
|
+
data.tar.gz: 44460ee8ea6773c46fd4ac0686f51dd2d731f9b749af7584c3064df1895aba0c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 23e25a96110c8d5f1a880df3023598eb5bd726a31a9dad637aedd74f6952ab0405bbcf43fd04e10b70b5bc59c3a6b98d9f0d6889e6811729fb8e3cc8fae1e4a1
|
|
7
|
+
data.tar.gz: f0576171afd7ab9d88bc593b34c8bff25b1a3f77ec33a40c352c3fe6255dc4b9178a123fdd5ce7e308a32fe2640bc37107c2f1079d491241c7dfc138b5820049
|
data/CHANGELOG.md
CHANGED
|
@@ -6,11 +6,42 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/)
|
|
|
6
6
|
and this project adheres to [Semantic Versioning](http://semver.org/).
|
|
7
7
|
|
|
8
8
|
|
|
9
|
+
## [1.0.0] - 2026-04-19
|
|
10
|
+
|
|
11
|
+
### Breaking
|
|
12
|
+
|
|
13
|
+
- `nil` stimulus values (static or returned from a proc) are now filtered out of the rendered data attributes instead of being serialized to an empty string. The previous behaviour silently turned Boolean-typed Stimulus values on, because Stimulus parses empty strings as `true`. To explicitly emit the JS `null` literal (for Object/Array values), return the new `Vident::StimulusNull` sentinel (#24).
|
|
14
|
+
- `Vident::StableId` now requires an explicit strategy and an explicit per-request seed (#14). Run `bin/rails generate vident:install` to create an initializer that picks `STRICT` outside tests and wires a `before_action` in `ApplicationController` seeding the generator from `request.fullpath`. `set_current_sequence_generator` now requires `seed:` (nil raises `ArgumentError`); calling `next_id_in_sequence` with no strategy configured raises `Vident::StableId::StrategyNotConfiguredError`; in `STRICT` mode, missing the per-request seeding raises `Vident::StableId::GeneratorNotSetError`. The previous hard-coded seed of `42` (which produced identical IDs across unrelated requests and caused DOM collisions when Ajax fragments were grafted into server-rendered pages) is gone, and the `new_current_sequence_generator` alias has been removed.
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
|
|
18
|
+
- `Vident::StimulusNull` sentinel. Assign or return it from a value proc to emit `data-...-value="null"`, which Stimulus's Object/Array parser reads as JSON `null`.
|
|
19
|
+
- `Vident::StableId.with_sequence_generator(seed:) { ... }` block helper for scoping a generator to a render outside the normal request flow (mailers, jobs, previews) (#14).
|
|
20
|
+
- `bin/rails generate vident:install` installer that writes `config/initializers/vident.rb` and patches `ApplicationController` with the per-request seed hook (#14).
|
|
21
|
+
- Claude Code skill at `skills/vident/SKILL.md` shipped with the gem. The install generator drops it into `.claude/skills/vident/SKILL.md` in the host app so Claude Code picks up the gem's conventions automatically.
|
|
22
|
+
- Action descriptor support. The `stimulus_actions:` prop, `stimulus do ... actions(...)` DSL, and `child_element(stimulus_action(s): ...)` now accept a `Hash` (`{event:, method:, controller:, options:, keyboard:, window:}`) or a typed `Vident::StimulusAction::Descriptor` instance, allowing the `:once`/`:prevent`/`:stop`/`:passive`/`:"!passive"`/`:capture`/`:self` event-option modifiers, `.ctrl+a`-style keyboard filters, and `@window` in a structured Ruby form rather than a hand-typed descriptor string.
|
|
23
|
+
- Stimulus action parameters. New `Vident::StimulusParam` / `StimulusParamCollection`, a `stimulus_params:` prop, a `params` DSL entry (mirrors `values`), a `stimulus_params:`/`stimulus_param:` kwarg on `child_element`, and inline `as_stimulus_param(s)` helpers. Emits `data-<controller>-<name>-param` attributes readable via `event.params.<name>` in the JS controller; element-scoped to match Stimulus's own semantics.
|
|
24
|
+
|
|
25
|
+
### Changed
|
|
26
|
+
|
|
27
|
+
- Internal refactor: introduced a `Vident::Stimulus::PRIMITIVES` registry (`lib/vident/stimulus.rb`) as the single source of truth for the seven Stimulus primitive kinds (controllers, actions, targets, outlets, values, params, classes). `StimulusDataAttributeBuilder`, the seven `stimulus_<kind>s(...)` plural parsers, and the seven `add_stimulus_<kind>s(...)` mutators are now generated from that registry, so adding a future primitive is a one-line registry addition plus a Value/Collection class pair — no per-kind edits in the resolver, builder, child-element helper, or data-attribute builder. No user-visible API change.
|
|
28
|
+
- `serialize_value` lives on `StimulusAttributeBase` and is shared across `StimulusValue` / `StimulusParam` (was duplicated).
|
|
29
|
+
- `StimulusAttributeBase.stimulize_path` is the canonical path → identifier helper (`stimulus_identifier_from_path` on `StimulusComponent` now delegates); both accept Symbol input consistently.
|
|
30
|
+
- `StimulusBuilder`'s two nil-filter methods collapsed into one `resolve_hash_filtering_nil`.
|
|
31
|
+
- Uniform shape matrix across all seven plural parsers: every kind now consistently accepts pre-built `<Kind>` instances, pre-built `<Kind>Collection`s, Arrays, and (where meaningful) Hashes in its variadic input.
|
|
32
|
+
|
|
33
|
+
### Fixed
|
|
34
|
+
|
|
35
|
+
- `stimulus_controllers:` prop and the `stimulus_controllers(...)` helper now accept Symbol paths (e.g. `:my_controller`, `:"admin/users"`) instead of raising `NoMethodError: undefined method 'split' for an instance of Symbol` (#15).
|
|
36
|
+
- `Vident::StimulusController`'s `implied_controller_path` / `implied_controller_name` overrides now raise the same `ArgumentError` as the base when `implied_controller` is nil, instead of a confusing `NoMethodError`.
|
|
37
|
+
- `stimulus_values:` and `stimulus_classes:` props now accept cross-controller entries. The type unions include `Array` (matching `stimulus_actions:`/`stimulus_targets:`), and the collection parsers pass through pre-built `StimulusValue`/`StimulusValueCollection` (and class equivalents) instead of re-wrapping them into the single-value constructor and raising `ArgumentError: Invalid number of arguments` (#23).
|
|
38
|
+
- `Vident::ComponentClassLists#class_list_builder` no longer memoises the `ClassListBuilder` instance. The first caller's `root_element_html_class:` was previously latched into the cached builder, which silently dropped the `class:` argument passed to a later `root_element(class: …)` whenever `class_list_for_stimulus_classes(:name)` ran first. The underlying `TailwindMerge::Merger` is still thread-cached, so the re-construction cost is negligible.
|
|
39
|
+
|
|
9
40
|
## [1.0.0.beta2] - 2026-04-16
|
|
10
41
|
|
|
11
42
|
### Breaking
|
|
12
43
|
|
|
13
|
-
- Renamed the `tag(...)` helper to `child_element(...)` (and the internal `Vident::TagHelper` module to `Vident::ChildElementHelper`). The old name shadowed Rails' own `tag(name, options)` positional API, which breaks Rails helpers like `hidden_field_tag` and `image_tag` when called inside vident components. Rename any `component.tag(...)` calls to `component.child_element(...)`.
|
|
44
|
+
- Renamed the `tag(...)` helper to `child_element(...)` (and the internal `Vident::TagHelper` module to `Vident::ChildElementHelper`). The old name shadowed Rails' own `tag(name, options)` positional API, which breaks Rails helpers like `hidden_field_tag` and `image_tag` when called inside vident components (#22). Rename any `component.tag(...)` calls to `component.child_element(...)`.
|
|
14
45
|
|
|
15
46
|
### Fixed
|
|
16
47
|
|
data/README.md
CHANGED
|
@@ -59,8 +59,11 @@ Then run:
|
|
|
59
59
|
|
|
60
60
|
```bash
|
|
61
61
|
bundle install
|
|
62
|
+
bin/rails generate vident:install
|
|
62
63
|
```
|
|
63
64
|
|
|
65
|
+
The `vident:install` generator writes `config/initializers/vident.rb`, wires per-request ID seeding into `ApplicationController`, and (if you use Claude Code) drops a Vident skill into `.claude/skills/vident/SKILL.md` so the model has first-party guidance on the gem's conventions. See [Element IDs and request-scoped seeding](#element-ids-and-request-scoped-seeding) for the initializer rationale, and [Claude Code skill](#claude-code-skill) for the skill.
|
|
66
|
+
|
|
64
67
|
## Quick Start
|
|
65
68
|
|
|
66
69
|
Here's a simple example of a Vident component using ViewComponent:
|
|
@@ -352,10 +355,25 @@ class ToggleComponent < Vident::ViewComponent::Base
|
|
|
352
355
|
classes expanded: "block",
|
|
353
356
|
collapsed: "hidden",
|
|
354
357
|
transitioning: "opacity-50"
|
|
358
|
+
|
|
359
|
+
# Action parameters (element-scoped; readable as event.params.* in JS)
|
|
360
|
+
params item_id: -> { @item.id }, kind: "inline"
|
|
355
361
|
end
|
|
356
362
|
end
|
|
357
363
|
```
|
|
358
364
|
|
|
365
|
+
**Action modifiers** — the Array form `[:click, :method]` handles the common case. For Stimulus's modifier syntax (`:once`/`:prevent`/`:stop`/`:passive`/`:"!passive"`/`:capture`/`:self`, keyboard filters, `@window`), pass a Hash or a `Vident::StimulusAction::Descriptor`:
|
|
366
|
+
|
|
367
|
+
```ruby
|
|
368
|
+
actions({event: :click, method: :submit, options: [:once, :prevent]})
|
|
369
|
+
actions({event: :keydown, method: :on_key, keyboard: "ctrl+a"})
|
|
370
|
+
actions({event: :resize, method: :on_resize, window: true})
|
|
371
|
+
# or, if you want a typed, passable object:
|
|
372
|
+
actions Vident::StimulusAction::Descriptor.new(event: :click, method: :save, options: [:prevent])
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
Unknown option symbols raise `ArgumentError` at attribute construction, not at render.
|
|
376
|
+
|
|
359
377
|
### Dynamic Values and Classes with Procs
|
|
360
378
|
|
|
361
379
|
The Stimulus DSL supports dynamic values and classes using procs or lambdas that are evaluated in the component instance context:
|
|
@@ -415,6 +433,43 @@ stimulus do
|
|
|
415
433
|
end
|
|
416
434
|
```
|
|
417
435
|
|
|
436
|
+
**Nil values.** Returning `nil` from a value proc (or setting a static `nil`) omits the data attribute entirely, so Stimulus uses its per-type default. Don't rely on `nil` becoming empty-string — that silently reads as `true` for Boolean values. If you genuinely need to emit a JS `null` (only meaningful for Object/Array-typed Stimulus values), return the `Vident::StimulusNull` sentinel, which serializes to the literal string `"null"` for JSON.parse.
|
|
437
|
+
|
|
438
|
+
```ruby
|
|
439
|
+
values current_user_id: -> { @user&.id }, # nil → attribute omitted
|
|
440
|
+
config: -> { @user ? @config : Vident::StimulusNull } # nil object → JSON null
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
### Action Parameters
|
|
444
|
+
|
|
445
|
+
Stimulus action parameters — `data-<controller>-<name>-param="value"` — are read inside an action handler as `event.params.<name>` (auto-typecast to Number / String / Object / Boolean). Params are **element-scoped**: every action attached to the same element sees the same params.
|
|
446
|
+
|
|
447
|
+
Vident mirrors the `values` entry points:
|
|
448
|
+
|
|
449
|
+
```ruby
|
|
450
|
+
# In the DSL (component root element)
|
|
451
|
+
stimulus do
|
|
452
|
+
actions [:click, :promote]
|
|
453
|
+
params release_id: -> { @release_id }, kind: "promote"
|
|
454
|
+
end
|
|
455
|
+
|
|
456
|
+
# As a prop at render time
|
|
457
|
+
render MyComponent.new(stimulus_params: { release_id: 42 })
|
|
458
|
+
|
|
459
|
+
# Cross-controller (Array form) — both as a prop and in the DSL
|
|
460
|
+
stimulus_params: [
|
|
461
|
+
[:release_id, 42], # implied-release-id-param="42"
|
|
462
|
+
["other/ctrl", :scope, "full"], # other--ctrl-scope-param="full"
|
|
463
|
+
]
|
|
464
|
+
|
|
465
|
+
# On a child element (co-located with the action it informs)
|
|
466
|
+
card.child_element(:button,
|
|
467
|
+
stimulus_action: [:click, :promote],
|
|
468
|
+
stimulus_params: { release_id: @release_id })
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
Inline helpers: `as_stimulus_param(:name, value)` / `as_stimulus_params({name: value, ...})`.
|
|
472
|
+
|
|
418
473
|
### Scoped Custom Events
|
|
419
474
|
|
|
420
475
|
Vident provides helper methods to generate scoped event names for dispatching custom events that are unique to your component:
|
|
@@ -474,6 +529,8 @@ class CustomComponent < Vident::ViewComponent::Base
|
|
|
474
529
|
end
|
|
475
530
|
```
|
|
476
531
|
|
|
532
|
+
All stimulus props accept Symbol paths as well as Strings (e.g. `stimulus_controllers: [:custom, :"admin/users"]`). `stimulus_values:` and `stimulus_classes:` additionally accept Array entries (for cross-controller: `[["admin/users", :name, "value"]]`) and pre-built `StimulusValue`/`StimulusValueCollection` / `StimulusClass`/`StimulusClassCollection` instances, so you can compose attribute sets outside the component and pass them in.
|
|
533
|
+
|
|
477
534
|
or you can use tag helpers to generate HTML with Stimulus attributes:
|
|
478
535
|
|
|
479
536
|
```erb
|
|
@@ -521,36 +578,71 @@ or directly in the ViewComponent template (eg with ERB) using the `as_stimulus_*
|
|
|
521
578
|
|
|
522
579
|
### Stimulus Helpers in Templates
|
|
523
580
|
|
|
524
|
-
|
|
581
|
+
Inline helpers emit pre-built `data-*` fragments you can drop into any tag. Both singular and plural forms exist; pass one or many arguments accordingly.
|
|
525
582
|
|
|
526
583
|
```erb
|
|
527
584
|
<%= render root do |component| %>
|
|
528
|
-
|
|
529
|
-
<div <%= component.as_target(:content) %>>
|
|
585
|
+
<div <%= component.as_stimulus_target(:content) %>>
|
|
530
586
|
Content here
|
|
531
587
|
</div>
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
<%= component.
|
|
588
|
+
|
|
589
|
+
<button <%= component.as_stimulus_action(:click, :toggle) %>>Toggle</button>
|
|
590
|
+
|
|
591
|
+
<input <%= component.as_stimulus_targets(:input, :field) %>
|
|
592
|
+
<%= component.as_stimulus_actions([:input, :validate], [:change, :save]) %>>
|
|
593
|
+
|
|
594
|
+
<%# Or build a whole element with the child_element helper: %>
|
|
595
|
+
<%= component.child_element(:div, stimulus_target: :output, class: "mt-4") do %>
|
|
540
596
|
Output here
|
|
541
597
|
<% end %>
|
|
542
|
-
|
|
543
|
-
<!-- Multiple targets/actions -->
|
|
544
|
-
<input <%= component.as_targets(:input, :field) %>
|
|
545
|
-
<%= component.as_actions([:input, :validate], [:change, :save]) %>>
|
|
546
598
|
<% end %>
|
|
547
599
|
```
|
|
548
600
|
|
|
601
|
+
Parallel helpers exist for every attribute kind: `as_stimulus_controller(s)`, `as_stimulus_value(s)`, `as_stimulus_class(es)`, `as_stimulus_outlet(s)`.
|
|
602
|
+
|
|
549
603
|
### Stimulus Outlets
|
|
550
604
|
|
|
551
|
-
|
|
605
|
+
[Stimulus outlets](https://stimulus.hotwired.dev/reference/outlets) let one controller hold references to other controllers matched by a CSS selector. Vident has a few forms for declaring them.
|
|
552
606
|
|
|
607
|
+
**On the component's root element** — via the DSL:
|
|
553
608
|
|
|
609
|
+
```ruby
|
|
610
|
+
class DashboardComponent < Vident::ViewComponent::Base
|
|
611
|
+
stimulus do
|
|
612
|
+
# kwarg form: outlet name is the implied controller's identifier
|
|
613
|
+
outlets modal: ".modal", user_status: ".online-user"
|
|
614
|
+
|
|
615
|
+
# positional-hash form: required when the outlet identifier contains "--"
|
|
616
|
+
# (e.g. cross-namespace controllers) because Ruby kwarg keys can't have dashes
|
|
617
|
+
outlets({"admin--users" => ".admin-users"})
|
|
618
|
+
end
|
|
619
|
+
end
|
|
620
|
+
```
|
|
621
|
+
|
|
622
|
+
Or via the `stimulus_outlets:` prop / `root_element_attributes`:
|
|
623
|
+
|
|
624
|
+
```ruby
|
|
625
|
+
stimulus_outlets: [
|
|
626
|
+
[:modal, ".modal"], # [name, selector] on implied controller
|
|
627
|
+
["admin/users", :row, ".user-row"], # [controller_path, name, selector] for cross-controller
|
|
628
|
+
:user_status, # single symbol → auto-selector (see below)
|
|
629
|
+
other_component # component instance → reuses its stimulus_identifier + id
|
|
630
|
+
]
|
|
631
|
+
```
|
|
632
|
+
|
|
633
|
+
**Auto-generated selectors.** Pass just a name (symbol or string) and the selector becomes `[data-controller~=<name>]`. Pass a component instance and the selector additionally scopes to the component's id (`#<id> [data-controller~=...]`), which is what lets you target a specific instance rather than every matching controller on the page.
|
|
634
|
+
|
|
635
|
+
**Self-registration via `stimulus_outlet_host`.** A built-in prop on every Vident component. When set to another component, the child registers itself as an outlet on that host at initialization — the host doesn't need to know about the child in its DSL:
|
|
636
|
+
|
|
637
|
+
```ruby
|
|
638
|
+
render DashboardComponent.new do |dashboard|
|
|
639
|
+
render ModalComponent.new(stimulus_outlet_host: dashboard)
|
|
640
|
+
end
|
|
641
|
+
```
|
|
642
|
+
|
|
643
|
+
The modal now appears on the dashboard's root element as `data-dashboard-component-modal-component-outlet="#<modal-id>"` without the dashboard declaring it upfront.
|
|
644
|
+
|
|
645
|
+
**On child elements** — `child_element` accepts `stimulus_outlet:` (singular) and `stimulus_outlets:` (plural / Enumerable) exactly like the target/action kwargs, so a nested `<div>` can carry its own outlet declarations.
|
|
554
646
|
|
|
555
647
|
|
|
556
648
|
### Stimulus Controller Naming
|
|
@@ -586,7 +678,7 @@ end
|
|
|
586
678
|
<% end %>
|
|
587
679
|
```
|
|
588
680
|
|
|
589
|
-
This creates a nested component that once clicked triggers the parent components `handleTrigger` action.
|
|
681
|
+
This creates a nested component that once clicked triggers the parent components `handleTrigger` action. The same pattern works for cross-controller `stimulus_values:`, `stimulus_classes:`, and `stimulus_outlets:` — build the entries with the parent's helpers (`parent.stimulus_value(...)`, etc.) and pass them down.
|
|
590
682
|
|
|
591
683
|
## Other Features
|
|
592
684
|
|
|
@@ -692,6 +784,68 @@ end
|
|
|
692
784
|
<% end %>
|
|
693
785
|
```
|
|
694
786
|
|
|
787
|
+
### Element IDs and request-scoped seeding
|
|
788
|
+
|
|
789
|
+
Every Vident component generates an element `id` at construction time (e.g. `button-component-abc123-0`). The IDs are produced by a deterministic sequence so the same render produces the same markup — which is what lets HTTP `ETag` caching return `304 Not Modified` for unchanged pages.
|
|
790
|
+
|
|
791
|
+
Because the IDs are deterministic, **the sequence has to be keyed on something that identifies the logical content of the request**. If two unrelated renders share the same seed, the same sequence indices produce the same IDs, and Ajax-inserted fragments can collide with IDs already on the page.
|
|
792
|
+
|
|
793
|
+
The `vident:install` generator wires this up for you. It writes `config/initializers/vident.rb`:
|
|
794
|
+
|
|
795
|
+
```ruby
|
|
796
|
+
# config/initializers/vident.rb
|
|
797
|
+
Vident::StableId.strategy = if Rails.env.test?
|
|
798
|
+
Vident::StableId::RANDOM_FALLBACK
|
|
799
|
+
else
|
|
800
|
+
Vident::StableId::STRICT
|
|
801
|
+
end
|
|
802
|
+
```
|
|
803
|
+
|
|
804
|
+
and patches `ApplicationController`:
|
|
805
|
+
|
|
806
|
+
```ruby
|
|
807
|
+
class ApplicationController < ActionController::Base
|
|
808
|
+
before_action do
|
|
809
|
+
Vident::StableId.set_current_sequence_generator(seed: request.fullpath)
|
|
810
|
+
end
|
|
811
|
+
after_action do
|
|
812
|
+
Vident::StableId.clear_current_sequence_generator
|
|
813
|
+
end
|
|
814
|
+
end
|
|
815
|
+
```
|
|
816
|
+
|
|
817
|
+
**Why `request.fullpath`?** It includes the query string, so `/items/1?page=2` and `/items/1?page=3` get different seeds and different IDs — which is what you want, because they are different pages from a caching perspective. Requests to the same fullpath get identical IDs, so `ETag` matches work unchanged.
|
|
818
|
+
|
|
819
|
+
#### Strategies
|
|
820
|
+
|
|
821
|
+
- `Vident::StableId::STRICT` (production/development default). Raises `Vident::StableId::GeneratorNotSetError` if a component renders on a thread that has no generator set. Use this in any environment where missing the `before_action` is a bug — the loud failure tells you immediately.
|
|
822
|
+
- `Vident::StableId::RANDOM_FALLBACK` (test default). Emits a random hex id when no generator is set. Tests, ViewComponent previews, and ad-hoc renders work without any extra wiring. You can still call `set_current_sequence_generator(seed:)` when you want to assert on deterministic IDs.
|
|
823
|
+
|
|
824
|
+
You can point `strategy` at any callable of your own, e.g. to log every generation or route through a different generator entirely.
|
|
825
|
+
|
|
826
|
+
#### Rendering outside a request
|
|
827
|
+
|
|
828
|
+
Mailers, jobs, previews, and scripts run outside the controller callback cycle, so there's no `before_action` to seed the generator. Under `STRICT` they will raise. Two options:
|
|
829
|
+
|
|
830
|
+
1. **Wrap the render in a scoped generator:**
|
|
831
|
+
```ruby
|
|
832
|
+
Vident::StableId.with_sequence_generator(seed: "daily-digest-#{Date.today}") do
|
|
833
|
+
render_component(DigestComponent.new(...))
|
|
834
|
+
end
|
|
835
|
+
```
|
|
836
|
+
The block sets the generator, yields, and restores whatever was on the thread before (including `nil`) in an `ensure`.
|
|
837
|
+
2. **Run in a context where `RANDOM_FALLBACK` is fine.** If you don't care about id stability for that particular render (no caching, no snapshot comparison), either swap the strategy for that block or just accept random IDs.
|
|
838
|
+
|
|
839
|
+
#### The collision bug in earlier versions
|
|
840
|
+
|
|
841
|
+
Before 1.0.0 `set_current_sequence_generator` hard-coded the seed to `42`, so every request that called it got the same deterministic sequence. When two independent renders shipped in one browser session — e.g. a server-rendered page and an Ajax fragment loaded into it — both started from index 0 and produced colliding `id` attributes. This surfaced as duplicate IDs in the DOM, broken `label[for=...]` associations, and Stimulus controllers attaching to the wrong element. Upgrading and running `bin/rails generate vident:install` seeds from `request.fullpath` instead, so each render gets its own sequence space.
|
|
842
|
+
|
|
843
|
+
### Claude Code skill
|
|
844
|
+
|
|
845
|
+
Vident ships a [Claude Code](https://docs.claude.com/claude-code) skill at `skills/vident/SKILL.md` that teaches the model the gem's conventions: the `stimulus do` DSL, `child_element`, outlets (including `stimulus_outlet_host` self-registration), the `nil` / `Vident::StimulusNull` value rules, the per-request StableId setup, and the Ruby↔JS dispatch handshake. It covers the foot-guns ahead of time so the model doesn't repeat them.
|
|
846
|
+
|
|
847
|
+
`bin/rails generate vident:install` copies the file into `.claude/skills/vident/SKILL.md` of the host app (skipped if already present). Claude Code picks it up automatically via skill discovery; no further wiring needed. If you want to update the skill later, delete the file and re-run the generator.
|
|
848
|
+
|
|
695
849
|
|
|
696
850
|
## Testing
|
|
697
851
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
module Vident
|
|
2
2
|
module ViewComponent
|
|
3
3
|
class Base < ::ViewComponent::Base
|
|
4
|
-
include
|
|
4
|
+
include Vident::Component
|
|
5
5
|
|
|
6
6
|
class << self
|
|
7
7
|
def cache_component_modified_time
|
|
@@ -92,6 +92,14 @@ module Vident
|
|
|
92
92
|
to_data_attribute_string(**stimulus_value(...))
|
|
93
93
|
end
|
|
94
94
|
|
|
95
|
+
def as_stimulus_params(...)
|
|
96
|
+
to_data_attribute_string(**stimulus_params(...))
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def as_stimulus_param(...)
|
|
100
|
+
to_data_attribute_string(**stimulus_param(...))
|
|
101
|
+
end
|
|
102
|
+
|
|
95
103
|
def as_stimulus_classes(...)
|
|
96
104
|
to_data_attribute_string(**stimulus_classes(...))
|
|
97
105
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: vident-view_component
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.0.0
|
|
4
|
+
version: 1.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Stephen Ierodiaconou
|
|
@@ -55,14 +55,14 @@ dependencies:
|
|
|
55
55
|
requirements:
|
|
56
56
|
- - "~>"
|
|
57
57
|
- !ruby/object:Gem::Version
|
|
58
|
-
version: 1.0.0
|
|
58
|
+
version: 1.0.0
|
|
59
59
|
type: :runtime
|
|
60
60
|
prerelease: false
|
|
61
61
|
version_requirements: !ruby/object:Gem::Requirement
|
|
62
62
|
requirements:
|
|
63
63
|
- - "~>"
|
|
64
64
|
- !ruby/object:Gem::Version
|
|
65
|
-
version: 1.0.0
|
|
65
|
+
version: 1.0.0
|
|
66
66
|
- !ruby/object:Gem::Dependency
|
|
67
67
|
name: view_component
|
|
68
68
|
requirement: !ruby/object:Gem::Requirement
|