fix(live): wrap preserves relative indent of multi-line picked elements

Companion to the prior outer-indent fix. live-wrap.mjs's
`originalLines.map(l => indent + '    ' + l.trimStart())` calls
`trimStart()` on every line, which strips ALL leading whitespace and
collapses multi-line picked elements to a uniform indent. So a 6/8/6
shape like

    <aside className="card">
      <h1 className="hero-title">Hero</h1>
    </aside>

was being reindented to 10/10/10 inside the wrapper, and on
accept/discard the round-trip restored 6/6/6 — the <h1> ended up at
its parent's depth instead of nested inside it.

Fix: extract `minLeadingSpaces(lines)` and strip only the COMMON
minimum across the picked lines before reindenting under the wrapper.
That mirrors how `deindentContent` on the accept side already works,
so wrap+accept now form a clean round-trip.

Test coverage:
- Expanded the indent regression test in live-accept.test.mjs to
  also assert the inner `<h1>` at 8-space indent and the closing
  `</aside>` at 6 — proving the relative depth survives wrap and
  discard end-to-end.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Paul Bakaus
2026-04-28 15:59:07 -07:00
co-authored by Claude Opus 4.7
parent 99e68370b9
commit a701ee613a
14 changed files with 387 additions and 43 deletions
+10 -4
View File
@@ -235,11 +235,17 @@ describe('live-accept — style-element edge cases', () => {
runAccept(tmp, ['--id', 'INDENTDISC', '--discard']);
const after = readFileSync(join(tmp, 'App.tsx'), 'utf-8');
// The aside opener should land at exactly 6 spaces — same as the
// original. Any deeper indent is the bug Bugbot flagged. (Inner indent
// loss inside the element is a separate, pre-existing wrap behavior;
// not asserted here.)
// original — and the <h1> child at 8 (preserved relative depth).
// The earlier 6/6/6 collapse was caused by `originalLines.map(l =>
// indent + ' ' + l.trimStart())` in live-wrap stripping ALL
// leading whitespace before reindenting; the fix strips only the
// COMMON minimum so the relative structure is preserved.
assert.match(after, /^ <aside className="card">$/m,
`<aside> opener must be at 6-space indent (was 8 before fix), got:\n${after}`);
`<aside> opener must be at 6-space indent (was 8 before outer-indent fix), got:\n${after}`);
assert.match(after, /^ <h1 className="hero-title">Hero<\/h1>$/m,
`<h1> child must be at 8-space indent — relative depth preserved through wrap+discard. Got:\n${after}`);
assert.match(after, /^ <\/aside>$/m,
`</aside> closer must be back at 6-space indent. Got:\n${after}`);
});
it('accept (no carbonize, raw HTML) restores at the original indent on JSX', () => {