fix(live): JSX accept/discard restores at original indent (Bugbot review)

Cursor Bugbot caught this on PR #118 review:

> JSX discard/accept restores content with wrong indentation. In the JSX
> path, `indent` is captured from `lines[block.start]` — the marker comment
> line inside the wrapper div, which is indented 2 extra spaces relative
> to the original element. But `expandReplaceRange` expands the replacement
> to include the outer `<div data-impeccable-variants>` wrapper, which sits
> at the original element's indent level. `deindentContent(original, indent)`
> restores content to the marker's deeper indent, so all restored lines end
> up 2 spaces deeper than the original element was.

I'd actually noticed the symptom during the live testing session ("some
odd indentation in card-2 after discard") and dismissed it as cosmetic.
Bugbot's analysis matches exactly.

Fix: anchor the deindent base on `replaceRange.start` instead of
`block.start`. For HTML the two are identical (markers sit outside the
wrapper), so HTML is unchanged. For JSX `replaceRange.start` is the
outer `<div>` at the original element's indent — correct base.

Also dropped a duplicate `expandReplaceRange` call in handleAccept that
the earlier edit left orphaned.

Test coverage:
- Two new regression tests in live-accept.test.mjs:
  - `discard restores JSX content at the original indent` runs the
    real wrap CLI and asserts the restored <aside> opener lands at
    its original 6-space indent (was 8 before the fix).
  - `accept (no carbonize, raw HTML) restores at the original indent
    on JSX` exercises the same anchor on the accept path.
- Inner-element indent loss inside the wrapped content (`<h1>` ending
  up at the same indent as its parent `<aside>`) is a separate,
  pre-existing wrap behavior — left for a follow-up; explicitly
  noted in the test comments.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Paul Bakaus
2026-04-28 15:55:27 -07:00
co-authored by Claude Opus 4.7
parent fdb9e7c6f8
commit 99e68370b9
14 changed files with 238 additions and 53 deletions
@@ -105,11 +105,16 @@ function handleDiscard(id, lines, targetFile) {
if (!block) return { handled: false, error: 'Markers not found' };
const original = extractOriginal(lines, block);
const indent = lines[block.start].match(/^(\s*)/)[1];
const isJsx = detectCommentSyntax(targetFile).open === '{/*';
const replaceRange = expandReplaceRange(block, lines, isJsx);
// De-indent the original content back to the marker's indentation level
// Restore at the line we're actually replacing FROM, not the marker line.
// For JSX wrappers the marker comments live INSIDE the outer `<div>`, so
// `block.start` sits 2 spaces deeper than the original element. Using that
// as the deindent base would push the restored content 2 spaces too far
// right on every JSX/TSX session. `replaceRange.start` is the outer wrapper
// line, which is at the original element's indent for both HTML and JSX.
const indent = lines[replaceRange.start].match(/^(\s*)/)[1];
const restored = deindentContent(original, indent);
const newLines = [
@@ -129,9 +134,14 @@ function handleAccept(id, variantNum, lines, targetFile, paramValues) {
const block = findMarkerBlock(id, lines);
if (!block) return { handled: false, error: 'Markers not found' };
const indent = lines[block.start].match(/^(\s*)/)[1];
const commentSyntax = detectCommentSyntax(targetFile);
const isJsx = commentSyntax.open === '{/*';
// Anchor indent on the line we're replacing FROM (the outer wrapper),
// not on `block.start` — for JSX that's the marker comment 2 spaces
// deeper than the original element. See handleDiscard for the full
// rationale.
const replaceRange = expandReplaceRange(block, lines, isJsx);
const indent = lines[replaceRange.start].match(/^(\s*)/)[1];
// Extract the chosen variant's inner content
const variantContent = extractVariant(lines, block, variantNum);
@@ -187,7 +197,6 @@ function handleAccept(id, variantNum, lines, targetFile, paramValues) {
replacement.push(...restored);
}
const replaceRange = expandReplaceRange(block, lines, isJsx);
const newLines = [
...lines.slice(0, replaceRange.start),
...replacement,