Skip to content

Stabilize cross-line code-span wrapping (#375) (#370) - #405

Merged
leynos merged 11 commits into
mainfrom
issue-375-370-wrapping-of-code-spans
Jul 27, 2026
Merged

Stabilize cross-line code-span wrapping (#375) (#370)#405
leynos merged 11 commits into
mainfrom
issue-375-370-wrapping-of-code-spans

Conversation

@lodyai

@lodyai lodyai Bot commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Summary

This branch makes cross-line inline-code wrapping stable in a single
mdtablefix --wrap run. It keeps a resolved prefixed span buffered until its
continuation ends, then emits the tail using the same boundary model that a
subsequent formatter run would see.

When joining a cross-line code span would exceed the configured width, the
wrapper preserves only already-conforming source boundaries inside that span.
It restores paragraph indentation, greedily reflows surrounding prose, and
continues scanning after unmatched fence runs so later differently sized spans
are still recognized. Dedicated traces expose the fallback, prefix mismatch,
and tail-reflow decisions without recording document content.

Closes #375.

Closes #370.

Review walkthrough

Validation

  • Targeted code-span regressions: 8 passed
  • Targeted code-span properties: 2 passed
  • make check-fmt: passed
  • make typecheck: passed
  • make lint: passed
  • make test: passed
  • make markdownlint: passed with 0 errors
  • make nixie: passed
  • mbake validate Makefile: passed
  • coderabbit review --agent: passed with 0 findings
  • git diff --check: passed

References

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry @LodyAI[bot], you have reached your weekly rate limit of 500000 diff characters.

Please try again later or upgrade to continue using Sourcery

@coderabbitai

coderabbitai Bot commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Rework cross-line inline-code wrapping so joined spans reflow trailing prose in one pass, continuation-prefix mismatches fall back to normal handling, and overlong spans preserve conforming authored boundaries. Add regression, property, architecture, user-guide, and changelog coverage.

Changes

Inline-code wrapping

Layer / File(s) Summary
Pending continuation state
src/wrap/paragraph.*, src/wrap/*_tests.rs
Define pending-prefix state, continuation modes, tail-reflow rules, prefix helpers, stable emission, and hard-break handling.
Continuation matching and tail reflow
src/wrap.rs, src/wrap/continuation.rs
Validate continuation prefixes, fall back on mismatches, and conditionally flush buffered paragraphs.
Span boundary fallback and validation
src/wrap/paragraph/spanning_code.rs, tests/wrap/*, tests/wrap_code_span_reflow_properties.rs
Preserve conforming boundaries for overlong spans, reflow surrounding prose, and validate width conformance and idempotence.
Wrapping documentation and release notes
docs/*.md, CHANGELOG.md
Document cross-line span handling, continuation state, atomic wrapping rules, and the related fixes.

Sequence Diagram(s)

sequenceDiagram
  participant Input
  participant wrap_text
  participant ParagraphWriter
  participant spanning_code
  participant Output
  Input->>wrap_text: provide markdown lines
  wrap_text->>ParagraphWriter: process pending continuation
  ParagraphWriter->>spanning_code: inspect cross-line code span
  spanning_code-->>ParagraphWriter: return fitted or preserved source lines
  ParagraphWriter->>Output: emit wrapped paragraph
Loading

Possibly related PRs

Suggested labels: Issue

Poem

Join the code span, then reflow the tail,
Keep authored lines within the rail.
Let prefixes march in harmony,
Preserve soft breaks faithfully.
Wrap once, wrap steady—
No second-pass déjà vu.

🚥 Pre-merge checks | ✅ 20
✅ Passed checks (20 passed)
Check name Status Explanation
Title check ✅ Passed Match the main change and include both linked issue references.
Description check ✅ Passed Stay on-topic and describe the cross-line inline-code wrapping fix.
Linked Issues check ✅ Passed Satisfy #375 and #370 by making wrap idempotent and preserving conforming span boundaries.
Out of Scope Changes check ✅ Passed Keep the docs, tests, and refactors tied to the wrapping fix; no unrelated changes stand out.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Testing (Overall) ✅ Passed New regression, snapshot, and property tests hit idempotence, width guards, overlong spans, hard breaks, indentation, and unmatched fences with exact-output oracles.
User-Facing Documentation ✅ Passed PASS: docs/users-guide.md documents the new --wrap behaviour, including cross-line inline-code reflow, overlong-span fallback, and prefixed-context rules for end users.
Developer Documentation ✅ Passed Confirm docs/developers-guide.md and docs/architecture.md document the new wrapping state, and docs/state-machine-abstractions-roadmap.md checks off 2.1.1.
Module-Level Documentation ✅ Passed PASS: A repo-wide audit found 0 Rust files missing module-level docs, and the changed wrap modules all carry clear top-level //! descriptions.
Testing (Unit And Behavioural) ✅ Passed PASS: New integration and property tests hit the public wrap_text boundary, and existing CLI wrap tests cover end-to-end --wrap behaviour; helper unit tests cover edge cases and invariants.
Testing (Property / Proof) ✅ Passed Add proptest coverage for cross-line code-span invariants and idempotence; no substantive proof obligation is introduced.
Testing (Compile-Time / Ui) ✅ Passed No compile-time surface changed; the PR adds focused rstest/proptest coverage for wrap output and idempotence, which fits this text-formatting change.
Unit Architecture ✅ Passed PASS: the new helpers keep mutation and fallback explicit; no ambient I/O, clocks, globals, or hidden side-effects were introduced.
Domain Architecture ✅ Passed The change stays within pure Markdown wrapping logic and tests/docs; no transport, storage, env, or adapter concerns leak into the core modules.
Observability ✅ Passed Verify trace! coverage at prefix mismatch, tail-reflow, width fallback, and span-lookup misses; keep fields bounded and payload-free.
Security And Privacy ✅ Passed Touched docs, tests, and wrapping code add no secrets, auth logic, unsafe sinks, or over-broad permissions; traces log only metadata, not document content.
Performance And Resource Use ✅ Passed PASS: the new wrapping paths stay bounded to paragraph-sized buffers; the added joins, scans, and Vecs are one-off per flush, with no unbounded loops or I/O.
Concurrency And State ✅ Passed PASS: wrap_text owns ParagraphState/ParagraphWriter locally; no mutexes, atomics, async tasks, or shared mutable state appear in the changed code.
Architectural Complexity And Maintainability ✅ Passed Private paragraph helpers split a real wrapping seam; no new public APIs, cycles, or speculative frameworks appear.
Rust Compiler Lint Integrity ✅ Passed No lint suppressions or artificial anchors were added; new helpers/re-exports are used throughout wrap/tests, and the lone clone just returns the stored prefix for later reuse.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch issue-375-370-wrapping-of-code-spans

Comment @coderabbitai help to get the list of available commands.

@leynos
leynos marked this pull request as ready for review July 22, 2026 10:13
chatgpt-codex-connector[bot]

This comment was marked as resolved.

@pandalump

Copy link
Copy Markdown
Collaborator

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot added the Issue label Jul 22, 2026
coderabbitai[bot]

This comment was marked as resolved.

@leynos

leynos commented Jul 22, 2026

Copy link
Copy Markdown
Owner

@coderabbitai Have the following failed checks now been resolved?

If further work is required, please provide an AI agent prompt for the remaining work to be done to address these failures.

Do not treat warnings as optional or aspirational. Where a change is out of scope for this PR, propose a GitHub issue unless one exists already. (Treat documentation and validation coverage as in scope).

❌ Failed checks (1 error, 2 warnings)

Check name Status Explanation Resolution
Testing (Overall) ❌ Error New tests miss key cases: the property test never forces an over-width joined span, and no code-span regressions cover blockquote/footnote continuations. Constrain the generator so the joined span exceeds width, assert the fallback preserves input, and add concrete blockquote and footnote code-span regressions for the new prefixed paths.
Developer Documentation ⚠️ Warning docs/developers-guide.md still says cross-line code spans are emitted atomically; it omits the new pending/spanning_code helpers. Update docs/developers-guide.md to document the new wrap state helpers and boundary-preserving fallback, then tick any completed roadmap item such as 2.1.1.
Observability ⚠️ Warning New wrap fallbacks and tail_reflow transitions are silent; the overlong-span preservation path and prefix-mismatch flush add no dedicated logs, metrics, or traces. Add trace events at the new decision boundaries with stable fields (mode, width, boundary, line count) and log the tail_reflow transition and silent fallback cases.

@coderabbitai

This comment was marked as resolved.

@leynos

leynos commented Jul 22, 2026

Copy link
Copy Markdown
Owner

@coderabbitai resume

@coderabbitai

coderabbitai Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Reviews resumed.

@coderabbitai coderabbitai Bot added the Roadmap label Jul 22, 2026
coderabbitai[bot]

This comment was marked as resolved.

@coderabbitai coderabbitai Bot removed the Roadmap label Jul 22, 2026
coderabbitai[bot]

This comment was marked as resolved.

@leynos

leynos commented Jul 23, 2026

Copy link
Copy Markdown
Owner

@coderabbitai resume

@coderabbitai

coderabbitai Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Reviews resumed.

@lodyai
lodyai Bot force-pushed the issue-375-370-wrapping-of-code-spans branch from 6b56c9c to 6440976 Compare July 23, 2026 13:02
@coderabbitai coderabbitai Bot added the Roadmap label Jul 23, 2026
coderabbitai[bot]

This comment was marked as resolved.

@buzzybee-df12

Copy link
Copy Markdown

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot removed the Roadmap label Jul 23, 2026
coderabbitai[bot]

This comment was marked as resolved.

@lodyai
lodyai Bot force-pushed the issue-375-370-wrapping-of-code-spans branch from 02bd624 to bd71fe9 Compare July 24, 2026 00:49
@leynos

leynos commented Jul 24, 2026

Copy link
Copy Markdown
Owner

@coderabbitai Have the following now been resolved?

src/wrap/paragraph.rs (1)

206-212: 🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift
Preserve hard-break boundaries during tail reflow.
Do not join already-rendered tail lines with a literal space. This moves trailing double-space or backslash hard breaks into the middle of the reconstructed string, so wrap_preserving_code cannot preserve their line-break semantics. Keep hard-break boundaries as separate segments and add a regression covering trailing prose with an authored hard break.

🤖 Detailed instructions

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @src/wrap/paragraph.rs around lines 206 - 212, Update the tail reflow logic
around lines.collect and wrap_preserving_code to preserve hard-break boundaries
instead of joining rendered lines with a literal space. Reconstruct and wrap
each boundary-separated segment independently while retaining the existing
continuation_prefix behavior, and add a regression test covering trailing prose
with an authored double-space or backslash hard break.

Source: MCP tools

@coderabbitai

This comment was marked as resolved.

@buzzybee-df12

Copy link
Copy Markdown

@coderabbitai review

@buzzybee-df12

Copy link
Copy Markdown

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 26, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@leynos

leynos commented Jul 26, 2026

Copy link
Copy Markdown
Owner

@coderabbitai resume

@leynos

leynos commented Jul 26, 2026

Copy link
Copy Markdown
Owner

@coderabbitai Have the following now been resolved?

docs/developers-guide.md (1)

941-1092: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win
Delete the duplicated documentation block.
Remove the repeated ## 1. Stateful pipeline helpers section beginning at Line 941 through the duplicate ## 3. Breaks module section at Line 1084. Retain the first copy at Lines 789-940 and preserve blank lines around its headings. This currently fails MD024 and MD022, so make markdownlint cannot pass.
Triage: [type:syntax/md] [type:docstyle]

🤖 Detailed instructions

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @docs/developers-guide.md around lines 941 - 1092, Delete the duplicated
documentation block beginning with “## 1. Stateful pipeline helpers” and ending
after the duplicate “## 3. Breaks module – Cow allocation strategy” section,
while retaining the earlier copy. Preserve blank lines around the remaining
headings so Markdownlint rules MD024 and MD022 pass.

Sources: Coding guidelines, Path instructions, Pipeline failures

@coderabbitai

coderabbitai Bot commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '== Relevant headings and their line numbers =='
rg -n -C 2 \
  '^## (1\. Stateful pipeline helpers|2\. Test infrastructure|3\. Breaks module . Cow allocation strategy)$|^### ' \
  docs/developers-guide.md || true

echo
echo '== Earlier retained section (reported Lines 789–940) =='
nl -ba docs/developers-guide.md | sed -n '770,950p'

echo
echo '== Reported duplicate range and following context =='
nl -ba docs/developers-guide.md | sed -n '930,1110p'

echo
echo '== Exact occurrence counts for duplicated section headings =='
for heading in \
  '## 1. Stateful pipeline helpers' \
  '## 2. Test infrastructure' \
  '## 3. Breaks module – Cow allocation strategy'
do
  printf '%s: ' "$heading"
  grep -Fxc "$heading" docs/developers-guide.md || true
done

echo
echo '== Markdownlint configuration and changed documentation diff =='
fd -HI -t f -g '.markdownlint*' -g 'markdownlint*' -g 'Makefile' . -x sh -c '
  echo "--- $1"
  sed -n "1,220p" "$1"
' sh {} || true
git diff --unified=20 HEAD^ HEAD -- docs/developers-guide.md || true

Length of output: 5491


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '== Relevant top-level heading occurrences =='
for heading in \
  '## 1. Stateful pipeline helpers' \
  '## 2. Test infrastructure' \
  '## 3. Breaks module – Cow allocation strategy'
do
  printf '%s: ' "$heading"
  awk -v heading="$heading" '$0 == heading { count++ } END { print count + 0 }' \
    docs/developers-guide.md
done

echo
echo '== Relevant section, with line numbers =='
awk 'NR >= 770 && NR <= 1110 { printf "%6d\t%s\n", NR, $0 }' \
  docs/developers-guide.md

echo
echo '== Heading spacing violations in the relevant section =='
awk '
  NR >= 770 && NR <= 1110 {
    lines[NR] = $0
  }
  END {
    for (n = 771; n < 1110; n++) {
      if (lines[n] ~ /^#{1,6} / && lines[n - 1] != "") {
        printf "Missing blank line before heading at Line %d: %s\n", n, lines[n]
      }
      if (lines[n] ~ /^#{1,6} / && lines[n + 1] != "") {
        printf "Missing blank line after heading at Line %d: %s\n", n, lines[n]
      }
    }
  }
' docs/developers-guide.md

echo
echo '== Current PR diff for the document =='
git diff --unified=12 HEAD^ HEAD -- docs/developers-guide.md || true

echo
echo '== Markdownlint targets/configuration references =='
rg -n -C 3 --glob 'Makefile' --glob '.markdownlint*' --glob 'markdownlint*' \
  'markdownlint|MD022|MD024' . || true

Length of output: 26318


@leynos Mark this finding resolved.

  • docs/developers-guide.md now contains exactly one copy of each affected top-level section:
    • ## 1. Stateful pipeline helpers at Line 788
    • ## 2. Test infrastructure at Line 870
    • ## 3. Breaks module – Cow allocation strategy at Line 931
  • The PR diff deletes the duplicated earlier block and retains the intended copy.
  • The retained section has blank lines before and after every checked heading; no MD022-style spacing violations were found.
  • The duplicate headings that triggered MD024 are absent.

Run make markdownlint in CI or a local checkout to complete runtime validation; this inspection environment cannot execute repository commands.

@leynos

leynos commented Jul 26, 2026

Copy link
Copy Markdown
Owner

@coderabbitai Have the following now been resolved?

src/wrap/paragraph.rs (2)

355-358: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Restore the hard-break guard removal — top-level paragraphs still lose Markdown hard breaks.
line_break_parts strips the trailing two-space marker before this loop runs, so ensure_trailing_hard_break_on_last_line() is the only place that can restore it. Gating that call on !state.indent.is_empty() means an unindented (top-level) paragraph segment ending in a hard break never gets the marker back. Drop the guard.

🐛 Proposed fix
if *hard_break {
                 self.push_wrapped_segment(&state.indent, &segment);
-                if !state.indent.is_empty() {
-                    self.ensure_trailing_hard_break_on_last_line();
-                }
+                self.ensure_trailing_hard_break_on_last_line();
                 segment.clear();
             }

Add a regression for an unindented paragraph ending in two trailing spaces.
Based on learnings, "Preserve trailing spaces that represent hard line breaks on the final wrapped line."

🤖 Detailed instructions

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @src/wrap/paragraph.rs around lines 355 - 358, Remove the state.indent guard
around ensure_trailing_hard_break_on_last_line() in the paragraph wrapping loop
so it runs for both indented and top-level segments. Add a regression test
covering an unindented paragraph whose final wrapped line ends with two trailing
spaces, preserving the Markdown hard-break marker.

Source: Learnings
202-246: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win
Extract the duplicated hard-break-marker check and tail-emission block.
Lines 213-222 re-derive the exact trailing-marker parity rule that ensure_trailing_hard_break_on_last_line already implements (lines 248-261), and lines 230-234/241-245 duplicate the wrap_preserving_code + continuation-prefix emission. Two live copies of the parity rule will drift — fold both into named helpers.

♻️ Proposed extraction
+fn hard_break_marker_len(line: &str) -> usize {
+    if line.ends_with("  ") {
+        return 2;
+    }
+    let trailing_backslashes = line
+        .chars()
+        .rev()
+        .take_while(|character| *character == '\\')
+        .count();
+    usize::from(trailing_backslashes % 2 == 1)
+}

As per coding guidelines, "Reuse equivalent in-repository implementations... instead of creating parallel or bespoke implementations."

🤖 Detailed instructions

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @src/wrap/paragraph.rs around lines 202 - 246, Refactor the paragraph
wrapping flow around the visible loop and
ensure_trailing_hard_break_on_last_line: reuse that existing helper for trailing
hard-break-marker detection instead of reimplementing the backslash-parity and
double-space checks. Extract the repeated wrap_preserving_code plus
continuation_prefix emission, including marker restoration and tail_segment
clearing, into named helpers and use them for both emission sites while
preserving current output.

Source: Coding guidelines

src/wrap/paragraph/spanning_code.rs (2)

49-66: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win
Derive found_overlong_span from the collected groups instead of mutating inside map.
The closure mutates captured state, so the check at line 64 only works because collect happens to drive the iterator eagerly. A future switch to a lazy consumer breaks it silently.

♻️ Proposed fix
-    let mut found_overlong_span = false;
     let groups = hard_break_groups(segments)
         .map(|group| {
             let (joined, boundaries) = join_with_boundaries(group);
             let spans = if group.len() < 2 || group.iter().any(|(line, _)| line.width() > available)
             {
                 Vec::new()
             } else {
                 overlong_code_spans_crossing_boundaries(&joined, &boundaries, available)
             };
-            found_overlong_span |= !spans.is_empty();
             let has_hard_break = group.last().is_some_and(|(_, hard_break)| *hard_break);
             (joined, spans, has_hard_break)
         })
         .collect::<Vec<_>>();
-    if !found_overlong_span {
+    if groups.iter().all(|(_, spans, _)| spans.is_empty()) {
         return None;
     }

As per coding guidelines, "Separate queries, commands, fallible operations, and side effects. Queries must be read-only."

🤖 Detailed instructions

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @src/wrap/paragraph/spanning_code.rs around lines 49 - 66, Remove the mutable
found_overlong_span update from the hard_break_groups map closure, then derive
it from the collected groups after collect by checking whether any group
contains a non-empty spans collection. Keep the existing early return and group
construction behavior unchanged.

Source: Coding guidelines
188-195: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win
Trace the miss path when the span cannot be located in wrapped output.
A miss here silently abandons boundary preservation and emits an over-width line without any diagnostic. Content-based lookup can miss whenever wrapping alters the span text or an earlier identical span was already split — record it.

♻️ Proposed trace
else {
+        trace!(
+            mode = "preserve_authored_boundaries",
+            boundary = "span_not_located",
+            span_len = span_text.len(),
+            line_count = lines.len(),
+            "skipped boundary preservation because the span was absent from wrapped output"
+        );
         return;
     };

As per coding guidelines, "Operationally significant changes must provide meaningful, bounded, non-sensitive logs, metrics, tracing across relevant boundaries."

🤖 Detailed instructions

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @src/wrap/paragraph/spanning_code.rs around lines 188 - 195, The span lookup
miss in the wrapping logic silently returns without diagnostics. Update the
find_map miss path in the surrounding span-boundary handling to emit a
bounded, non-sensitive trace containing enough context to identify the failed
lookup, while preserving the existing early return and avoiding logging full
content or sensitive data.

Source: Coding guidelines

tests/wrap_code_span_reflow_properties.rs (1)

9-24: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win
Strengthen the fixed-point property beyond a bare second pass.
A wrapper that emitted the whole item as one over-long line, or dropped the tail entirely, would also satisfy wrap_text(&once, width) == once. Pin the first-pass behaviour too: the rejoined span must land on a single line and the tail words must survive.

💚 Proposed strengthening
let once = wrap_text(&input, width);
+        prop_assert!(
+            once.iter()
+                .any(|line| line.contains(&format!("`{code_first} {code_second}`")))
+        );
+        prop_assert!(once.join("\n").contains(&tail));
         prop_assert_eq!(wrap_text(&once, width), once);

As per coding guidelines, "tests must not be vacuous, over-mocked, tautological, or implementation-detail-focused."

🤖 Detailed instructions

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @tests/wrap_code_span_reflow_properties.rs around lines 9 - 24, Strengthen
prefixed_cross_line_code_span_with_tail_reaches_a_fixed_point by asserting the
first wrapped result preserves all tail words and reflows the joined code span
onto a single line. Keep the existing fixed-point assertion, and derive the
checks from the wrapped output without relying on implementation-specific
formatting.

Source: Coding guidelines

tests/wrap/code_span_reflow.rs (1)

95-147: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win
Assert idempotence in these three regressions too.
prose_outside_overlong_spanning_code_is_greedily_reflowed re-runs wrap_text on its own output; these three do not, even though the unmatched-fence and indent-preservation paths are exactly where a second pass previously drifted.

💚 Proposed additions
assert!(lines_conform(&output));
+    assert_eq!(wrap_text(&output, WRAP_COLS), output);
 }

As per coding guidelines, "New functionality and behavioral changes require substantive tests that would fail for plausible incorrect implementations."

🤖 Detailed instructions

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @tests/wrap/code_span_reflow.rs around lines 95 - 147, Add idempotence checks
to all three regression
tests—later_differently_sized_fence_preserves_overlong_span_boundaries,
overlong_spanning_code_preserves_paragraph_indent, and
mixed_hard_break_groups_preserve_eligible_span_boundaries—by reapplying
wrap_text to each test’s output and asserting the second result equals the
first, while preserving the existing boundary and conformance assertions.

Source: Coding guidelines

src/wrap/paragraph_tests.rs (1)

39-98: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win
Parameterise the three continuation-prefix scenarios with #[rstest(...)].
The quoted-list block is a third copy of the same arrange/act/assert shape, differing only in width, prefix, rest, repeat_prefix, outer_prefix, and expectation. A failure in the first scenario hides the other two.

♻️ Proposed parameterisation
-#[test]
-fn handle_prefix_line_can_repeat_or_change_the_continuation_prefix() {
-    let mut out = Vec::new();
-    let mut writer = ParagraphWriter::new(&mut out, 14);
-    ...
-}
+#[rstest]
+#[case::task_list(14, "- [ ] ", "alpha beta", false, None, &["- [ ] alpha", "      beta"])]
+#[case::blockquote(10, "> ", "alpha beta gamma", true, None, &["> alpha", "> beta", "> gamma"])]
+#[case::quoted_list(10, "> - ", "alpha beta gamma", false, Some("> "), &["> - alpha", ">   beta", ">   gamma"])]
+fn handle_prefix_line_can_repeat_or_change_the_continuation_prefix(
+    #[case] width: usize,
+    #[case] prefix: &str,
+    #[case] rest: &str,
+    #[case] repeat_prefix: bool,
+    #[case] outer_prefix: Option<&str>,
+    #[case] expected: &[&str],
+) {
+    let mut out = Vec::new();
+    let mut writer = ParagraphWriter::new(&mut out, width);
+    let mut state = ParagraphState::default();
+    writer.handle_prefix_line(
+        &mut state,
+        &PrefixLine {
+            prefix: Cow::Borrowed(prefix),
+            rest,
+            repeat_prefix,
+            outer_prefix: outer_prefix.map(Cow::Borrowed),
+        },
+    );
+    assert_eq!(out, expected.iter().map(ToString::to_string).collect::<Vec<_>>());
+}

As per coding guidelines, "Replace duplicated Rust tests with #[rstest(...)] parameterized cases."

🤖 Detailed instructions

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @src/wrap/paragraph_tests.rs around lines 39 - 98, Parameterize
handle_prefix_line_can_repeat_or_change_the_continuation_prefix with rstest
cases instead of maintaining three duplicated arrange/act/assert blocks. Make
each case supply the writer width, PrefixLine fields, and expected output for
the plain list, repeated quote, and quoted-list scenarios, while preserving all
existing assertions and behavior.

Source: Coding guidelines

@coderabbitai

coderabbitai Bot commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '== Relevant outlines =='
ast-grep outline src/wrap/paragraph.rs --items all
ast-grep outline src/wrap/paragraph/spanning_code.rs --items all
ast-grep outline src/wrap/paragraph_tests.rs --items all
ast-grep outline tests/wrap/code_span_reflow.rs --items all
ast-grep outline tests/wrap_code_span_reflow_properties.rs --items all

echo
echo '== Paragraph hard-break and stable-tail paths =='
rg -n -C 18 \
  'append_stable_pending_prefix|ensure_trailing_hard_break_on_last_line|hard_break_marker_len|tail_reflow|line_break_parts|push_wrapped_segment' \
  src/wrap/paragraph.rs

echo
echo '== Spanning-code group detection and lookup miss trace =='
rg -n -C 16 \
  'found_overlong_span|hard_break_groups|collect::<Vec|spans\.is_empty|find_map|span_not_located|preserve_authored_boundaries|trace!' \
  src/wrap/paragraph/spanning_code.rs

echo
echo '== Property assertions for first-pass span/tail behaviour =='
sed -n '1,180p' tests/wrap_code_span_reflow_properties.rs

echo
echo '== Code-span regression idempotence coverage =='
rg -n -C 16 \
  'fn later_differently_sized_fence_preserves_overlong_span_boundaries|fn overlong_spanning_code_preserves_paragraph_indent|fn mixed_hard_break_groups_preserve_eligible_span_boundaries|assert_eq!\(wrap_text' \
  tests/wrap/code_span_reflow.rs

echo
echo '== Continuation-prefix test parameterization =='
sed -n '1,150p' src/wrap/paragraph_tests.rs
rg -n -C 4 'rstest|handle_prefix_line_can_repeat_or_change_the_continuation_prefix|case::|use rstest' \
  src/wrap/paragraph_tests.rs Cargo.toml

echo
echo '== Current diff for all reported files =='
git diff --unified=35 HEAD^ HEAD -- \
  src/wrap/paragraph.rs \
  src/wrap/paragraph/spanning_code.rs \
  src/wrap/paragraph_tests.rs \
  tests/wrap/code_span_reflow.rs \
  tests/wrap_code_span_reflow_properties.rs

Length of output: 62735


@leynos Mark all seven findings resolved based on static inspection of the current PR revision.

Finding Resolution
Top-level hard-break restoration src/wrap/paragraph.rs now calls ensure_trailing_hard_break_on_last_line() unconditionally after every hard-break-delimited buffered segment. top_level_paragraph_preserves_trailing_spaces_hard_break covers the unindented trailing-double-space case and checks idempotence.
Duplicate tail marker/emission logic trailing_hard_break_marker_len() now owns the double-space and odd-backslash parity detection. emit_tail_segment() owns wrapping, continuation-prefix emission, marker restoration, and segment clearing.
Eager mutation within map conforming_source_lines_for_overlong_span() now collects groups first, then derives found_overlong_span with groups.iter().any(...).
Silent span-lookup miss preserve_span_boundaries() now emits a bounded trace! event for boundary = "span_lookup_miss", including mode, width, line count, and span width without content logging.
Vacuous fixed-point property prefixed_cross_line_code_span_with_tail_reaches_a_fixed_point now asserts the joined inline-code span appears on one output line and verifies every generated tail word survives before asserting fixed-point behaviour.
Missing idempotence checks The unmatched-fence, indentation-preservation, and mixed-hard-break-group regressions each now assert that a second wrap_text pass equals the first output.
Duplicated continuation-prefix test setup handle_prefix_line_can_repeat_or_change_the_continuation_prefix now uses #[rstest] cases for plain list, repeated blockquote, and non-repeating blockquote-list prefixes, including outer_prefix: Some("> ").

Run the relevant Rust tests, formatter, and lint commands in CI or a local checkout to complete runtime validation; this inspection environment only supports static analysis.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/wrap/paragraph.rs`:
- Around line 433-441: Extract the tail-reflow and hard-break functions
append_stable_pending_prefix, emit_tail_segment, trailing_hard_break_marker_len,
and ensure_trailing_hard_break_on_last_line from paragraph.rs into a dedicated
submodule, updating visibility, imports, and call sites as needed while
preserving behavior. Keep paragraph.rs under the 400-line limit.
- Around line 232-248: Split the oversized paragraph-wrapping implementation
into smaller focused modules, moving related functionality from the surrounding
paragraph code while preserving behavior and interfaces. Keep emit_tail_segment
and its callers working unchanged, and ensure each resulting module remains
cohesive and within the 400-line limit.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 6f8d574b-e4b4-4511-a2a3-6aca019a513f

📥 Commits

Reviewing files that changed from the base of the PR and between 67e8418 and 68128f3.

📒 Files selected for processing (8)
  • docs/architecture.md
  • docs/developers-guide.md
  • src/wrap/paragraph.rs
  • src/wrap/paragraph/spanning_code.rs
  • src/wrap/paragraph_tests.rs
  • tests/wrap/code_span_reflow.rs
  • tests/wrap/hard_line_breaks.rs
  • tests/wrap_code_span_reflow_properties.rs
💤 Files with no reviewable changes (1)
  • docs/developers-guide.md

Comment thread src/wrap/paragraph.rs Outdated
Comment thread src/wrap/paragraph.rs Outdated
@coderabbitai

coderabbitai Bot commented Jul 26, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Reviews resumed.

@lodyai
lodyai Bot force-pushed the issue-375-370-wrapping-of-code-spans branch from 6510fcb to 1cb6628 Compare July 26, 2026 22:02
@buzzybee-df12

Copy link
Copy Markdown

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 27, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@CHANGELOG.md`:
- Around line 24-29: Update the changelog entry for issue `#370` to qualify that
authored line boundaries are preserved only when the joined inline-code span
exceeds the wrap width and every authored source line already fits within that
width; retain the existing intent about preventing new MD013 violations.

In `@src/wrap/paragraph/spanning_code.rs`:
- Around line 241-247: Update restore_last_hard_break to use the shared
hard-break parity helper instead of checking only for a two-space suffix,
preserving existing hard-break markers such as a final backslash. Move the
helper from tail_reflow.rs into a shared location and call it from both
restore_last_hard_break and the existing tail-reflow path.

In `@src/wrap/paragraph/tail_reflow.rs`:
- Around line 69-75: Add a Rustdoc comment above
ensure_trailing_hard_break_on_last_line documenting that it mutates the last
emitted line in place, does nothing when the output is empty, and considers an
odd trailing-backslash run an existing hard break. Use the required /// syntax.

In `@tests/wrap_code_span_reflow_properties.rs`:
- Around line 22-25: Move the UnicodeWidthStr::width(joined_span.as_str()) <=
width - 2 prop_assume! check to immediately after constructing input and before
calling wrap_text in the first property. Keep the existing joined_span
construction and assertion unchanged, matching the assumption-before-action
ordering used by the second property.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 75f94c59-4a55-4b24-9eee-8355aeec1109

📥 Commits

Reviewing files that changed from the base of the PR and between 67e8418 and 1cb6628.

📒 Files selected for processing (17)
  • CHANGELOG.md
  • docs/architecture.md
  • docs/developers-guide.md
  • docs/state-machine-abstractions-roadmap.md
  • docs/users-guide.md
  • src/wrap.rs
  • src/wrap/continuation.rs
  • src/wrap/continuation_tests.rs
  • src/wrap/paragraph.rs
  • src/wrap/paragraph/pending.rs
  • src/wrap/paragraph/spanning_code.rs
  • src/wrap/paragraph/tail_reflow.rs
  • src/wrap/paragraph_tests.rs
  • tests/wrap/code_span_reflow.rs
  • tests/wrap/hard_line_breaks.rs
  • tests/wrap/mod.rs
  • tests/wrap_code_span_reflow_properties.rs

Comment thread CHANGELOG.md
Comment thread src/wrap/paragraph/spanning_code.rs
Comment thread src/wrap/paragraph/tail_reflow.rs
Comment thread tests/wrap_code_span_reflow_properties.rs Outdated
leynos and others added 11 commits July 27, 2026 19:51
Keep resolved list continuations buffered through their paragraph boundary
so trailing prose reaches its fixed-point layout in one formatter run.

Preserve authored source breaks when joining an inline-code span would
create an overlong line, while retaining the existing fallback for
ambiguous close-and-reopen spans.

Cover both reported examples with integration and property tests, and
document the wrapping contract for users and maintainers.
Retain only authored boundaries inside inline-code spans that would exceed
the wrap width when joined. Keep surrounding prose eligible for greedy
reflow, account for paragraph indentation, and continue scanning after an
unmatched fence run.

Cover the fallback with concrete prefixed-context regressions and targeted
properties. Reconcile the user and developer documentation with the
boundary-preserving guarantee and document its state and tracing decisions.
Replace the absolute no-split claim with the normal fitting rule and point
readers to the existing exception for conforming authored boundaries in an
overlong cross-line span.
Detect qualifying overlong spans before invoking the greedy wrapper, avoid
cloning span text, and use binary authored-boundary lookup. Document the
helper contract and complexity so its fallback cost is explicit.

Number the developer guide's remaining long-form hierarchy for stable review
cross-references.
Keep an over-width source line from disabling authored-boundary preservation
in a later conforming hard-break group. Leave each ineligible group on the
ordinary atomic wrapping path.

Cover the mixed-group behaviour with a regression combining an intentionally
over-width atomic span and a qualifying cross-line span.
Keep the reopened-span trace boundary categorical and expose its byte
position through a dedicated offset field. Document why pending-prefix
flushing deliberately rewraps its tail in a second stage to preserve
deterministic, idempotent output.
Keep double-space and odd-backslash boundaries intact while independently
rewrapping pending-prefix tail segments, including stable continuation
alignment on subsequent formatter passes.

Clarify continuation dispatch helpers, remove the avoidable spanning-code
piece clone, and align the developer source map with the pending submodule.
Reuse one hard-break marker detector while emitting stable pending-prefix
tails, and preserve authored markers for top-level paragraphs as well as
indented continuations.

Strengthen fixed-point and content coverage, consolidate continuation-prefix
cases, and add bounded diagnostics for failed span-boundary lookup.

Remove duplicated developer guidance and restore the malformed architecture
example so the documentation remains formatter- and lint-stable.
Move `append_stable_pending_prefix`, `emit_tail_segment`,
`ensure_trailing_hard_break_on_last_line`, and
`trailing_hard_break_marker_len` out of `paragraph.rs` into a dedicated
`tail_reflow` submodule. This isolates the deterministic tail-rewrap and
hard-break logic from the buffer-management code and brings `paragraph.rs`
back under the 400-line limit (442 -> 366 lines). Behaviour is unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Rebasing onto origin/main auto-merged docs/architecture.md and
docs/developers-guide.md with the Weave driver, which introduced two
semantic corruptions that failed markdownlint:

- developers-guide.md duplicated the entire "Stateful pipeline helpers"
  through "Breaks module" section verbatim; remove the duplicate copy.
- architecture.md lost the "After:" label and its opening fence for the
  footnote-rewrite example, folding the "After" footnote references into
  the "Before" block and duplicating the trailing paragraph; restore the
  two distinct Before/After ```markdown examples.

Both files now pass make markdownlint and make nixie.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Address review feedback on the cross-line code-span wrapping work:

- Extract `trailing_hard_break_marker_len` into a new `hard_break`
  submodule so both the stable tail-reflow path and the spanning-code
  fallback share one parity check.
- Fix `restore_last_hard_break` to detect an existing hard break via the
  shared helper instead of a two-space suffix, so a final backslash
  hard-break marker is preserved rather than doubled.
- Document `ensure_trailing_hard_break_on_last_line`, noting the in-place
  mutation, the empty-output no-op, and the odd trailing-backslash case.
- Reorder the first reflow property so its `prop_assume!` precedes
  `wrap_text`, matching the assumption-before-action ordering of the
  second property.
- Qualify the #370 changelog entry: authored line boundaries are
  preserved only when joining the span exceeds the wrap width and every
  authored source line already fits within that width.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@lodyai
lodyai Bot force-pushed the issue-375-370-wrapping-of-code-spans branch from 89bd85c to ff6ff3a Compare July 27, 2026 17:53
@leynos
leynos merged commit 4d33369 into main Jul 27, 2026
4 checks passed
@leynos
leynos deleted the issue-375-370-wrapping-of-code-spans branch July 27, 2026 17:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

3 participants