6832b910c2
Reported by ethie:
```mermaid
graph LR
user[ethie] -->|asks| packet[packet >w<]
```
Double-click "ethie" inside the fence → copied "hie]". Selection
shifted right by 2 at the start AND extended past `]` at the end.
Root cause: code fences (and tables / lists / blockquotes) render their
content inside a `<Box paddingLeft={2}>` nested inside the
`<CopySource>` Box. The hit-test walked up the DOM looking for a
copyRangeId and reported visualLine/col relative to THAT outer Box,
which has rect.x=0. The visual col (which includes the +2 padding) was
passed through to `simpleOffsetFor` unchanged — but simpleOffsetFor
treats col as a source col, so every char shifted +2 in source space.
Also: code fences register no per-fragment source data, so the focus
point falls through to the no-fragment path in toCopyText where the
cell-INCLUSIVE col was treated as a byte-EXCLUSIVE slice end. Last char
got dropped.
Fix (two parts):
1. copyPointHitTest tracks the deepest non-rangeId rect's X during the
walk-up and reports col relative to that (when present). visualLine
stays relative to the rangeId Box's rect.y — that's the coordinate
system the registered rowStarts / visualLineCount were measured in
(rows counted from block start, not from any sub-text element).
Inline content w/o an indented wrapper sees no change.
2. buildCopyTextFromDom now bumps the focus point's col by +1 when the
hit-test returned no sourceOffset (the fallback path). This handles
the cell-INCLUSIVE → byte-EXCLUSIVE conversion for blocks that don't
carry per-fragment source data. The fragment path already does this
bump internally via the endpoint='end' arg.
Tests:
- packages/hermes-ink/src/ink/copyPointHitTest.test.ts: new test asserts
visualLine/col reporting respects an inner padded Box's rect.x while
preserving the rangeId Box's rect.y for visualLine.
- ui-tui/src/lib/copySource/__tests__/codeFencePadding.test.ts: end-to-end
regression test reproducing ethie's exact mermaid example and
asserting the copied string is 'ethie' (not 'hie]').