Skip to content

Debug mode: Never wrap head-emitting helper output in debug spans - #1953

Draft
kcdragon wants to merge 7 commits into
marcoroth:mainfrom
kcdragon:md-skip-head-content-helpers
Draft

Debug mode: Never wrap head-emitting helper output in debug spans#1953
kcdragon wants to merge 7 commits into
marcoroth:mainfrom
kcdragon:md-skip-head-content-helpers

Conversation

@kcdragon

@kcdragon kcdragon commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

Problem

While using Herb in development, I'm seeing an error from Turbo saying that I'm loading Turbo from a <script> inside <body> despite it being inside <head> in my source code.

app/views/shared/_head.html.erb

<%# locals: () %>
<%= csrf_meta_tags %>
<%= csp_meta_tag %>
<%= viewport_meta_tag %>

<%= render "shared/seo" %>

<%= javascript_include_tag "application", "data-turbo-track": "reload", defer: true %>
<%= stylesheet_link_tag "application", media: "all", "data-turbo-track": "reload" %>

<%# Be sure to add your own custom favicons %>
<%= render "shared/favicons" %>
<%= render "shared/payments_dependencies" unless Rails.env.test? %>

<%= render "shared/google_analytics" %>
<%= render "shared/ahrefs_analytics" %>

<%= content_for(:head) %>

Screenshot 2026-08-01 at 5 16 29 PM Screenshot 2026-08-01 at 5 25 53 PM

Solution

A partial rendered into a layout's has no node in its own AST, so the element-stack exclusion in in_excluded_context? never fires and helpers like csrf_meta_tags or javascript_include_tag got wrapped in a <span style="display: contents;">. Since is invalid head content, browsers implicitly close there and push the remaining scripts/stylesheets into , breaking Turbo apps.

Add a name-based head_content_helper? skip (alongside the existing complex_rails_helper? guard) covering the standard head helpers and tag.meta/tag.link/tag.title/tag.base, and add meta, link, and base to the excluded tags for the inline-in-layout case.

Screenshot 2026-08-01 at 5 17 24 PM

Is there a better fix than this?

It seems like it would be impossible to catch all the potential helper methods that belong in <head> which is the direction this PR is going in. Maybe its good enough for now? It seemed like a fine approach since its similar to the existing check done by complex_rails_helper?(code). I'm curious if you know of a better approach.

Another option is a herb linter that disscourages <head> partials but I think those a fairly common especially in apps with multiple layouts.

A partial rendered into a layout's <head> has no <head> node in its own
AST, so the element-stack exclusion in `in_excluded_context?` never fires
and helpers like `csrf_meta_tags` or `javascript_include_tag` got wrapped
in a `<span style="display: contents;">`. Since <span> is invalid head
content, browsers implicitly close </head> there and push the remaining
scripts/stylesheets into <body>, breaking Turbo apps.

Add a name-based `head_content_helper?` skip (alongside the existing
`complex_rails_helper?` guard) covering the standard head helpers and
`tag.meta`/`tag.link`/`tag.title`/`tag.base`, and add `meta`, `link`,
and `base` to the excluded tags for the inline-in-layout case.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E16jrPfFSya7LHfBXMd5yg
@github-actions github-actions Bot added ruby Ruby source for the gem and its libraries engine Herb engine and Rails template compilation rubygem The herb RubyGem and its packaging labels Aug 1, 2026
false
end

# TODO: Rewrite using Prism Nodes once available

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added this TODO because related methods have this same TODO

kcdragon and others added 2 commits August 1, 2026 17:42
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E16jrPfFSya7LHfBXMd5yg
@kcdragon
kcdragon force-pushed the md-skip-head-content-helpers branch from f3f19cd to 5ce00a6 Compare August 1, 2026 21:43
@github-actions github-actions Bot added the rbs RBS type signatures in sig/ label Aug 1, 2026
Comment thread lib/herb/engine/debug_visitor.rb Outdated
Comment on lines +400 to +411
head_helpers = [
"csrf_meta_tags",
"csp_meta_tag",
"viewport_meta_tag",
"javascript_include_tag",
"javascript_importmap_tags",
"stylesheet_link_tag",
"stylesheet_import_tag",
"favicon_link_tag",
"auto_discovery_link_tag",
"preload_link_tag"
]

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we should add this metadata into our Action View Helper registry in config/action_view_helpers/* so we could dynamically query them.

I added the missing importmap-rails helpers in #2005

marcoroth added a commit that referenced this pull request Aug 5, 2026
@kcdragon brought up these helpers in
#1953 and I realized they were
missing. So I think it makes sense to also add them to our registry, as
`importmap-rails` is a default gem.
kcdragon and others added 3 commits August 6, 2026 22:01
Flag helpers whose output belongs in <head> (meta/link/script includes
from actionview, actioncable, importmap-rails, and turbo-rails) with
head_content: true in their registry YAMLs, and expose them through
HelperRegistry.head_content_helpers / head_content?(name) so consumers
can query the list instead of hardcoding helper names.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E16jrPfFSya7LHfBXMd5yg
Replace the hardcoded helper name list in head_content_helper? with a
pattern built from HelperRegistry.head_content_helpers. This picks up
helpers the list missed (action_cable_meta_tag, the turbo-rails meta
tag helpers, turbo_include_tags, and the remaining importmap-rails
helpers) and drops viewport_meta_tag / stylesheet_import_tag, which
don't exist in any registered gem.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E16jrPfFSya7LHfBXMd5yg
@github-actions github-actions Bot added the action-view-helpers Action View helper support and metadata label Aug 7, 2026
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E16jrPfFSya7LHfBXMd5yg
@kcdragon
kcdragon marked this pull request as draft August 7, 2026 02:50
gem: "actioncable"
output: "html"
visibility: "public"
head_content: true

@marcoroth marcoroth Aug 7, 2026

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kcdragon I think this is the best thing to do long-term!

Maybe we could reverse it to something like:

context: "head"

So other helpers could define other contexts, like:

context: "body"
context: "html"
context: "table"

There is also this other pull request which adds more schema-like metadata that could be also useful to have: #1030

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

action-view-helpers Action View helper support and metadata engine Herb engine and Rails template compilation rbs RBS type signatures in sig/ ruby Ruby source for the gem and its libraries rubygem The herb RubyGem and its packaging

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants