en un clic
rails-activity-timeline
Add polymorphic activity timelines with live Turbo Stream updates to any Rails model. Covers migration, model, concern, shared partials, broadcasting, and optional AI-generated change summaries.
Menu
Add polymorphic activity timelines with live Turbo Stream updates to any Rails model. Covers migration, model, concern, shared partials, broadcasting, and optional AI-generated change summaries.
Add Tiptap rich text editing with debounced autosave to Rails models using Stimulus. Stores markdown in text columns (not ActionText). Covers installation, Stimulus controller, shared partials, Turbo cache handling, and optional change tracking.
Implement MCP server authentication with OAuth Dynamic Client Registration (RFC 7591), Authorization Server Metadata Discovery (RFC 8414), and generalized per-agent credential support. This skill should be used when building admin UIs that let users connect to third-party MCP servers, whether they use OAuth (Linear, Sentry, Granola), bearer tokens (Render, custom APIs), or API keys. Covers the full flow: metadata discovery, client registration, PKCE authorization, token exchange, token refresh, tool sync, and credential storage patterns (shared vs per-agent for any auth type). Includes hard-won lessons from production implementation.
Apply Better Stimulus best practices for writing maintainable, reusable StimulusJS controllers following SOLID principles
| name | rails-activity-timeline |
| description | Add polymorphic activity timelines with live Turbo Stream updates to any Rails model. Covers migration, model, concern, shared partials, broadcasting, and optional AI-generated change summaries. |
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)