agentsclimarketplace

Turbo morphing

Skill davidteren/hotwire-codex-skills/skills/turbo-morphing

Apply Turbo 8 page refreshes with morphing and broadcast refreshes correctly, and avoid the morphing footguns. Use when adding smooth page refreshes (turbo_refreshes_with / turbo-refresh-method morph), broadcasting live updates with broadcasts_refreshes, deciding between morph-refresh vs targeted Turbo Streams vs frames, or when morphing resets browser state (an open details or dialog element, popovers, scroll, focus, JS widgets) after a refresh. Provides a decision guide and a morph-readiness checker.From its SKILL.md

Install
npx -y skills add davidteren/hotwire-codex-skills --skill turbo-morphing

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

2 things to look at

  • 3 stars3 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
  • runs commandsInstructs the agent to run 1 command, including `scripts/lint_morphing.sh path/to/rails-app`.

SKILL.md

3.7 KB, 804 tokens by cl100k_base, as published. Nobody here has run it

Turbo 8 morphing & page refreshes

Turbo 8 added a second update model alongside targeted Turbo Streams: page refreshes with morphing. Render the current page again (e.g. submit → redirect back) and Turbo morphs only the changed DOM via idiomorph, preserving screen state. It's the simpler "happy path" — but it has sharp edges. This skill helps you pick the right tool and avoid the footguns.

Full background: references/morphing-guide.md. Real-world note: piazza-web/wip/analysis/08-turbo8-morphing-refreshes.md.

When to use

  • Adding turbo_refreshes_with method: :morph, scroll: :preserve.
  • Broadcasting live updates and considering broadcasts_refreshes vs targeted streams.
  • Morphing resets UI state (a <details> reopens for everyone, a popover closes, scroll jumps, a JS-initialized widget breaks) after a refresh/broadcast.
  • Reviewing a PR that enables morphing, especially in the global layout.

Decision guide

NeedUse
Submit-and-redirect-back with many small scattered changesmorph page refresh: turbo_refreshes_with method: :morph
Live multi-user updates, simplest server code, "good enough" fidelitybroadcasts_refreshes + turbo_stream_from @record
High-fidelity append/prepend (chat), surgical controltargeted streams (broadcast_append_to, etc.)
Reload one region during a full-page refresh / explicit .reload()frame with refresh="morph"
Morph a single element instead of replacing itturbo_stream.replace id, ..., method: :morph

Mantra (37signals): morphing is an implementation detail of a page refresh, not a new partial-update tool. Use targeted streams when you need precision.

broadcasts_refreshes (the simplification)

class Listing < ApplicationRecord
  broadcasts_refreshes      # after create/update/destroy → <turbo-stream action="refresh">
end
<%= turbo_stream_from @listing %>
<%= turbo_refreshes_with method: :morph, scroll: :preserve %>

Async via Turbo::Streams::BroadcastJob (needs a job backend), auto-debounced (only the last of a burst), and self-refreshes are deduped via X-Turbo-Request-Id.

Avoid the footguns (morphing resets browser-only state)

In order of preference:

  1. data-turbo-permanent — exclude an element from morphing entirely.
  2. turbo:before-morph-element / turbo:before-morph-attribute + a small Stimulus controller to veto a specific change (e.g. keep <details open> open).
  3. Best — keep the state on the server (a user-pref record or a URL param like ?sidebar=1) so the re-render already matches the browser; morph is then a no-op.

Don't enable turbo_refreshes_with in the global layout without auditing stateful widgets first.

Check a repo

scripts/lint_morphing.sh path/to/rails-app

Reports: whether morph is enabled (and if it's global — in the layout — which is risky), stateful widgets (<details open>, <dialog>, scrollable/JS-controlled elements) that morphing may reset without a data-turbo-permanent / before-morph guard nearby, and _later broadcast usage that needs a job backend. Heuristic grep scan that names its ceiling — guidance, not a parser.

What ships with it: 2 files

7.1 KB alongside SKILL.md, 1 of them executable

references/

scripts/

Keep looking

Skills are one crate of 325,949. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.