Fix Live accept for Elixir templates in lib/ (#374)

* Fix Live accept for Elixir templates in lib/

Wrap and accept search the repo for impeccable variant markers. That
search skipped .ex files and the lib/ tree, so Phoenix LiveView markup
inside ~H""" blocks never matched and browser Accept returned
"Session markers not found".

Extend the same EXTENSIONS and searchDirs in live-accept.mjs and
live-wrap.mjs. Add a regression test that accepts from
lib/my_app_web/components/layouts.ex.

* Live: give the source search one owner for template extensions

The #374 fix had to patch the same hardcoded EXTENSIONS array in two
files because live-wrap.mjs and live-accept.mjs each carried their own
copy of the project source walk. The copies had already drifted: same
extension list twice, same searchDirs twice, and one realpathSync
guarded by try/catch while the other was not.

Meanwhile hook-lib.mjs had solved this properly for the design hook in
#316/#347 with a configurable `detector.extensions` and suffix matching
that handles .blade.php and .html.erb. Live never read it, so a project
that taught the hook about .heex still got 'Session markers not found'
on Accept.

- lib/template-extensions.mjs is the single owner. It holds Live's
  built-in markup list, the suffix matcher, and the detector.extensions
  config reader. hook-lib.mjs now imports its normalize/merge/match
  helpers from here instead of duplicating them, and re-exports
  matchConfiguredExtension for its existing callers.
- Live resolves built-ins PLUS detector.extensions, so teaching the hook
  about a server template teaches wrap and accept at the same time.
- live/source-search.mjs holds the walk both scripts share. Callers pass
  the one thing that actually differs (skipDirs, fileFilter). Unifying
  gives live-wrap the guarded realpathSync, so a dangling symlink in the
  tree no longer throws out of the whole wrap, and makes it skip
  .impeccable artifacts the way accept already did.
- Extensions are matched on filename suffix rather than path.extname, so
  root.html.heex and show.html.erb resolve.
- Drop .exs. Those are Elixir scripts (mix.exs, config/*.exs), never
  markup, and including them only lets a wrap query match build config.
- Fill the Elixir gap in the manual-edit paths, which kept their own
  allowlists and would have left Live half-working for Phoenix:
  live-commit-manual-edits.mjs and live-manual-edit-evidence.mjs.

Verified the round trip by hand against a Phoenix layout: wrap injects
markers into a ~H""" block in lib/**/*.ex, accept carbonizes the chosen
variant back out.

AI assistance: written with Claude Code.

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Nils Kanevad <heliumbrain@users.noreply.github.com>
Co-authored-by: Paul Bakaus <paul.bakaus@gmail.com>
Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
Nils Kanevad
2026-07-20 10:49:36 -07:00
committed by GitHub
co-authored by Nils Kanevad Paul Bakaus Claude
parent e6f3ce6d9a
commit 5d719a279a
11 changed files with 600 additions and 157 deletions
+7 -4
View File
@@ -25,11 +25,11 @@ export const SUITES = {
triggers: [
...COMMON_INFRA_PATTERNS,
/^scripts\/(?!benchmark-detector|build-browser-detector|build-extension)/,
/^skill\/(SKILL\.src\.md|agents\/|reference\/|scripts\/(cleanup-deprecated|context|context-signals|critique-storage|design-parser|hook|impeccable-paths|is-generated|lib\/provider|pin))/,
/^skill\/(SKILL\.src\.md|agents\/|reference\/|scripts\/(cleanup-deprecated|context|context-signals|critique-storage|design-parser|hook|impeccable-paths|is-generated|lib\/provider|lib\/template-extensions|pin))/,
/^site\/(pages|content|components|layouts)\//,
/^README(\.npm)?\.md$/,
/^cli\/bin\//,
/^tests\/(build|cleanup-deprecated|cli-ignores|context|context-signals|critique-storage|design-parser|docs-integrity|github-sheriff|hook|hook-build|impeccable-paths|openai-plugin|pin|shiki-theme|skills-cli|target-args|test-suites|windows-path-fix|zip)\.test\.(js|mjs)$/,
/^tests\/(build|cleanup-deprecated|cli-ignores|context|context-signals|critique-storage|design-parser|docs-integrity|github-sheriff|hook|hook-build|impeccable-paths|openai-plugin|pin|shiki-theme|skills-cli|target-args|template-extensions|test-suites|windows-path-fix|zip)\.test\.(js|mjs)$/,
/^tests\/lib\//,
],
commands: [
@@ -67,6 +67,7 @@ export const SUITES = {
'tests/pin.test.mjs',
'tests/target-args.test.mjs',
'tests/shiki-theme.test.mjs',
'tests/template-extensions.test.mjs',
'tests/test-suites.test.mjs',
'tests/zip.test.mjs',
],
@@ -109,7 +110,7 @@ export const SUITES = {
description: 'Fast live-mode unit and local-server integration tests, excluding full browser fixture sweeps.',
triggers: [
...COMMON_INFRA_PATTERNS,
/^skill\/(reference\/live\.md|scripts\/(detect-csp|lib\/is-generated|live\/|live|live-|modern-screenshot|pin|palette))/,
/^skill\/(reference\/live\.md|scripts\/(detect-csp|lib\/is-generated|lib\/template-extensions|live\/|live|live-|modern-screenshot|pin|palette))/,
/^tests\/live-/,
/^tests\/live-e2e\/(agent|agents\/llm-agent|cli-options|preactions|session|steer|ui)\.mjs$/,
/^tests\/live-e2e\/agent-insert\.test\.mjs$/,
@@ -148,6 +149,7 @@ export const SUITES = {
'tests/live-server.test.mjs',
'tests/live-session-store.test.mjs',
'tests/live-source-lock.test.mjs',
'tests/live-source-search.test.mjs',
'tests/live-target-context.test.mjs',
'tests/live-wrap.test.mjs',
'tests/live-wrap-buffer-aware.test.mjs',
@@ -163,7 +165,8 @@ export const SUITES = {
/^tests\/framework-fixtures\.test\.mjs$/,
/^skill\/scripts\/(detect-csp|live-inject|live-wrap)\.mjs$/,
/^skill\/scripts\/lib\/is-generated\.mjs$/,
/^skill\/scripts\/live\/sveltekit-adapter\.mjs$/,
/^skill\/scripts\/lib\/template-extensions\.mjs$/,
/^skill\/scripts\/live\/(source-search|sveltekit-adapter)\.mjs$/,
],
commands: [
{