Five related bugs that surfaced in a real Next.js App Router project
(EAC) all rooted in live-wrap.mjs treating source as line-anchored HTML:
1. findElement matched on raw substring anywhere, so it landed on a
className continuation line of a multi-line JSX tag whose class
happened to collide with a later target. The wrong tag got wrapped
(really, its attribute line got wrapped, producing broken JSX).
2. findClosingLine's opener regex required whitespace or `>` after the
tag name, so a bare `<section\n className="..."\n>` opener was
unrecognised; it returned `start` silently, capturing only one line.
3. buildSearchQueries only emitted `class="..."`, missing React's
`className="..."`. The full-combo query never fired in JSX, so
search silently degraded to single-class substring matching.
4. Wrapper output used `style="display: contents"` unconditionally,
which is invalid JSX (type error in strict setups, parser hazard
in production transforms).
5. --tag was ignored during the primary class search. Ambiguous class
hits inside the wrong element type weren't filtered out.
## Fixes
- New OPENER_RE `/<([A-Za-z][A-Za-z0-9]*)(?=[\s/>]|$)/` recognises
tag openers at end-of-line too.
- New findOpenerLine(lines, matchLine, tag): walks up to 10 lines
backward to the enclosing opener when the match lands on a
continuation line. Aborts the walk if it hits a different tag.
- findElement now iterates all matches (not just the first), takes
a tag parameter, and routes through findOpenerLine; wrapCli passes
--tag through.
- buildSearchQueries emits both `class="..."` and `className="..."`
for multi-class queries, and both `<tag class="..."` /
`<tag className="..."` for tag+class combos.
- Wrapper builder emits `style={{ display: "contents" }}` when
commentSyntax is JSX and `style="display: contents"` otherwise.
- findClosingLine uses the same OPENER_RE so its tag-name extraction
works on multi-line openers too.
## Tests
Five new regression tests in tests/live-wrap.test.mjs, all failing
before the fix, all passing after:
- wraps the correct <section> when a class collides with a multi-line
tag elsewhere
- emits JSX-safe style attribute ({{ }}) in .tsx files
- finds elements via className= (React) when the exact class combo is
unique there
- respects --tag to reject matches inside the wrong element type
- findClosingLine recognises an opener line where the tag sits at
end-of-line (multi-line JSX)
31/31 in tests/live-wrap.test.mjs and 54/54 in
tests/framework-fixtures.test.mjs pass.
Credit: precise bug report from the other agent in the EAC session
made diagnosis and test design straightforward.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Tests:
- tests/live-wrap.test.mjs (26 tests): unit tests for buildSearchQueries,
findElement, findClosingLine, detectCommentSyntax (20 pure function
tests) + integration tests for the full wrapCli on HTML and JSX
fixtures with temp dirs (6 tests covering wrapping, ID/class lookup,
error handling, content preservation).
- tests/live-server.test.mjs (15 tests): integration tests that start a
real server on port 8499, then test /health, /live.js, /detect.js,
/poll (timeout + auth), /events POST (validation + auth), browser→agent
event flow (POST event → poll receives it), agent→browser SSE flow
(POST reply → SSE stream delivers it), /source (read, path traversal
rejection, auth, 404).
Also:
- Added auto-execute guards to live-wrap.mjs and live-poll.mjs so they
work when run directly with `node live-wrap.mjs ...` (needed for both
skill instructions and integration tests).
- Exported buildSearchQueries, findElement, findClosingLine,
detectCommentSyntax from live-wrap.mjs for unit testing.
- Updated package.json test script to include the new test files.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>