rails-action-text
Use when designing, implementing, reviewing, testing, or operating Rails Action Text rich-text content, Trix editors, RichText associations, sanitization, embedded attachments, Signed Global IDs, rend
SKILL.mdskills/rails-action-text/SKILL.md
GitHubRails Action Text Engineering
Purpose
Treat Action Text rich content as a persisted presentation and attachment boundary with its own security, authorization, rendering, performance, and compatibility contracts.
Action Text provides the Trix editor and persists rich content through ActionText::RichText rather than a text column on the owning model. Embedded files can use Active Storage, while non-storage attachables can be referenced through Signed Global IDs. Rails sanitizes Action Text rich content before safe HTML rendering.
Core flow:
define rich-text ownership
-> define content/format contract
-> authorize editing
-> validate/sanitize input
-> persist RichText
-> resolve attachments/attachables
-> render safely
-> preload efficiently
-> expose through API/mail/feed
-> test lifecycle/security
Activate when
- adding
has_rich_text; - adding or changing
ActionText::RichText; - adding
rich_textareaor Trix editor behavior; - changing rich-text sanitization;
- customizing Action Text rendering/layouts/partials;
- embedding Active Storage files in rich text;
- embedding Signed Global ID attachables;
- exposing rich text through APIs;
- changing rich-text edit/update authorization;
- diagnosing missing embedded attachments or unresolved attachables;
- investigating Action Text N+1 queries or rendering performance;
- localizing rich text or editor content;
- testing rich-text content, attachments, or sanitization;
- upgrading Trix/Action Text behavior.
Do not activate merely because a model contains a plain text column. Use rails-action-view, rails-active-record, or rails-i18n unless the Action Text boundary exists.
Repository inspection
Inspect before changing Action Text:
- Ruby/Rails and Action Text/Trix versions;
has_rich_textdeclarations and naming conventions;- Action Text migrations and RichText schema;
- Trix/application JavaScript integration;
rich_textareaforms and permitted attributes;- Action Text layouts and blob/attachable partials;
- sanitization configuration and HTML-safe rendering;
- authorization/policy around the owning record;
- Active Storage service and upload rules;
- Signed Global ID / GlobalID usage;
- custom attachable implementations;
- API serializers and rich-text wire format;
- localized rich-text/editor behavior;
with_rich_text_*scopes and query patterns;- cache/CDN behavior for rendered HTML;
- tests, fixtures, system tests, and JavaScript tests;
- security scanners and CSP/HTML policies.
Do not introduce a second rich-text model or editor abstraction without repository evidence.
Decision rules
- Classify the change: ownership and content contract, editing authorization and the Trix/editor boundary, sanitization and links, attachments and attachables, rendering/API/forms/localization, persistence and background work, or performance/security/testing.
- Load the matching reference below before changing behavior; attachment and embed changes need both the attachment and the sanitization reference.
- Enforce server-side sanitization and authorize every embedded object at the server, whatever the editor produced.
Critical invariants
- Never claim rich-text safety merely because the content originated in Trix.
- Never treat successful sanitization as proof that the user was authorized to reference every embedded object.
- Never let a user embed arbitrary privileged objects merely because they can construct or obtain a signed identifier.
- Do not expose internal Action Text storage tables as a public API contract.
References
Load only the reference for the boundary being changed, before changing behavior there. Each reference is self-contained and one level deep; none links to another. Consult a listed pattern from the pattern catalog only when the change needs its implementation shape.
| Load when | Reference | Covers | Patterns |
|---|---|---|---|
| a change alters rich-text ownership, the stored content contract, who may edit, or the Trix/editor boundary | references/content-and-editing.md | Ownership boundary; Content contract; Editing authorization; Trix/editor boundary | action-text-content-contract |
| a change alters allowed tags/attributes, sanitization, or link and URL handling | references/sanitization-and-links.md | Sanitization boundary; Links and URLs | action-text-sanitization-security |
| a change embeds attachments or custom attachables, resolves signed identifiers, or handles missing attachables | references/attachments-and-attachables.md | Attachments; Signed Global ID attachables; Attachables and rendering; Missing attachables | action-text-attachable-contract, action-text-attachment-authorization |
| a change renders rich text, exposes it through an API, localizes it, or accepts it through forms | references/rendering-and-apis.md | Rendering boundary; API boundary; Rich text and localization; Forms and parameters | action-text-rendering, action-text-api-boundary |
| a change alters rich-text persistence, background processing, or search/indexing | references/lifecycle-and-background-work.md | Persistence and lifecycle; Background work; Search/indexing | action-text-lifecycle |
| a change has N+1, caching, content-size, security/privacy, or test-strategy impact | references/performance-security-testing.md | Performance and N+1; Caching rendered rich text; Content size and resource limits; Security and privacy; Testing | action-text-preload-performance, action-text-testing |
Reference example
Rich text declared on the model, rendered through its sanitized fragment, never through html_safe of editor input.
class Article < ApplicationRecord
has_rich_text :body
validates :body, presence: true
end
# app/views/articles/_form.html.erb:
# <%= f.rich_text_area :body %>
#
# Rendering contract:
# article.body.to_s -> sanitized HTML fragment, safe for templates and API payloads
# Direct editor input is never trusted; sanitization happens on save, not on render.
#
# Attachments inside rich text are signed Global ID attachables:
# ActionText::Attachment carries sgid://... references resolved server-side.
Agent review checklist
- Action Text/Rails version resolved
- rich-text owner and attribute explicit
- edit authorization checked
- content contract defined
- server-side sanitization preserved
- link/URL policy reviewed
- attachment boundary composed with Active Storage
- SGID attachable authorization reviewed
- attachable rendering/fallback defined
- API representation explicit
- locale behavior explicit when applicable
- persistence/deletion lifecycle reviewed
- N+1/preloading measured
- rendered rich-text cache identity reviewed
- content/resource limits defined
- background processing semantics reviewed
- search/indexing representation defined when applicable
- security/privacy tests added
- deterministic rich-text tests exist
Anti-patterns / failure modes
- authorizing arbitrary RichText by ID;
- treating Signed Global IDs as authorization by themselves;
- trusting Trix/client-side validation;
- disabling/bypassing sanitization for convenience;
- marking raw Action Text body HTML-safe manually;
- allowing arbitrary attachable objects;
- rendering private attachments through public rich-text pages;
- duplicating Active Storage provider logic inside Action Text code;
- exposing ActionText::RichText table structure as an API;
- rendering large collections without RichText/embed preload;
- globally preloading every rich-text body/embed;
- caching private rich text without permission identity;
- assuming missing attachables never occur;
- using locale to authorize content;
- allowing unbounded rich-text/attachment payloads;
- relying on background processing without defining its failure state.
Verification
For a rich-text feature:
ownership
-> edit authorization
-> content/input contract
-> sanitization
-> attachment/attachable authorization
-> rendering contract
-> API/localization contract
-> performance/preload review
-> focused security tests
-> regression suite
For a rendering incident distinguish:
stored RichText
-> sanitizer
-> attachable resolution
-> attachment/blob rendering
-> localized presentation
-> preload/query behavior
-> cache
-> final HTML
Never claim rich-text safety merely because the content originated in Trix. Verify server-side sanitization, attachment authorization, attachable resolution, and final rendering behavior.
Source foundation
Primary Rails source:
- https://guides.rubyonrails.org/action_text_overview.html
- https://api.rubyonrails.org/classes/ActionText/RichText.html
The Rails guide documents Trix, RichText persistence, sanitized HTML rendering, Active Storage attachments, Signed Global ID attachables, custom attachment rendering, and preloading RichText/embeds.
Composed repository skills:
rails-active-recordskillrails-action-viewskillrails-validationsskillrails-active-storageskillrails-securityskillrails-security-engineeringskillrails-i18nskillrails-cachingskillrails-performanceskillruby-performanceskillrails-api-integrationskillrails-active-jobskillrails-test-engineeringskillrails-test-engineeringskill
Rails Action Text changes
For Action Text/rich-text changes:
- inspect has_rich_text declarations, Action Text/Trix versions, RichText schema, editor integration, sanitization, custom partials, attachment/attachable behavior, APIs, locale behavior, query patterns, and tests before implementation;
- authorize editing through the owning domain resource; never treat ActionText::RichText IDs or Signed Global IDs as authorization by themselves;
- preserve Action Text server-side sanitization and review custom HTML/link/attachment rendering for XSS and privacy risks;
- treat Trix/client-side validation as usability only; server-side authorization and content controls remain authoritative;
- use rails-active-storage for embedded file storage/access lifecycle rather than duplicating storage-provider logic in Action Text code;
- restrict and explicitly authorize attachable object types, tenant scope, and rendering partials; define missing-record fallback;
- keep public API representations stable and avoid exposing internal RichText/attachment table structure unless explicitly contractual;
- measure RichText and embed query behavior before choosing
with_rich_text_*preloads; use the narrowest preload justified by the rendering workload; - review locale and cache identity when rendered rich text varies by locale, permission, content version, or attachment state;
- bound rich-text size, attachment count, and transformation work where product requirements allow;
- coordinate lifecycle/cleanup semantics across RichText and Active Storage instead of assuming external object deletion is transactional;
- add negative security tests for malicious HTML/URLs and unauthorized embedded resources;
- use deterministic local/test storage and avoid live cloud-provider dependencies in ordinary CI;
- never claim rich-text safety merely because the content came from Trix; verify sanitization, authorization, attachable resolution, and final rendering.