diff --git a/docs/plans/2026-03-08-coverage-99-plus-design.md b/docs/plans/2026-03-08-coverage-99-plus-design.md
new file mode 100644
index 0000000..40ac7cc
--- /dev/null
+++ b/docs/plans/2026-03-08-coverage-99-plus-design.md
@@ -0,0 +1,44 @@
+# Coverage 99+ Design
+
+**Goal:** Raise unit-test coverage from the current post-fix level to 99%+ without changing production behavior.
+
+**Context:** The recent `preserveMarkup` work added substantial new logic under `src/Truncate/engines/markup.tsx` and `src/Truncate/markup/render.tsx`. CI focused on browser E2E coverage, so Codecov correctly reported a project-level drop. A first recovery pass already moved local coverage to `98.41%`, leaving only a handful of defensive branches uncovered.
+
+**Recommended Approach:** Add focused unit tests only, centered in `test/Markup.spec.tsx`, and avoid any production refactor. This keeps the fix surgical, preserves behavior, and directly addresses the Codecov signal instead of muting it.
+
+## Options Considered
+
+### Option A: Add focused helper and engine tests
+- Keep all changes in tests
+- Target the remaining uncovered branches directly
+- Fastest path to 99%+
+
+**Trade-off:** A few tests will exercise fairly defensive branches, so they are slightly more synthetic than top-level component tests.
+
+### Option B: Refactor helper internals for easier direct testing
+- Could make each branch easier to target
+- Might reduce test setup complexity
+
+**Trade-off:** Changes production code purely for coverage, which is unnecessary for this CI repair.
+
+### Option C: Relax or reshape coverage rules
+- Fast to make CI green
+
+**Trade-off:** Hides a real test gap and weakens the project signal. Rejected.
+
+## Design
+
+### Scope
+- Modify only `test/Markup.spec.tsx`
+- Do not modify `src/**` unless a genuine bug is discovered
+- Keep existing E2E coverage unchanged
+
+### Test strategy
+- Cover the remaining helper branches in `render.tsx`
+- Cover the remaining defensive cleanup branches in `markup.tsx`
+- Reuse the current measurement sandbox so tests stay close to the real `preserveMarkup` execution path
+
+### Success criteria
+- `pnpm vitest run test/Markup.spec.tsx` passes
+- `pnpm coverage` reports total coverage at `99%+`
+- No production behavior changes
diff --git a/docs/plans/2026-03-08-coverage-99-plus.md b/docs/plans/2026-03-08-coverage-99-plus.md
new file mode 100644
index 0000000..4669918
--- /dev/null
+++ b/docs/plans/2026-03-08-coverage-99-plus.md
@@ -0,0 +1,71 @@
+# Coverage 99+ Implementation Plan
+
+> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
+
+**Goal:** Raise project unit-test coverage to 99%+ by adding focused tests for the remaining uncovered `preserveMarkup` helper branches.
+
+**Architecture:** Keep production code unchanged and extend the existing focused spec `test/Markup.spec.tsx`. Use narrowly scoped assertions that target the remaining uncovered defensive branches in the snapshot/render helpers and markup truncation engine, then verify with the existing `vitest` coverage workflow.
+
+**Tech Stack:** React, TypeScript, Vitest, Testing Library, Sinon, Happy DOM
+
+---
+
+### Task 1: Cover remaining render helper branches
+
+**Files:**
+- Modify: `test/Markup.spec.tsx`
+- Reference: `src/Truncate/markup/render.tsx`
+
+**Step 1: Write the failing tests**
+- Add tests for `sliceSnapshotNodes` with zero or fractional remaining width.
+- Add tests for `trimLeadingWhitespace` when the first node is a line break.
+- Add tests for any still-uncovered whitespace handling branches.
+
+**Step 2: Run test to verify it fails or expands coverage needfully**
+Run: `pnpm vitest run test/Markup.spec.tsx`
+Expected: Existing suite stays green or a new assertion fails for the uncovered branch.
+
+**Step 3: Write minimal test-only implementation**
+- Adjust only test fixtures/assertions until the branch is exercised correctly.
+
+**Step 4: Run test to verify it passes**
+Run: `pnpm vitest run test/Markup.spec.tsx`
+Expected: PASS
+
+### Task 2: Cover remaining markup engine defensive branches
+
+**Files:**
+- Modify: `test/Markup.spec.tsx`
+- Reference: `src/Truncate/engines/markup.tsx`
+
+**Step 1: Write the failing tests**
+- Add a test that forces recursive cleanup of whitespace-only text and empty elements.
+- Add a test that exercises unsupported child-node cleanup in the truncation clone path.
+
+**Step 2: Run test to verify it fails or expands coverage needfully**
+Run: `pnpm vitest run test/Markup.spec.tsx`
+Expected: Targeted assertions prove the branch is now reached.
+
+**Step 3: Write minimal test-only implementation**
+- Keep the change isolated to fixture setup and expectations in `test/Markup.spec.tsx`.
+
+**Step 4: Run test to verify it passes**
+Run: `pnpm vitest run test/Markup.spec.tsx`
+Expected: PASS
+
+### Task 3: Verify project coverage
+
+**Files:**
+- Verify: `test/Markup.spec.tsx`
+
+**Step 1: Run focused tests**
+Run: `pnpm vitest run test/Markup.spec.tsx`
+Expected: PASS
+
+**Step 2: Run full coverage**
+Run: `pnpm coverage`
+Expected: Total coverage reaches at least `99%`
+
+**Step 3: Review result**
+- Confirm no production files changed.
+- Confirm only test/docs plan files were touched.
diff --git a/test/Markup.spec.tsx b/test/Markup.spec.tsx
new file mode 100644
index 0000000..ad45aac
--- /dev/null
+++ b/test/Markup.spec.tsx
@@ -0,0 +1,584 @@
+import { render } from '@testing-library/react'
+import React from 'react'
+import sinon from 'sinon'
+import { getMarkupTruncation } from '@/Truncate/engines/markup'
+import {
+ getSnapshotTextLength,
+ renderMarkupLines,
+ renderMarkupPrefix,
+ renderSnapshotNodes,
+ sliceSnapshotNodes,
+ trimLeadingWhitespace,
+ trimTrailingWhitespace,
+} from '@/Truncate/markup/render'
+import {
+ type MarkupSnapshotNode,
+ createMarkupSnapshot,
+} from '@/Truncate/markup/snapshot'
+import { separator } from './config/test-config'
+
+const createRect = (width = 0, height = 0) => {
+ const rect = new DOMRect()
+ rect.width = width
+ rect.height = height
+ return rect
+}
+
+const renderMarkup = (content: React.ReactNode) => {
+ return render(
{content}
)
+}
+
+const createSnapshotFromHtml = (html: string) => {
+ const root = document.createElement('span')
+ root.innerHTML = html
+ return createMarkupSnapshot(root, separator)
+}
+
+const createStyleDeclaration = (styles: Record) => {
+ return styles as unknown as CSSStyleDeclaration
+}
+
+const setupMeasurementSandbox = ({
+ parentWidth = 100,
+ parentPadding = 0,
+ parentBorder = 0,
+ singleLineHeight = 10,
+ zeroFullContent = false,
+ zeroSingleLine = false,
+}: {
+ parentBorder?: number
+ parentPadding?: number
+ parentWidth?: number
+ singleLineHeight?: number
+ zeroFullContent?: boolean
+ zeroSingleLine?: boolean
+} = {}) => {
+ const parent = document.createElement('div')
+ const rootNode = document.createElement('span')
+ const node = document.createElement('span')
+
+ parent.appendChild(rootNode)
+ rootNode.appendChild(node)
+ document.body.appendChild(parent)
+
+ sinon
+ .stub(parent, 'getBoundingClientRect')
+ .returns(createRect(parentWidth, 0))
+
+ sinon.stub(window, 'getComputedStyle').callsFake((element: Element) => {
+ if (element === parent) {
+ return createStyleDeclaration({
+ borderLeftWidth: `${parentBorder}px`,
+ borderRightWidth: `${parentBorder}px`,
+ paddingLeft: `${parentPadding}px`,
+ paddingRight: `${parentPadding}px`,
+ })
+ }
+
+ return createStyleDeclaration({
+ direction: 'ltr',
+ font: '16px Arial',
+ fontFamily: 'Arial',
+ fontSize: '16px',
+ fontStyle: 'normal',
+ fontWeight: '400',
+ letterSpacing: '0px',
+ lineHeight: `${singleLineHeight}px`,
+ overflowWrap: 'break-word',
+ textIndent: '0px',
+ textTransform: 'none',
+ whiteSpace: 'normal',
+ wordBreak: 'normal',
+ wordSpacing: '0px',
+ })
+ })
+
+ sinon
+ .stub(HTMLSpanElement.prototype, 'getBoundingClientRect')
+ .callsFake(function (this: HTMLSpanElement) {
+ if (this.getAttribute('aria-hidden') !== 'true') {
+ return createRect(0, 0)
+ }
+
+ const width = Number.parseFloat(this.style.width || `${parentWidth}`)
+
+ if ((this.textContent || '') === 'A') {
+ return createRect(width, zeroSingleLine ? 0 : singleLineHeight)
+ }
+
+ if (zeroFullContent) {
+ return createRect(width, 0)
+ }
+
+ const charactersPerLine = Math.max(1, Math.floor(width / 10))
+ const textLength = this.textContent?.length || 0
+ const lineBreakCount = this.querySelectorAll('br').length
+ const lineCount = Math.max(
+ 1,
+ lineBreakCount + Math.ceil(textLength / charactersPerLine),
+ )
+
+ return createRect(width, lineCount * singleLineHeight)
+ })
+
+ return { node, parent, rootNode }
+}
+
+describe('markup helpers', () => {
+ afterEach(() => {
+ sinon.restore()
+ document.body.innerHTML = ''
+ })
+
+ it('creates a snapshot for text, line breaks, elements, and ignores unsupported nodes', () => {
+ const host = document.createElement('span')
+
+ host.append('Hello\n')
+
+ const link = document.createElement('a')
+ link.setAttribute('class', 'rich-link')
+ link.setAttribute('data-id', '1')
+ link.append('world')
+ host.append(link)
+ host.append(document.createComment('ignore me'))
+ host.append(document.createElement('br'))
+
+ const emphasis = document.createElement('em')
+ emphasis.append('!')
+ host.append(emphasis)
+
+ expect(createMarkupSnapshot(host, separator)).toEqual([
+ { text: 'Hello ', type: 'text' },
+ {
+ attributes: { class: 'rich-link', 'data-id': '1' },
+ children: [{ text: 'world', type: 'text' }],
+ tagName: 'a',
+ type: 'element',
+ },
+ { type: 'line-break' },
+ {
+ attributes: {},
+ children: [{ text: '!', type: 'text' }],
+ tagName: 'em',
+ type: 'element',
+ },
+ ])
+ })
+
+ it('renders snapshot nodes with normalized attributes and skips empty elements', () => {
+ const nodes: MarkupSnapshotNode[] = [
+ {
+ attributes: {
+ class: 'rich-link',
+ href: '/docs',
+ style: 'color: red; background-color: blue; border-left-width: 1px;',
+ },
+ children: [{ text: 'Read docs', type: 'text' }],
+ tagName: 'a',
+ type: 'element',
+ },
+ { type: 'line-break' },
+ {
+ attributes: { class: 'ignored-node' },
+ children: [],
+ tagName: 'span',
+ type: 'element',
+ },
+ ]
+
+ const { container } = renderMarkup(renderSnapshotNodes(nodes))
+
+ const link = container.querySelector('a[href="/docs"]')
+ expect(link).toBeInTheDocument()
+ expect(link).toHaveClass('rich-link')
+ expect(link).toHaveStyle({
+ backgroundColor: 'blue',
+ borderLeftWidth: '1px',
+ color: 'red',
+ })
+ expect(container.querySelectorAll('br')).toHaveLength(1)
+ expect(container.querySelector('.ignored-node')).toBeNull()
+ })
+
+ it('slices nested snapshots and tracks the remaining nodes', () => {
+ const nodes: MarkupSnapshotNode[] = [
+ {
+ attributes: {},
+ children: [{ text: 'hello', type: 'text' }],
+ tagName: 'strong',
+ type: 'element',
+ },
+ { type: 'line-break' },
+ { text: 'world', type: 'text' },
+ ]
+
+ expect(sliceSnapshotNodes(nodes, 2)).toEqual({
+ remaining: 0,
+ rest: [
+ {
+ attributes: {},
+ children: [{ text: 'llo', type: 'text' }],
+ tagName: 'strong',
+ type: 'element',
+ },
+ { type: 'line-break' },
+ { text: 'world', type: 'text' },
+ ],
+ taken: [
+ {
+ attributes: {},
+ children: [{ text: 'he', type: 'text' }],
+ tagName: 'strong',
+ type: 'element',
+ },
+ ],
+ })
+ })
+
+ it('returns untouched rest nodes when slicing starts with no remaining space', () => {
+ const nodes: MarkupSnapshotNode[] = [{ text: 'hello', type: 'text' }]
+
+ expect(sliceSnapshotNodes(nodes, 0)).toEqual({
+ remaining: 0,
+ rest: nodes,
+ taken: [],
+ })
+ })
+
+ it('keeps a line-break in the rest set when less than one character is available', () => {
+ expect(sliceSnapshotNodes([{ type: 'line-break' }], 0.5)).toEqual({
+ remaining: 0.5,
+ rest: [{ type: 'line-break' }],
+ taken: [],
+ })
+ })
+
+ it('trims leading and trailing whitespace through nested elements', () => {
+ const nodes: MarkupSnapshotNode[] = [
+ {
+ attributes: {},
+ children: [{ text: ' ', type: 'text' }],
+ tagName: 'span',
+ type: 'element',
+ },
+ {
+ attributes: {},
+ children: [{ text: ' hello', type: 'text' }],
+ tagName: 'strong',
+ type: 'element',
+ },
+ { text: ' world ', type: 'text' },
+ { type: 'line-break' },
+ ]
+
+ expect(trimLeadingWhitespace(nodes)).toEqual([
+ {
+ attributes: {},
+ children: [{ text: 'hello', type: 'text' }],
+ tagName: 'strong',
+ type: 'element',
+ },
+ { text: ' world ', type: 'text' },
+ { type: 'line-break' },
+ ])
+
+ expect(trimTrailingWhitespace(nodes)).toEqual([
+ {
+ attributes: {},
+ children: [{ text: ' ', type: 'text' }],
+ tagName: 'span',
+ type: 'element',
+ },
+ {
+ attributes: {},
+ children: [{ text: ' hello', type: 'text' }],
+ tagName: 'strong',
+ type: 'element',
+ },
+ { text: ' world', type: 'text' },
+ ])
+ })
+
+ it('leaves leading line breaks untouched when trimming leading whitespace', () => {
+ const nodes: MarkupSnapshotNode[] = [
+ { type: 'line-break' },
+ { text: ' hello', type: 'text' },
+ ]
+
+ expect(trimLeadingWhitespace(nodes)).toEqual(nodes)
+ })
+
+ it('counts snapshot text length across text, line breaks, and nested elements', () => {
+ const nodes = createSnapshotFromHtml('Hi there
!')
+ expect(getSnapshotTextLength(nodes)).toBe(10)
+ })
+
+ it('renders markup lines and preserves empty intermediate lines', () => {
+ const nodes = createSnapshotFromHtml('Hello world')
+
+ const { container } = renderMarkup(
+ renderMarkupLines(nodes, ['Hello', '', 'world'], …),
+ )
+
+ expect(container.querySelectorAll('br')).toHaveLength(2)
+ expect(container.querySelector('strong')).toHaveTextContent('world')
+ expect(container.textContent).toBe('Helloworld…')
+ })
+
+ it('renders a markup prefix and trims trailing whitespace before the ellipsis', () => {
+ const nodes = createSnapshotFromHtml('Hello world ')
+
+ const { container } = renderMarkup(
+ renderMarkupPrefix(nodes, 13, …, true),
+ )
+
+ expect(container.textContent).toBe('Hello world…')
+ expect(container.querySelector('strong')).toHaveTextContent('world')
+ })
+
+ it('drops whitespace-only trailing nodes and keeps the untrimmed prefix when requested', () => {
+ const whitespaceOnlyText: MarkupSnapshotNode[] = [
+ { text: 'hello', type: 'text' },
+ { text: ' ', type: 'text' },
+ ]
+
+ expect(trimTrailingWhitespace(whitespaceOnlyText)).toEqual([
+ { text: 'hello', type: 'text' },
+ ])
+
+ const whitespaceOnlyElement: MarkupSnapshotNode[] = [
+ { text: 'hello', type: 'text' },
+ {
+ attributes: {},
+ children: [{ text: ' ', type: 'text' }],
+ tagName: 'span',
+ type: 'element',
+ },
+ ]
+
+ expect(trimTrailingWhitespace(whitespaceOnlyElement)).toEqual([
+ { text: 'hello', type: 'text' },
+ ])
+
+ const { container } = renderMarkup(
+ renderMarkupPrefix(createSnapshotFromHtml('hello '), 8, …),
+ )
+
+ expect(container.textContent).toBe('hello …')
+ })
+})
+
+describe('getMarkupTruncation', () => {
+ afterEach(() => {
+ sinon.restore()
+ document.body.innerHTML = ''
+ })
+
+ it('returns no truncation when there is no markup node', () => {
+ const result = getMarkupTruncation({
+ ellipsis: '…',
+ ellipsisNode: null,
+ fallbackDidTruncate: false,
+ fallbackVisibleTextLines: [],
+ lines: 1,
+ node: null,
+ rootNode: null,
+ separator,
+ trimWhitespace: false,
+ })
+
+ expect(result).toEqual({ didTruncate: false, result: null })
+ })
+
+ it('falls back to plain-text lines when single-line measurement is unavailable', () => {
+ const { node, rootNode } = setupMeasurementSandbox({ zeroSingleLine: true })
+ node.innerHTML = 'Hello world again'
+
+ const result = getMarkupTruncation({
+ ellipsis: …,
+ ellipsisNode: null,
+ fallbackDidTruncate: true,
+ fallbackVisibleTextLines: ['Hello world'],
+ lines: 1,
+ node,
+ rootNode,
+ separator,
+ trimWhitespace: false,
+ })
+
+ expect(result.didTruncate).toBe(true)
+
+ const { container } = renderMarkup(result.result)
+ expect(container.textContent).toBe('Hello world…')
+ expect(container.querySelector('strong')).toHaveTextContent('world')
+ })
+
+ it('falls back safely when content measurement returns zero height', () => {
+ const node = document.createElement('span')
+ node.innerHTML = 'Hello world'
+ document.body.appendChild(node)
+
+ setupMeasurementSandbox({ zeroFullContent: true })
+
+ const result = getMarkupTruncation({
+ ellipsis: '…',
+ ellipsisNode: null,
+ fallbackDidTruncate: false,
+ fallbackVisibleTextLines: [],
+ lines: 1,
+ node,
+ rootNode: null,
+ separator,
+ trimWhitespace: false,
+ })
+
+ expect(result).toEqual({ didTruncate: false, result: null })
+ })
+
+ it('keeps the original markup when the content already fits', () => {
+ const { node, rootNode } = setupMeasurementSandbox({ parentWidth: 300 })
+ node.innerHTML = 'Hello world'
+
+ const result = getMarkupTruncation({
+ ellipsis: '…',
+ ellipsisNode: null,
+ fallbackDidTruncate: true,
+ fallbackVisibleTextLines: ['ignored'],
+ lines: 2,
+ node,
+ rootNode,
+ separator,
+ trimWhitespace: false,
+ })
+
+ expect(result).toEqual({ didTruncate: false, result: null })
+ })
+
+ it('returns no truncation when the snapshot contains no visible text', () => {
+ const { node, rootNode } = setupMeasurementSandbox()
+ node.innerHTML = ''
+
+ const result = getMarkupTruncation({
+ ellipsis: '…',
+ ellipsisNode: null,
+ fallbackDidTruncate: true,
+ fallbackVisibleTextLines: ['ignored'],
+ lines: 1,
+ node,
+ rootNode,
+ separator,
+ trimWhitespace: false,
+ })
+
+ expect(result).toEqual({ didTruncate: false, result: null })
+ })
+
+ it('truncates rich content, clones the ellipsis markup, and trims break-only tails', () => {
+ const { node, rootNode } = setupMeasurementSandbox({
+ parentBorder: 1,
+ parentPadding: 4,
+ parentWidth: 90,
+ })
+ node.innerHTML = 'Hello
world again'
+
+ const ellipsisNode = document.createElement('span')
+ ellipsisNode.innerHTML = '…'
+
+ const result = getMarkupTruncation({
+ ellipsis: …,
+ ellipsisNode,
+ fallbackDidTruncate: true,
+ fallbackVisibleTextLines: ['fallback'],
+ lines: 1,
+ node,
+ rootNode,
+ separator,
+ trimWhitespace: true,
+ })
+
+ expect(result.didTruncate).toBe(true)
+
+ const { container } = renderMarkup(result.result)
+ expect(container.querySelector('a[href="/more"]')).toBeInTheDocument()
+ expect(container.textContent).toBe('Hello…')
+ expect(container.querySelectorAll('br')).toHaveLength(0)
+ })
+
+ it('removes whitespace-only trailing text before appending the ellipsis', () => {
+ const { node, rootNode } = setupMeasurementSandbox({ parentWidth: 30 })
+ node.append('Hi there')
+
+ const result = getMarkupTruncation({
+ ellipsis: '…',
+ ellipsisNode: null,
+ fallbackDidTruncate: true,
+ fallbackVisibleTextLines: ['fallback'],
+ lines: 1,
+ node,
+ rootNode,
+ separator,
+ trimWhitespace: true,
+ })
+
+ expect(result.didTruncate).toBe(true)
+
+ const { container } = renderMarkup(result.result)
+ expect(container.textContent).toBe('Hi…')
+ })
+
+ it('removes whitespace-only trailing elements before appending the ellipsis', () => {
+ const { node, rootNode } = setupMeasurementSandbox({ parentWidth: 30 })
+ node.append('Hi')
+
+ const trailingWhitespace = document.createElement('span')
+ trailingWhitespace.append(' ')
+ node.append(trailingWhitespace)
+ node.append('there')
+
+ const result = getMarkupTruncation({
+ ellipsis: '…',
+ ellipsisNode: null,
+ fallbackDidTruncate: true,
+ fallbackVisibleTextLines: ['fallback'],
+ lines: 1,
+ node,
+ rootNode,
+ separator,
+ trimWhitespace: true,
+ })
+
+ expect(result.didTruncate).toBe(true)
+
+ const { container } = renderMarkup(result.result)
+ expect(container.textContent).toBe('Hi…')
+ expect(container.querySelector('span')).toBeNull()
+ })
+
+ it('removes emptied child elements and unsupported nodes while truncating', () => {
+ const { node, rootNode } = setupMeasurementSandbox({ parentWidth: 30 })
+ node.append('H')
+
+ const emptyTail = document.createElement('span')
+ emptyTail.append(document.createComment('drop me'))
+ node.append(emptyTail)
+ node.append('i123')
+
+ const result = getMarkupTruncation({
+ ellipsis: '…',
+ ellipsisNode: null,
+ fallbackDidTruncate: true,
+ fallbackVisibleTextLines: ['fallback'],
+ lines: 1,
+ node,
+ rootNode,
+ separator,
+ trimWhitespace: false,
+ })
+
+ expect(result.didTruncate).toBe(true)
+
+ const { container } = renderMarkup(result.result)
+ expect(container.textContent).toBe('Hi1…')
+ expect(container.querySelector('span')).toBeNull()
+ })
+})