From obie-skills
Adds polymorphic activity timelines with live Turbo Stream updates to any Rails model for tracking field changes, status transitions, comments, and attachments.
npx claudepluginhub joshuarweaver/cascade-code-general-misc-2 --plugin obie-skillsThis skill uses the workspace's default tool permissions.
Add a polymorphic activity timeline with live Turbo Stream updates to any Rails model. Track field changes, status transitions, comments, attachments, and more with configurable icons and colors per action type.
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.
Add a polymorphic activity timeline with live Turbo Stream updates to any Rails model. Track field changes, status transitions, comments, attachments, and more with configurable icons and colors per action type.
Invoke this skill when:
The system is built around four components:
ActivityEvent model (polymorphic) — the core event recordActivityTrackable concern — auto-logs child model lifecycle events on a parent's timelinebelongs_to :trackable, polymorphic: true # required — parent entity whose timeline this belongs to
belongs_to :subject, polymorphic: true, optional: true # related entity being acted upon
belongs_to :user, optional: true # who performed the action
ACTIONS = %w[
created updated destroyed
field_updated status_changed
comment_added
attachment_added attachment_removed
assigned unassigned
relationship_added relationship_removed
tag_added tag_removed
].freeze
Add domain-specific actions as needed (e.g., published, approved, merged). Just add them to ACTIONS and the DISPLAY hash.
Migration, full ActivityEvent model, ActivityTrackable concern, and prerequisites.
See: references/setup.md
Stream naming convention, broadcast methods, partial routing, shared timeline partial, and event partial.
See: references/broadcasting.md
The DISPLAY hash, adding custom actions with icons, display methods, and customizing the event partial.
See: references/display.md
AI-generated change summaries for field_updated events with long text changes.
See: references/ai-summaries.md
Quick step-by-step for adding a timeline to any model (e.g., Project, Article, Post):
class Project < ApplicationRecord
has_many :activity_events, as: :trackable, dependent: :destroy
end
In ActivityEvent#broadcast_activity_prepend, add your model to the partial routing case statement:
def broadcast_partial
case trackable_type
when "Project", "Article" then "activity_events/activity_event"
# Add new types here as needed
else "activity_events/activity_event"
end
end
<%# app/views/projects/show.html.erb %>
<%= render "shared/activity_timeline", record: @project %>
# In a controller action
ActivityEvent.create!(
trackable: @project,
user: current_user,
action: "status_changed",
details: { from_status: "draft", to_status: "active", rationale: "Ready for review" }
)
# Or track field changes
if @project.saved_change_to_status?
ActivityEvent.create!(
trackable: @project,
user: current_user,
action: "field_updated",
details: {
field: "status",
from: @project.status_previously_was,
to: @project.status
}
)
end
For child models that should auto-log events on a parent's timeline, include ActivityTrackable and implement the interface:
class Comment < ApplicationRecord
include ActivityTrackable
def activity_trackable = post # parent entity whose timeline gets the event
def activity_action_created = "comment_added"
def activity_action_destroyed = "comment_removed"
def activity_label = body.truncate(60)
def activity_user = user
end
class Attachment < ApplicationRecord
include ActivityTrackable
def activity_trackable = record # polymorphic parent
def activity_action_created = "attachment_added"
def activity_action_destroyed = "attachment_removed"
def activity_label = filename
def activity_user = user
end
Override activity_created_details or activity_destroyed_details to store additional metadata:
def activity_created_details
{ file_size: byte_size, content_type: content_type }
end
# Recent events on a record's timeline (includes user, limit 50)
@project.activity_events.timeline
# Filter by action type
@project.activity_events.by_type("status_changed")
# Find events related to a specific subject across all timelines
ActivityEvent.where(subject: @task).recent
# Events by a specific user
@project.activity_events.where(user: current_user).recent
Events created with wrong trackable — events appear on the wrong timeline. Double-check activity_trackable returns the parent entity, not self.
Missing broadcast partial routing for new trackable types — if you add a new model type and it uses a different partial, add it to the case statement in broadcast_partial. Without this, broadcasts silently fail or render the wrong template.
Forgetting to add new actions to ACTIONS constant and DISPLAY hash — validation will reject events with unrecognized actions. Always add both the action string to ACTIONS and a display entry to DISPLAY.
Using trackable: parent when you want event on the child's own timeline — if a model has its own timeline AND appears as a subject on a parent's timeline, you need two separate events or a clear decision about which timeline the event belongs to.
N+1 queries on the timeline — always use the .timeline scope which includes :user. If you add other associations to the event partial, add them to the scope.
Turbo Stream not updating — verify the turbo_stream_from tag in your view matches the stream name in the broadcast method. The convention is "#{record_type}_{id}_activity".
turbo-rails gem)User model (optional but recommended for attribution)