From obie-skills
Applies Better Stimulus best practices for maintainable, reusable StimulusJS controllers using SOLID principles. Useful for writing new controllers, refactoring, reviews, debugging inter-controller communication, and Turbo integration.
npx claudepluginhub joshuarweaver/cascade-code-general-misc-2 --plugin obie-skillsThis skill uses the workspace's default tool permissions.
Apply opinionated best practices from [betterstimulus.com](https://betterstimulus.com/) when writing or refactoring Stimulus controllers. These patterns emphasize code reusability, proper separation of concerns, and SOLID design principles.
Creates isolated Git worktrees for feature branches with prioritized directory selection, gitignore safety checks, auto project setup for Node/Python/Rust/Go, and baseline verification.
Executes implementation plans in current session by dispatching fresh subagents per independent task, with two-stage reviews: spec compliance then code quality.
Dispatches parallel agents to independently tackle 2+ tasks like separate test failures or subsystems without shared state or dependencies.
Apply opinionated best practices from betterstimulus.com when writing or refactoring Stimulus controllers. These patterns emphasize code reusability, proper separation of concerns, and SOLID design principles.
Invoke this skill when:
Externalize hardcoded values into data attributes rather than embedding them in controller logic.
Bad:
toggle() {
this.element.classList.toggle("active")
}
Good:
static classes = ["active"]
toggle() {
this.element.classList.toggle(this.activeClass)
}
<div data-controller="toggle" data-toggle-active-class="active"></div>
Store controller state in Stimulus values, not instance properties, to leverage reactivity and DOM persistence.
Bad:
connect() {
this.count = 0
}
Good:
static values = { count: Number }
countValueChanged(count) {
this.updateDisplay()
}
Each controller should have one reason to change. Split controllers that mix concerns.
Ask: "What would cause this controller to change?" If multiple unrelated reasons, split it.
Use connect() for:
Don't use connect() for:
data-action)Use data-action attributes instead of addEventListener() to let Stimulus manage lifecycle.
Bad:
connect() {
document.addEventListener("click", this.handler.bind(this))
}
Good:
<div data-action="click@document->controller#handler"></div>
See: references/architecture.md
[name]ValueChanged)See: references/state-management.md
connect() for third-party library initializationconnect() with disconnect() for cleanupconnect() with state setupteardown() for Turbo-specific cleanupSee: references/lifecycle.md
Three approaches:
Choose based on relationship:
See: references/events-and-interaction.md
See: references/solid-principles.md
<template> to restore DOM staterequestSubmit() not submit() for formsSee: references/dom-and-turbo.md
handleError() methodSee: references/error-handling.md
static values = {
url: String,
count: Number,
enabled: Boolean,
items: Array,
config: Object
}
<!-- Element events -->
<div data-action="click->controller#method">
<!-- Global events -->
<div data-action="resize@window->controller#layout">
<div data-action="keydown@document->controller#handleKey">
<!-- Multiple actions -->
<div data-action="click->ctrl1#method1 click->ctrl2#method2">
// Dispatch
const event = new CustomEvent('name:action', {
bubbles: true,
detail: { key: 'value' }
})
this.element.dispatchEvent(event)
// Listen
data-action="name:action->controller#handler"
<div data-controller="parent"
data-parent-child-outlet=".child">
<div class="child" data-controller="child"></div>
</div>
static outlets = ['child']
this.childOutlets.forEach(outlet => outlet.method())
connect() // Element connected to DOM
disconnect() // Element removed from DOM
[name]TargetConnected(element) // Target added
[name]TargetDisconnected(element) // Target removed
[name]ValueChanged(value, oldValue) // Value changed
[name]OutletConnected(outlet) // Outlet connected
[name]OutletDisconnected(outlet) // Outlet disconnected
When writing a new controller:
When refactoring:
connect() with state setup and event listenersaddEventListener() without proper cleanup.bind() separately in connect and disconnectsubmit() instead of requestSubmit()All patterns in this skill come from betterstimulus.com, an opinionated collection of StimulusJS best practices.
For detailed explanations and examples, see:
references/architecture.md - Controller design patternsreferences/state-management.md - Values API usagereferences/lifecycle.md - Lifecycle best practicesreferences/events-and-interaction.md - Communication patternsreferences/solid-principles.md - SOLID design principlesreferences/dom-and-turbo.md - DOM manipulation and Turboreferences/error-handling.md - Error management